index.py 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083
  1. import json
  2. import logging
  3. import subprocess
  4. import threading
  5. import time
  6. from collections import OrderedDict
  7. from datetime import datetime
  8. import requests
  9. from django.http import JsonResponse
  10. from django.shortcuts import render
  11. from django.views import View
  12. from django.views.generic import TemplateView
  13. from azoauth.config import *
  14. from model.models import UserModel, UidRtspModel, AlexaAuthModel, UserCountModel, SwitchModel
  15. from object.RedisObject import RedisObject
  16. from object.ResObject import ResObject
  17. from object.ResponseObject import ResponseObject
  18. from object.tkObject import tkObject
  19. from service.CommonService import CommonService
  20. import uuid
  21. class authView(TemplateView):
  22. def post(self, request, *args, **kwargs):
  23. request.encoding = 'utf-8'
  24. request_dict = json.loads(request.body.decode('utf-8'))
  25. return self.validate(request_dict)
  26. def get(self, request, *args, **kwargs):
  27. request.encoding = 'utf-8'
  28. request_dict = request.GET
  29. return self.validate(request_dict)
  30. def validate(self, request_dict):
  31. logger = logging.getLogger('django')
  32. logger.info('------oa2/auth请求参数: {}------'.format(request_dict))
  33. state = request_dict.get("state", '')
  34. client_id = request_dict.get("client_id", '')
  35. response_type = request_dict.get("response_type", '')
  36. scope = request_dict.get("scope", '')
  37. redirect_uri = request_dict.get("redirect_uri", '')
  38. context = {
  39. 'state': state,
  40. 'client_id': client_id,
  41. 'response_type': response_type,
  42. 'scope': scope,
  43. 'redirect_uri': redirect_uri,
  44. 'skill_name': 'zosi smart'
  45. }
  46. return render(request=None, template_name="login.html", context=context)
  47. # Anlapus登录
  48. class authAnlapusView(TemplateView):
  49. def post(self, request, *args, **kwargs):
  50. request.encoding = 'utf-8'
  51. request_dict = json.loads(request.body.decode('utf-8'))
  52. return self.validate(request_dict)
  53. def get(self, request, *args, **kwargs):
  54. request.encoding = 'utf-8'
  55. request_dict = request.GET
  56. return self.validate(request_dict)
  57. def validate(self, request_dict):
  58. state = request_dict.get("state", '')
  59. client_id = request_dict.get("client_id", '')
  60. response_type = request_dict.get("response_type", '')
  61. scope = request_dict.get("scope", '')
  62. redirect_uri = request_dict.get("redirect_uri", '')
  63. context = {
  64. 'state': state,
  65. 'client_id': client_id,
  66. 'response_type': response_type,
  67. 'scope': scope,
  68. 'redirect_uri': redirect_uri,
  69. 'skill_name': 'Anlapus'
  70. }
  71. return render(request=None, template_name="login_anlapus.html", context=context)
  72. # loocam登录
  73. class authLoocamView(TemplateView):
  74. def post(self, request, *args, **kwargs):
  75. request.encoding = 'utf-8'
  76. request_dict = json.loads(request.body.decode('utf-8'))
  77. return self.validate(request_dict)
  78. def get(self, request, *args, **kwargs):
  79. request.encoding = 'utf-8'
  80. request_dict = request.GET
  81. return self.validate(request_dict)
  82. def validate(self, request_dict):
  83. state = request_dict.get("state", '')
  84. client_id = request_dict.get("client_id", '')
  85. response_type = request_dict.get("response_type", '')
  86. scope = request_dict.get("scope", '')
  87. redirect_uri = request_dict.get("redirect_uri", '')
  88. context = {
  89. 'state': state,
  90. 'client_id': client_id,
  91. 'response_type': response_type,
  92. 'scope': scope,
  93. 'redirect_uri': redirect_uri,
  94. 'skill_name': 'loocam'
  95. }
  96. logger = logging.getLogger('django')
  97. logger.info('loocam请求登录网页,参数为{}'.format(context))
  98. return render(request=None, template_name="login_loocam.html", context=context)
  99. # VSees登录
  100. class authVSeesView(TemplateView):
  101. def post(self, request, *args, **kwargs):
  102. request.encoding = 'utf-8'
  103. request_dict = json.loads(request.body.decode('utf-8'))
  104. return self.validate(request_dict)
  105. def get(self, request, *args, **kwargs):
  106. request.encoding = 'utf-8'
  107. request_dict = request.GET
  108. return self.validate(request_dict)
  109. def validate(self, request_dict):
  110. state = request_dict.get("state", '')
  111. client_id = request_dict.get("client_id", '')
  112. response_type = request_dict.get("response_type", '')
  113. scope = request_dict.get("scope", '')
  114. redirect_uri = request_dict.get("redirect_uri", '')
  115. context = {
  116. 'state': state,
  117. 'client_id': client_id,
  118. 'response_type': response_type,
  119. 'scope': scope,
  120. 'redirect_uri': redirect_uri,
  121. 'skill_name': 'VSees'
  122. }
  123. logger = logging.getLogger('django')
  124. logger.info('VSees请求登录网页,参数为{}'.format(context))
  125. return render(request=None, template_name="login_VSees.html", context=context)
  126. class loginHandleView(TemplateView):
  127. def post(self, request, *args, **kwargs):
  128. request.encoding = 'utf-8'
  129. request_dict = json.loads(request.body.decode('utf-8'))
  130. return self.validate(request_dict)
  131. def get(self, request, *args, **kwargs):
  132. request.encoding = 'utf-8'
  133. request_dict = request.GET
  134. return self.validate(request_dict)
  135. def validate(self, request_dict):
  136. response = ResObject()
  137. user = request_dict.get("user", '')
  138. pwd = request_dict.get("pwd", '')
  139. state = request_dict.get("state", '')
  140. client_id = request_dict.get("client_id", '')
  141. response_type = request_dict.get("response_type", '')
  142. scope = request_dict.get("scope", '')
  143. redirect_uri = request_dict.get("redirect_uri", '')
  144. skill_name = request_dict.get("skill_name", '')
  145. # 返回code
  146. logger = logging.getLogger('django')
  147. logger.info('------开始认证登录------参数:{}'.format(request_dict))
  148. # 请求美洲服登录接口
  149. region_code = 'US'
  150. auth_request_url = '{SERVER_PREFIX}/oalexa/auth'.format(SERVER_PREFIX=SERVER_PREFIX)
  151. requests_data = {'userName': user, 'userPwd': pwd}
  152. res = requests.post(url=auth_request_url, data=requests_data)
  153. if res.status_code != 200:
  154. return response.json(10, res={'错误': '请求响应异常'})
  155. res_json = res.json()
  156. logger.info('美洲服务器响应: {}'.format(res_json))
  157. # 账号不存在/密码错误/账号存在但用户地区为欧洲时请求欧洲服
  158. result_code = res_json['result_code']
  159. if result_code == 104 or result_code == 111 or (result_code == 0 and res_json['result']['region_code'] == 'EU'):
  160. auth_request_url = '{SERVER_PREFIX}/oalexa/auth'.format(SERVER_PREFIX=SERVER_PREFIX_EU)
  161. res = requests.post(url=auth_request_url, data=requests_data)
  162. if res.status_code != 200:
  163. return response.json(10, res={'错误': '请求响应异常'})
  164. res_json = res.json()
  165. logger.info('欧洲服务器响应: {}'.format(res_json))
  166. if res_json['result_code'] == 0:
  167. region_code = 'EU'
  168. if res_json['result_code'] != 0:
  169. return response.json(10, res={'msg': 'error'}, extra={'msg': res_json['reason']})
  170. nowTime = int(time.time())
  171. code = CommonService.encrypt_data(32)
  172. userID = res_json['result']['userID']
  173. user_qs = UserModel.objects.filter(userID=userID)
  174. if user_qs.exists():
  175. user_qs.update(region_code=region_code, code=code, updTime=nowTime)
  176. else:
  177. UserModel.objects.create(userID=userID, region_code=region_code, code=code, addTime=nowTime,
  178. updTime=nowTime)
  179. year_month = str(time.strftime('%Y%m', time.localtime(nowTime))) # 获取当前年月
  180. user_count_qs = UserCountModel.objects.filter(skill_name=skill_name, year_month=year_month).values('amount')
  181. if not user_count_qs.exists():
  182. UserCountModel.objects.create(skill_name=skill_name, year_month=year_month, amount=1)
  183. else:
  184. # 用户数量+1
  185. amount = user_count_qs[0]['amount'] + 1
  186. user_count_qs.update(amount=amount)
  187. redirect_uri += '?code=' + code + '&state=' + state
  188. logger.info('------认证登录响应------:{}'.format(redirect_uri))
  189. return response.json(0, res=redirect_uri)
  190. class oa2TokenView(TemplateView):
  191. def post(self, request, *args, **kwargs):
  192. request.encoding = 'utf-8'
  193. # request_dict = json.loads(request.body.decode('utf-8'))
  194. request_dict = request.POST
  195. return self.validate(request_dict)
  196. def get(self, request, *args, **kwargs):
  197. request.encoding = 'utf-8'
  198. request_dict = request.GET
  199. return self.validate(request_dict)
  200. def validate(self, request_dict):
  201. # 增加对code和client_id的校验代码,返回access_token和refresh_token
  202. code = request_dict.get("code", None)
  203. grant_type = request_dict.get("grant_type", None) # refresh_token, authorization_code
  204. client_id = request_dict.get("client_id", None)
  205. refresh_token = request_dict.get("refresh_token", None)
  206. logger = logging.getLogger('django')
  207. logger.info('请求获取令牌接口参数:{}'.format(request_dict))
  208. # 根据用户授权码获取令牌
  209. user_qs = UserModel.objects.filter(code=code)
  210. if not user_qs.exists():
  211. if grant_type == 'authorization_code':
  212. user_qs = UserModel.objects.filter(user_authorization_code=code)
  213. else:
  214. user_qs = UserModel.objects.filter(refresh_token=refresh_token)
  215. if not user_qs.exists():
  216. res_json = {'msg': 'code not exists'}
  217. logger.info('请求获取令牌接口响应:{}'.format(res_json))
  218. return JsonResponse(res_json)
  219. access_token = CommonService.encrypt_data(randomlength=32)
  220. refresh_token = CommonService.encrypt_data(randomlength=32)
  221. is_update = user_qs.update(access_token=access_token, refresh_token=refresh_token)
  222. if is_update:
  223. res_json = {
  224. "access_token": access_token,
  225. "token_type": "bearer",
  226. "expires_in": 3600,
  227. "refresh_token": refresh_token,
  228. }
  229. logger.info('请求获取令牌接口响应:{}'.format(res_json))
  230. return JsonResponse(res_json)
  231. else:
  232. logger.info({'msg': 'error'})
  233. return JsonResponse({'msg': 'error'})
  234. def runSendRtspMsg_thread(UID, PWD, MSG):
  235. command = "./pushtool {UID} {PWD} {MSG} 1".format(UID=UID, PWD=PWD, MSG=MSG)
  236. print('command=>{command}'.format(command=command))
  237. try:
  238. back = subprocess. \
  239. Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE). \
  240. communicate(timeout=10)
  241. except Exception as e:
  242. return False
  243. else:
  244. print("back0----", back[0].decode()) # 注意需要进行解码操作,默认输出的是字节
  245. print("back1----", back[1].decode()) # back是一个元祖,可以通过元祖取值的方式获取结果
  246. return True
  247. class oa2RtspStartView(TemplateView):
  248. def post(self, request, *args, **kwargs):
  249. request.encoding = 'utf-8'
  250. request_dict = request.POST
  251. return self.validate(request_dict)
  252. def get(self, request, *args, **kwargs):
  253. request.encoding = 'utf-8'
  254. request_dict = request.GET
  255. return self.validate(request_dict)
  256. def validate(self, request_dict):
  257. st = request_dict.get("st", 0)
  258. uid = request_dict.get("id", '')
  259. access_token = request_dict.get("access_token", '')
  260. skill_name = request_dict.get("skill_name", 'zosi smart')
  261. user_qs = UserModel.objects.filter(access_token=access_token)
  262. if not user_qs.exists():
  263. return JsonResponse({'错误': '用户数据不存在'})
  264. ur_qs = UidRtspModel.objects.filter(uid=uid).values('user_id', 'uid', 'nick', 'rtsp_url', 'password')
  265. if not ur_qs.exists():
  266. return JsonResponse({'错误': 'uid数据不存在'})
  267. # 确认设备的用户地区
  268. region = 'US'
  269. user_id = ur_qs[0]['user_id']
  270. user_qs = UserModel.objects.filter(userID=user_id).values('region_code')
  271. if user_qs.exists():
  272. region = user_qs[0]['region_code']
  273. UID = ur_qs[0]['uid']
  274. nick = ur_qs[0]['nick']
  275. PWD = ur_qs[0]['password']
  276. stream_name = ur_qs[0]['rtsp_url']
  277. channel = '0'
  278. if '_' in UID:
  279. # 多通道设备
  280. channel = UID[-1:]
  281. UID = UID[:-2]
  282. # 根据用户地区确认域名
  283. domain_name = SERVER_PREFIX_EU if region == 'EU' else SERVER_PREFIX
  284. RESP_SERVER_DOMAIN = RESP_SERVER_DOMAIN_DATA[region]
  285. MSG = '{}://{}:8554/{}'.format(RTSP_PREFIX, RESP_SERVER_DOMAIN, stream_name)
  286. # 测试往go2rtc推流
  287. if UID == '3VCV5HZD9DRPA6EU111A':
  288. MSG = 'rtsp://144.24.9.228:8554/camera1'
  289. logger = logging.getLogger('django')
  290. logger.info('------{} 开始向设备下发推流指令------'.format(skill_name))
  291. # 此处用线程异步发送指令
  292. if int(st) == 1:
  293. send_flag = self.runSendStop(UID, PWD, MSG)
  294. logger.info('----------send_flag---st=1-----------------')
  295. if send_flag:
  296. return JsonResponse({'msg': 'stop yes', 'code': 0})
  297. else:
  298. return JsonResponse({'msg': 'stop no', 'code': 0})
  299. # 创建并启动线程
  300. thread = threading.Thread(
  301. target=self.send_rtsp_command_thread,
  302. args=(domain_name, UID, PWD, MSG, channel, logger, region)
  303. )
  304. thread.start()
  305. # 拉流地址
  306. rtsp_uri = '{}://{}:443/{}'.format(RTSP_PREFIX, RESP_SERVER_DOMAIN, stream_name)
  307. stop_time = int(time.time()) + 2 * 60
  308. expirationTime = time.strftime('%Y-%m-%dT%H:%MZ', time.localtime(stop_time))
  309. res_json = {
  310. 'uid': UID,
  311. 'pwd': PWD,
  312. 'msg': MSG,
  313. 'uri': rtsp_uri,
  314. 'endpointId': uid,
  315. 'friendlyName': nick,
  316. 'manufacturerName': skill_name,
  317. 'expirationTime': expirationTime,
  318. 'description': 'Camera connected via {}'.format(skill_name),
  319. 'audioCodecs': 'ACC',
  320. 'videoCodecs': 'H264',
  321. 'protocols': ['RTSP'],
  322. 'idleTimeoutSeconds': 5,
  323. 'modelName': 'P1425-LE',
  324. 'authorizationTypes': ['NONE'],
  325. 'manufacturerId': 'zosi-ACCC8E5E7513',
  326. 'resolutions': {'width': 640, 'height': 360},
  327. }
  328. logger.info('------------返回控制摄像头的信息---------------: {}'.format(res_json))
  329. return JsonResponse(res_json, safe=False)
  330. def send_rtsp_command_thread(self, domain_name, UID, PWD, MSG, channel, logger, region):
  331. """异步发送RTSP命令的线程函数"""
  332. thing_name = CommonService.query_serial_with_uid(UID)
  333. topic_name = 'ansjer/generic/{}'.format(thing_name)
  334. msg = OrderedDict(
  335. [
  336. ('alexaRtspCommand', MSG),
  337. ('enable', 1),
  338. ]
  339. )
  340. publish_result = CommonService.req_publish_mqtt_msg(thing_name, topic_name, msg)
  341. logger.info('{}下发指令结果:{}'.format(UID, publish_result))
  342. command = "./pushtool {UID} {PWD} {MSG} 1 {channel}".format(
  343. UID=UID, PWD=PWD, MSG=MSG, channel=channel)
  344. # 请求MQTT发布消息
  345. url = '{}/iot/requestPublishMessage'.format(domain_name)
  346. requests_data = {'UID': UID, 'rtsp': MSG, 'enable': '1'}
  347. try:
  348. r = requests.post(url, requests_data)
  349. if r.status_code == 200:
  350. res = r.json()
  351. logger.info('请求MQTT发布消息参数:{},result_code: {}'.format(requests_data, res['result_code']))
  352. if res['result_code'] == 0:
  353. logger.info('请求MQTT下发指令成功---正式服')
  354. elif res['result_code'] == 10044:
  355. url = '{}/iot/requestPublishMessage'.format(SERVER_PREFIX_TEST)
  356. r = requests.post(url, requests_data)
  357. if r.status_code == 200:
  358. res = r.json()
  359. if res['result_code'] == 0:
  360. logger.info('请求MQTT下发指令成功---测试服')
  361. else:
  362. self.runSendRtspMsg(logger, region, command)
  363. else:
  364. self.runSendRtspMsg(logger, region, command)
  365. else:
  366. self.runSendRtspMsg(logger, region, command)
  367. else:
  368. self.runSendRtspMsg(logger, region, command)
  369. except Exception as e:
  370. logger.error('发送RTSP命令异常: {}'.format(str(e)))
  371. self.runSendRtspMsg(logger, region, command)
  372. def runReqRtspMsg(self, UID, PWD, MSG):
  373. request_url = 'http://localhost:5000/?UID={UID}&MSG={MSG}&CMD=1&PWD={PWD}'. \
  374. format(UID=UID, PWD=PWD, MSG=MSG)
  375. res = requests.get(url=request_url)
  376. print(res)
  377. return True
  378. # 触发此方法,让摄像头推流到MSG流地址
  379. def runSendRtspMsg(self, logger, region, command):
  380. logger.info('------------推流指令: {}---------------'.format(command))
  381. if region == 'CN':
  382. logger.info('------------国内发送推流指令---------------')
  383. url = "http://52.83.252.41:7880/alexa/command?command={command}".format(command=command)
  384. # 请求国内服务器调用pushtool
  385. try:
  386. requests.get(url=url, timeout=10)
  387. except Exception as e:
  388. logger.info('请求国内服务器调用pushtool异常: {}'.format(repr(e)))
  389. else:
  390. logger.info('------------国外发送推流指令---------------')
  391. try:
  392. back = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE). \
  393. communicate(timeout=10)
  394. logger.info('back: {}'.format(str(back[0].decode()) + str(back[1].decode())))
  395. except Exception as e:
  396. self.runSendRtspMsg(logger, 'CN', command) # 调用失败时尝试用国内的发送
  397. logger.info('调用pushtool异常: {}'.format(repr(e)))
  398. def runSendStop(self, UID, PWD, MSG):
  399. command = "./pushtool {UID} {PWD} {MSG} 0".format(UID=UID, PWD=PWD, MSG=MSG)
  400. print('command=>{command}'.format(command=command))
  401. try:
  402. back = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE). \
  403. communicate(timeout=10)
  404. except Exception as e:
  405. return False
  406. else:
  407. print("back0----", back[0].decode()) # 注意需要进行解码操作,默认输出的是字节
  408. print("back1----", back[1].decode()) # back是一个元祖,可以通过元祖取值的方式获取结果
  409. return True
  410. class oa2DiscoveryDevice(TemplateView):
  411. def post(self, request, *args, **kwargs):
  412. request.encoding = 'utf-8'
  413. request_dict = request.POST
  414. return self.validate(request_dict)
  415. def get(self, request, *args, **kwargs):
  416. request.encoding = 'utf-8'
  417. request_dict = request.GET
  418. return self.validate(request_dict)
  419. def validate(self, request_dict):
  420. # 增加对code和client_id的校验代码,返回access_token和refresh_token
  421. skill_name = request_dict.get("skill_name", 'zosi smart')
  422. access_token = request_dict.get("access_token", None)
  423. logger = logging.getLogger('django')
  424. logger.info('--------{} 开始搜索设备--------'.format(skill_name))
  425. response = ResObject()
  426. user_qs = UserModel.objects.filter(access_token=access_token)
  427. if not user_qs.exists():
  428. return response.json(500, res={'msg': '用户数据不存在'})
  429. user = user_qs[0]
  430. userID = user.userID
  431. region_code = user.region_code
  432. logger.info('搜索设备的用户id:{},地区:{}'.format(userID, region_code))
  433. # AlexaAuthModel表数据
  434. alexAuth = AlexaAuthModel.objects.filter(token=access_token, skill_name=skill_name).order_by('-addTime')
  435. if alexAuth.exists():
  436. auth_res = alexAuth.values()
  437. event_access_token = auth_res[0]['access_token']
  438. event_refresh_token = auth_res[0]['refresh_token']
  439. event_token = auth_res[0]['token']
  440. event_expiresTime = auth_res[0]['expiresTime']
  441. event_addTime = auth_res[0]['addTime']
  442. event_updTime = auth_res[0]['updTime']
  443. event_alexa_region = auth_res[0]['alexa_region']
  444. AlexaAuthModel.objects.filter(userID=userID).delete()
  445. alexAuth.delete()
  446. AlexaAuthModel.objects.create(
  447. userID=userID,
  448. access_token=event_access_token,
  449. refresh_token=event_refresh_token,
  450. token=event_token,
  451. expiresTime=event_expiresTime,
  452. addTime=event_addTime,
  453. updTime=event_updTime,
  454. alexa_region=event_alexa_region,
  455. skill_name=skill_name,
  456. )
  457. # 请求搜索设备
  458. domain_name = SERVER_PREFIX_EU if region_code == 'EU' else SERVER_PREFIX
  459. auth_request_url = '{}/oalexa/discoveryuid'.format(domain_name)
  460. requests_data = {'sid': 'admin', 'sst': 'admin', 'alexa_user_id': userID}
  461. res = requests.post(url=auth_request_url, data=requests_data)
  462. if res.status_code != 200:
  463. return response.json(500, res={'msg': 'discover device error'})
  464. res_json = res.json()
  465. logger.info('{}服务器搜索设备响应:{}'.format(region_code, res_json))
  466. if res_json['result_code'] != 0:
  467. return response.json(500, res={'msg': 'discover device error'})
  468. uid_arr = res_json['result']['uid_arr']
  469. rtko = tkObject(rank=1)
  470. now_time = int(time.time())
  471. user.uid_rtsp.clear()
  472. res_json = []
  473. uid_rtsp_id_list = []
  474. for uid_a in uid_arr:
  475. uid = uid_a['uid']
  476. serial_number = uid_a['serial_number']
  477. nick = uid_a['nick']
  478. rtsp_url = rtko.encrypt(data=uid)
  479. try:
  480. uid_rtsp_qs = UidRtspModel.objects.get(uid=uid)
  481. except UidRtspModel.DoesNotExist:
  482. uid_rtsp_qs = UidRtspModel.objects.create(
  483. user_id=userID, uid=uid, serial_number=serial_number, nick=nick, region=region_code,
  484. password=uid_a['password'], rtsp_url=rtsp_url, addTime=now_time, updTime=now_time)
  485. else:
  486. uid_rtsp_qs.nick = nick
  487. uid_rtsp_qs.region = region_code
  488. uid_rtsp_qs.password = uid_a['password']
  489. uid_rtsp_qs.user_id = userID
  490. uid_rtsp_qs.serial_number = serial_number
  491. uid_rtsp_qs.save()
  492. uid_rtsp_id_list.append(uid_rtsp_qs.id)
  493. rtsp_domain = RESP_SERVER_DOMAIN_DATA[region_code]
  494. rtsp_uri = '{}://{}:443/{}'.format(RTSP_PREFIX, rtsp_domain, rtsp_url)
  495. ur_data = {
  496. 'uri': rtsp_uri,
  497. 'endpointId': uid,
  498. 'friendlyName': nick,
  499. 'manufacturerName': skill_name,
  500. 'description': 'Camera connected via {}'.format(skill_name),
  501. 'protocols': ['RTSP'],
  502. 'audioCodecs': ['ACC'],
  503. 'videoCodecs': ['H264'],
  504. 'modelName': 'P1425-LE',
  505. 'authorizationTypes': ['NONE'],
  506. 'manufacturerId': 'zosi-ACCC8E5E7513',
  507. 'resolutions': [{'width': 640, 'height': 360}],
  508. }
  509. res_json.append(ur_data)
  510. user.uid_rtsp.add(*uid_rtsp_id_list)
  511. logger.info('搜索设备返回值: {}'.format(res_json))
  512. return JsonResponse(res_json, safe=False)
  513. class oa2DiscoverySwitch(TemplateView):
  514. def post(self, request, *args, **kwargs):
  515. request.encoding = 'utf-8'
  516. request_dict = request.POST
  517. return self.validate(request_dict)
  518. def get(self, request, *args, **kwargs):
  519. request.encoding = 'utf-8'
  520. request_dict = request.GET
  521. return self.validate(request_dict)
  522. def validate(self, request_dict):
  523. # 增加对code和client_id的校验代码,返回access_token和refresh_token
  524. skill_name = request_dict.get("skill_name", 'loocam')
  525. access_token = request_dict.get("access_token", None)
  526. logger = logging.getLogger('django')
  527. logger.info('--------{} 开始搜索设备--------'.format(skill_name))
  528. user_qs = UserModel.objects.filter(access_token=access_token)
  529. if not user_qs.exists():
  530. return JsonResponse({'错误': '用户数据不存在'})
  531. response = ResObject()
  532. user = user_qs.first()
  533. userID = user.userID
  534. region_code = user.region_code
  535. logger.info('userID: {},region_code:{}'.format(userID, region_code))
  536. # 更新事件网关接口
  537. alexAuth = AlexaAuthModel.objects.filter(token=access_token, skill_name=skill_name).order_by('-addTime')
  538. if alexAuth.exists():
  539. auth_res = alexAuth.values()
  540. event_access_token = auth_res[0]['access_token']
  541. event_refresh_token = auth_res[0]['refresh_token']
  542. event_token = auth_res[0]['token']
  543. event_expiresTime = auth_res[0]['expiresTime']
  544. event_addTime = auth_res[0]['addTime']
  545. event_updTime = auth_res[0]['updTime']
  546. event_alexa_region = auth_res[0]['alexa_region']
  547. AlexaAuthModel.objects.filter(userID=userID).delete()
  548. alexAuth.delete()
  549. logger.info('update_event_access_token')
  550. logger.info(event_token)
  551. AlexaAuthModel.objects.create(
  552. userID=userID,
  553. access_token=event_access_token,
  554. refresh_token=event_refresh_token,
  555. token=event_token,
  556. expiresTime=event_expiresTime,
  557. addTime=event_addTime,
  558. updTime=event_updTime,
  559. alexa_region=event_alexa_region,
  560. skill_name=skill_name,
  561. )
  562. domain_name = SERVER_PREFIX_EU if region_code == 'EU' else SERVER_PREFIX
  563. auth_request_url = '{}/oalexa/discoveryswitch'.format(domain_name)
  564. requests_data = {'sid': 'admin', 'sst': 'admin', 'alexa_user_id': userID}
  565. res = requests.post(url=auth_request_url, data=requests_data)
  566. res_json = res.json()
  567. logger.info('{}正式服务器响应: {}'.format(region_code, res_json))
  568. # 添加测试服务器测试
  569. if res_json['result_code'] != 0:
  570. auth_request_url = '{}/oalexa/discoveryswitch'.format(SERVER_PREFIX_TEST)
  571. res = requests.post(url=auth_request_url, data=requests_data)
  572. res_json = res.json()
  573. logger.info('请求服务器url: {}'.format(auth_request_url))
  574. logger.info('服务器响应: {}'.format(res_json))
  575. if res_json['result_code'] != 0:
  576. return response.json(0, res={'msg': 'error'})
  577. switch_list = res_json['result']['switch_list']
  578. now_time = int(time.time())
  579. res_json = []
  580. for switch in switch_list:
  581. serial_number = switch['uid']
  582. nick = switch['nick']
  583. switch_info_qs = SwitchModel.objects.filter(serial_number=serial_number, userID=userID)
  584. if switch_info_qs.exists():
  585. switch_info_qs.update(nick=nick, region=switch['region'], updTime=now_time)
  586. else:
  587. SwitchModel.objects.create(serial_number=serial_number, nick=nick, region=switch['region'],
  588. addTime=now_time, updTime=now_time, userID=userID)
  589. ur_data = {
  590. 'endpointId': serial_number,
  591. 'friendlyName': nick,
  592. 'manufacturerName': skill_name,
  593. 'description': 'Plug connected via {}'.format(skill_name),
  594. 'modelName': 'P1425-LE',
  595. 'manufacturerId': 'zosi-ACCC8E5E7513',
  596. }
  597. res_json.append(ur_data)
  598. logger.info('搜索设备返回值: {}'.format(res_json))
  599. return JsonResponse(res_json, safe=False)
  600. class powerController(TemplateView):
  601. def post(self, request, *args, **kwargs):
  602. request.encoding = 'utf-8'
  603. request_dict = request.POST
  604. return self.power_controller(request_dict)
  605. def get(self, request, *args, **kwargs):
  606. request.encoding = 'utf-8'
  607. request_dict = request.GET
  608. return self.power_controller(request_dict)
  609. def power_controller(self, request_dict):
  610. serial_number = request_dict.get('serial_number', '')
  611. access_token = request_dict.get('access_token', '')
  612. skill_name = request_dict.get('skill_name', 'loocam')
  613. power_controller = request_dict.get('power_controller', '')
  614. if not all([serial_number, access_token, skill_name]):
  615. return JsonResponse({'result_code': '500', '错误': '缺少参数'})
  616. user_qs = UserModel.objects.filter(access_token=access_token)
  617. if not user_qs.exists():
  618. return JsonResponse({'result_code': '500', '错误': '用户数据不存在'})
  619. userID = user_qs.first().userID
  620. switch_qs = SwitchModel.objects.filter(serial_number=serial_number, userID=userID).values('nick', 'region')
  621. if not switch_qs.exists():
  622. return JsonResponse({'result_code': '500', '错误': 'serial_number数据不存在'})
  623. logger = logging.getLogger('django')
  624. logger.info('{} 控制插座 {}'.format(skill_name, serial_number))
  625. # 请求MQTT发布消息
  626. region = switch_qs[0]['region']
  627. if region == 'EU':
  628. domain_name = SERVER_PREFIX_EU
  629. elif region == 'CN':
  630. domain_name = SERVER_PREFIX_TEST
  631. else:
  632. domain_name = SERVER_PREFIX
  633. url = '{}/api/loocam/open/socket/alexa-socket-switch'.format(domain_name)
  634. requests_data = {'serial_number': serial_number, 'power_controller': power_controller} # TurnOn, TurnOff
  635. r = requests.post(url, requests_data)
  636. if r.status_code != 200:
  637. return JsonResponse({'result_code': '500', '错误': '请求服务器响应异常'})
  638. res = r.json()
  639. logger.info('{}服务器返回状态: {}'.format(region, res))
  640. if res['result_code'] != 0:
  641. return JsonResponse({'result_code': '500', '错误': '请求MQTT下发指令失败'})
  642. else:
  643. logger.info('请求MQTT下发指令成功')
  644. return JsonResponse({'result_code': '0'})
  645. class RtcController(TemplateView):
  646. def get(self, request, *args, **kwargs):
  647. request.encoding = 'utf-8'
  648. operation = kwargs.get('operation')
  649. return self.validation(request.GET, operation)
  650. def post(self, request, *args, **kwargs):
  651. request.encoding = 'utf-8'
  652. operation = kwargs.get('operation')
  653. return self.validation(request.POST, operation)
  654. def validation(self, request_dict, operation):
  655. if operation == 'rtc':
  656. return self.rtc(request_dict)
  657. @classmethod
  658. def rtc(cls, request_dict):
  659. uid = request_dict.get("uid")
  660. access_token = request_dict.get("access_token")
  661. skill_name = request_dict.get("skill_name")
  662. offer_sdp = request_dict.get("offer_sdp")
  663. if not all([uid, access_token, skill_name, offer_sdp]):
  664. return JsonResponse({'错误': '缺少参数'})
  665. user_qs = UserModel.objects.filter(access_token=access_token)
  666. if not user_qs.exists():
  667. return JsonResponse({'错误': '用户数据不存在'})
  668. ur_qs = UidRtspModel.objects.filter(uid=uid).values('user_id', 'uid', 'nick', 'rtsp_url', 'password')
  669. if not ur_qs.exists():
  670. return JsonResponse({'错误': 'uid数据不存在'})
  671. logger = logging.getLogger('django')
  672. try:
  673. # 请求go2rtc创建流
  674. rtsp = GO2RTC_RTSP.format(uid)
  675. rtc_url = GO2RTC_API.format('api/streams')
  676. params = {
  677. 'src': rtsp,
  678. 'name': uid,
  679. }
  680. r = requests.put(url=rtc_url, data=params, timeout=30)
  681. assert r.status_code == 200
  682. # mqtt下发推流指令
  683. logger.info('------rtc开始向设备下发推流指令:{}------'.format(uid))
  684. # 确认设备的用户地区
  685. region = 'US'
  686. user_id = ur_qs[0]['user_id']
  687. user_qs = UserModel.objects.filter(userID=user_id).values('region_code')
  688. if user_qs.exists():
  689. region = user_qs[0]['region_code']
  690. # 根据用户地区确认域名
  691. domain_name = SERVER_PREFIX_EU if region == 'EU' else SERVER_PREFIX
  692. # 请求MQTT发布消息
  693. url = '{}/iot/requestPublishMessage'.format(domain_name)
  694. requests_data = {'UID': uid, 'rtsp': rtsp, 'enable': '1'} # 1: 开始推流,0: 停止推流; channel: 推流通道
  695. r = requests.post(url, requests_data)
  696. assert r.status_code == 200
  697. res = r.json()
  698. logger.info('rtc请求MQTT发布消息参数:{},result_code: {}'.format(requests_data, res['result_code']))
  699. if res['result_code'] == 0:
  700. logger.info('rtc请求MQTT下发指令成功,地区:{}'.format(region))
  701. elif res['result_code'] == 10044:
  702. url = '{}/iot/requestPublishMessage'.format(SERVER_PREFIX_TEST) # 测试服务器
  703. r = requests.post(url, requests_data)
  704. assert r.status_code == 200
  705. res = r.json()
  706. if res['result_code'] == 0:
  707. logger.info('请求MQTT下发指令成功---测试服')
  708. # 获取SDP
  709. sdp = cls.handle_alexa_offer(offer_sdp, rtsp)
  710. # params = {
  711. # 'src': uid,
  712. # }
  713. # r = requests.get(url=rtc_url, params=params, timeout=30)
  714. # assert r.status_code == 200
  715. # res = r.json()
  716. # # 遍历producers数组,查找包含sdp字段的对象
  717. # sdp = ''
  718. # for producer in res['producers']:
  719. # if 'sdp' in producer:
  720. # sdp = producer['sdp']
  721. # break
  722. res_json = {
  723. 'SDP': sdp
  724. }
  725. logger.info('------------rtc响应数据---------------: {}'.format(res_json))
  726. return JsonResponse(res_json, safe=False)
  727. except Exception as e:
  728. logger.info('rtc异常,error_line:{},error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  729. return JsonResponse({'错误': 'rtc异常'})
  730. class VesseTest(TemplateView):
  731. def get(self, request, *args, **kwargs):
  732. request.encoding = 'utf-8'
  733. operation = kwargs.get('operation')
  734. return self.validation(request.GET, operation, request)
  735. def post(self, request, *args, **kwargs):
  736. request.encoding = 'utf-8'
  737. operation = kwargs.get('operation')
  738. return self.validation(request.POST, operation, request)
  739. def validation(self, request_dict, operation, request):
  740. response = ResponseObject()
  741. if operation == 'get-token': # 获取token
  742. return self.get_token(response)
  743. elif operation == 'get-user-id': # 获取userid
  744. return self.get_user_id(request_dict, response, request)
  745. elif operation == 'get-user-info': # 获取用户信息
  746. return self.get_user_info(request_dict, response, request)
  747. elif operation == 'rtc': # rtc测试
  748. return self.rtc(request_dict, response, request)
  749. def get_token(self, response):
  750. data = {
  751. 'corpid': 'ww467ec1685e8262e6',
  752. 'corpsecret': 'IeUoaQ-0hEhEduCQq1zyfVXjfeZpMsThK1nklszRzUY'
  753. }
  754. try:
  755. token_url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken'
  756. token_response = requests.get(token_url, params=data)
  757. if token_response.status_code == 200:
  758. data = token_response.json()
  759. access_token = data.get("access_token")
  760. if access_token:
  761. return response.json(0, {'access_token': access_token})
  762. else:
  763. return response.json(0, "Failed to get AccessToken.")
  764. else:
  765. code = token_response.status_code
  766. content = token_response.content
  767. return response.json(code, content)
  768. except requests.exceptions.RequestException as e:
  769. print(e)
  770. return response.json(500, repr(e))
  771. def get_user_id(self, request_dict, response, request):
  772. access_token = request_dict.get('access_token', None)
  773. code = request_dict.get('code', None)
  774. if not all([access_token, code]):
  775. return response.json(444, 'error: access_token, code')
  776. data = {
  777. 'access_token': access_token,
  778. 'code': code
  779. }
  780. try:
  781. token_url = 'https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo'
  782. token_response = requests.get(token_url, params=data)
  783. if token_response.status_code == 200:
  784. data = token_response.json()
  785. userid = data.get("userid")
  786. if userid:
  787. return response.json(0, {'userid': userid})
  788. else:
  789. code = token_response.status_code
  790. content = token_response.content
  791. return response.json(code, content)
  792. except Exception as e:
  793. print(e)
  794. return response.json(500, repr(e))
  795. def get_user_info(self, request_dict, response, request):
  796. access_token = request_dict.get('access_token', None)
  797. userid = request_dict.get('userid', None)
  798. if not all([access_token, userid]):
  799. return response.json(444, 'error: access_token, userid')
  800. data = {
  801. 'access_token': access_token,
  802. 'userid': userid
  803. }
  804. try:
  805. token_url = 'https://qyapi.weixin.qq.com/cgi-bin/user/get'
  806. token_response = requests.get(token_url, params=data)
  807. if token_response.status_code == 200:
  808. data = token_response.json()
  809. userid = data.get("userid")
  810. name = data.get('name')
  811. position = data.get('position')
  812. status = data.get('status')
  813. if userid:
  814. url_data = {
  815. 'userid': userid,
  816. 'name': name,
  817. 'position': position,
  818. 'status': status
  819. }
  820. return response.json(0, {'data': url_data})
  821. else:
  822. code = token_response.status_code
  823. content = token_response.content
  824. return response.json(code, content)
  825. except Exception as e:
  826. print(e)
  827. return response.json(500, repr(e))
  828. @staticmethod
  829. def rtc(request_dict, response, request):
  830. uid = request_dict.get('uid', None)
  831. try:
  832. # 请求go2rtc创建流
  833. # rtsp = GO2RTC_RTSP.format(uid)
  834. rtc_url = GO2RTC_API.format('api/streams')
  835. # params = {
  836. # 'src': rtsp,
  837. # 'name': uid,
  838. # }
  839. # r = requests.put(url=rtc_url, params=params, timeout=30)
  840. # assert r.status_code == 200
  841. # 获取SDP
  842. params = {
  843. 'src': uid,
  844. }
  845. r = requests.get(url=rtc_url, params=params, timeout=30)
  846. assert r.status_code == 200
  847. res = r.json()
  848. # 遍历producers数组,查找包含sdp字段的对象
  849. sdp = ''
  850. for producer in res['producers']:
  851. if 'sdp' in producer:
  852. sdp = producer['sdp']
  853. break
  854. res_json = {
  855. 'SDP': sdp
  856. }
  857. return response.json(0)
  858. except Exception as e:
  859. print(e)
  860. return response.json(500, repr(e))
  861. def get_domain_by_region(region):
  862. if region == 'EU':
  863. return SERVER_PREFIX_EU
  864. if region == 'CN':
  865. return SERVER_PREFIX_TEST
  866. return SERVER_PREFIX
  867. def get_uuid():
  868. return str(uuid.uuid4())
  869. class GetSnapshotView(View):
  870. def post(self, request):
  871. logger = logging.getLogger('django')
  872. try:
  873. data = json.loads(request.body)
  874. endpoint_id = data['endpoint_id']
  875. access_token = data['access_token']
  876. correlation_token = data.get('correlation_token', '')
  877. region = data.get('region', 'US')
  878. if not endpoint_id or not access_token:
  879. raise ValueError("endpoint_id 或 access_token 为空")
  880. except Exception as e:
  881. logger.error(f"参数解析/校验失败: {e}")
  882. return JsonResponse({'error': 'BAD_REQUEST'}, status=400)
  883. try:
  884. user = UserModel.objects.get(access_token=access_token)
  885. except UserModel.DoesNotExist:
  886. return JsonResponse({'error': 'INVALID_TOKEN'}, status=401)
  887. # 验证设备归属
  888. if not user.uid_rtsp.filter(uid=endpoint_id).exists():
  889. return JsonResponse({'error': 'NO_SUCH_DEVICE'}, status=403)
  890. # 获取设备信息
  891. device = user.uid_rtsp.get(uid=endpoint_id)
  892. password = device.password or ''
  893. region = getattr(device, 'region', region)
  894. rtsp_path = device.rtsp_url
  895. rtsp_domain = get_domain_by_region(region).split('//')[-1]
  896. rtsp_url = f'rtsp://{rtsp_domain}:8554/{rtsp_path}'
  897. if '_' in endpoint_id:
  898. uid_base, channel = endpoint_id.rsplit('_', 1)
  899. else:
  900. uid_base = endpoint_id
  901. channel = '0'
  902. logger.info(f"[GetSnapshot] uid={uid_base}, channel={channel}, region={region}")
  903. domain_name = get_domain_by_region(region)
  904. # 启动线程异步下发快照指令
  905. threading.Thread(
  906. target=self.send_snapshot_command,
  907. args=(domain_name, uid_base, password, rtsp_url, channel, logger),
  908. daemon=True
  909. ).start()
  910. logger.info(f"[GetSnapshot] 已启动快照指令线程 → domain={domain_name}, UID={uid_base}")
  911. # 返回 DeferredResponse
  912. return JsonResponse({
  913. "event": {
  914. "header": {
  915. "namespace": "Alexa",
  916. "name": "DeferredResponse",
  917. "messageId": get_uuid(),
  918. "correlationToken": correlation_token,
  919. "payloadVersion": "3"
  920. },
  921. "endpoint": {
  922. "endpointId": endpoint_id
  923. },
  924. "payload": {
  925. "estimatedDeferralInSeconds": 7
  926. }
  927. }
  928. })
  929. def send_snapshot_command(self, domain_name, uid, password, rtsp_url, channel, logger):
  930. url = f'{domain_name}/iot/requestPublishMessage'
  931. payload = {
  932. 'UID': uid,
  933. 'rtsp': rtsp_url,
  934. 'enable': '1',
  935. 'channel': channel
  936. }
  937. try:
  938. resp = requests.post(url, data=payload, timeout=10)
  939. if resp.status_code != 200:
  940. logger.error(f"[send_snapshot] {url} 返回 HTTP {resp.status_code}")
  941. return
  942. data = resp.json()
  943. logger.info(f"[send_snapshot] 响应({url}): {data}")
  944. if data.get('result_code') != 0:
  945. logger.error(f"[send_snapshot] 下发失败, result_code={data.get('result_code')}")
  946. except Exception as ex:
  947. logger.error(f"[send_snapshot] 请求异常: {ex}")