bolai_gateway_plug.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import copy
  4. import datetime
  5. import logging
  6. import random
  7. import time
  8. from typing import TYPE_CHECKING
  9. from apps.web.constant import Const
  10. from apps.web.core.adapter.base import SmartBox, start_error_timer
  11. from apps.web.core.device_define.baolai import send_request
  12. from apps.web.core.exceptions import ServiceException
  13. from apps.web.device.models import Device
  14. from apps.web.user.models import MyUser, ConsumeRecord
  15. if TYPE_CHECKING:
  16. pass
  17. logger = logging.getLogger(__name__)
  18. class ChargingGatewayPlugBox(SmartBox):
  19. """
  20. """
  21. def __init__(self, device):
  22. super(ChargingGatewayPlugBox, self).__init__(device)
  23. devObj = self.device.my_obj
  24. self.nodeDict = devObj.nodeDict
  25. self.billingType = devObj.otherConf.get('billingType', 1) # 0 :电量 1:时间 2:功率
  26. self.config_list = devObj.otherConf.get('config_list', [])
  27. self.onceCard = devObj.otherConf.get('onceCard', 100)
  28. self.cardTime = devObj.otherConf.get('cardTime', 180)
  29. self.cardElec = devObj.otherConf.get('cardElec', 1)
  30. self.node_index = None
  31. for nodeIndex, nodeDevNo in devObj.nodeDict.items():
  32. if nodeDevNo == device['devNo']:
  33. self.node_index = int(nodeIndex)
  34. break
  35. else:
  36. continue
  37. self.gatewayDevNo = Device.get_collection().find({'devNo':devObj.devNo})[0]['gateImei']
  38. @property
  39. def isHaveStopEvent(self):
  40. return True
  41. def translate_funcode(self, funCode):
  42. funCodeDict = {
  43. }
  44. return funCodeDict.get(funCode, '')
  45. def translate_event_cmdcode(self, cmdCode):
  46. cmdDict = {
  47. }
  48. return cmdDict.get(cmdCode, '')
  49. def get_port_from_ab(self, portAB):
  50. portConf = {'A': 0, 'B': 1, 'C': 2}
  51. if portAB in portConf:
  52. return portConf[portAB]
  53. return portAB
  54. def get_abport_from_index(self, port):
  55. portConf = {'0': 'A', '1': 'B', '2': 'C'}
  56. return portConf.get(port)
  57. def send_request(self, cmdPath, jsonPara,cmdKind=2):
  58. return send_request(self.device.devNo, self.gatewayDevNo, cmdPath, jsonPara,cmdKind)
  59. def test(self, coins, port=1):
  60. return self.send_request('device/plug/list', {})
  61. @start_error_timer(missMessages=[u"请您选择合适的充电线路、电池类型信息", u"请您选择合适的充电线路", u"该端口正在使用中"])
  62. def start_device(self, package, openId, attachParas):
  63. if attachParas is None:
  64. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路、电池类型信息'})
  65. if not attachParas.has_key('chargeIndex'):
  66. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'})
  67. port = int(self.get_port_from_ab(attachParas['chargeIndex']))
  68. attachParas['chargeIndex'] = port
  69. unit = package.get('unit', u'分钟')
  70. needTime, needElec = None, None
  71. jsonPara = {'node_index': self.node_index, 'port_index': port, 'switch_state': 1}
  72. cmdPath = 'cmd/write-node'
  73. if self.billingType == 1:
  74. if unit == u'秒':
  75. if int(package['time']) < 60:
  76. raise ServiceException({'result': 2, 'description': u'套餐的最小时间不能小于60秒'})
  77. needTime = int(float(package['time']) / 60.0)
  78. jsonPara.update({'charge_time': needTime, 'charge_mode': 1, 'charge_energy': 0})
  79. elif unit == u'分钟':
  80. needTime = int(float(package['time']))
  81. jsonPara.update({'charge_time': needTime, 'charge_mode': 1, 'charge_energy': 0})
  82. elif unit == u'小时':
  83. needTime = int(float(package['time']) * 60)
  84. jsonPara.update({'charge_time': needTime, 'charge_mode': 1, 'charge_energy': 0})
  85. elif unit == u'天':
  86. needTime = int(float(package['time']) * 60 * 24)
  87. jsonPara.update({'charge_time': needTime, 'charge_mode': 1, 'charge_energy': 0})
  88. else:
  89. raise ServiceException({'result': 2, 'description': u'运营商没有配置正确的套餐,请运营商配置正确的套餐'})
  90. elif self.billingType == 0:
  91. if unit == u'度':
  92. needElec = int(float(package['time']) * 1000) # 微度
  93. jsonPara.update({'charge_energy': needElec, 'charge_mode': 0, 'charge_time': 0})
  94. else:
  95. raise ServiceException({'result': 2, 'description': u'运营商没有配置正确的套餐,请运营商配置正确的套餐'})
  96. else:
  97. if not self.config_list:
  98. raise ServiceException({'result': 2, 'description': u'运营商没有配置正确的分档功率,请运营商配置正确的分档功率'})
  99. cmdPath = 'cmd/write-node-with-power'
  100. coins = int(float(package['coins']) * 100) # 单位为分
  101. jsonPara.update({'power': {'money': coins, 'config_list': self.config_list}})
  102. devInfo = self.send_request(cmdPath, jsonPara)
  103. if devInfo['data']['code'] != 0:
  104. raise ServiceException({'result': 2, 'description': u'设备服务器返回错误,建议您重试,或者联系客服'})
  105. if devInfo['rst'] == 0: # 成功
  106. newValue = {
  107. str(port): {
  108. 'status': Const.DEV_WORK_STATUS_WORKING,
  109. 'startTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  110. }
  111. }
  112. else: # TODO result的枚举列出原因
  113. raise ServiceException({'result': 2, 'description': u'充电插座响应异常,请您稍后再试哦'})
  114. servicedInfo = {}
  115. if self.billingType == 1:
  116. finishedTime = int(time.time()) + needTime * 60
  117. devInfo['needTime'] = needTime
  118. servicedInfo = {'needTime': needTime, 'billingType': 'time'}
  119. elif self.billingType == 0:
  120. finishedTime = int(time.time()) + 60 * 60 * 10 # 设定10个小时,确实很难知道可以用多久结束
  121. devInfo['needElec'] = float(package['time'])
  122. servicedInfo = {'needElec': float(package['time']), 'billingType': 'elec'}
  123. else:
  124. finishedTime = int(time.time()) + 60 * 60 * 10 # 设定10个小时,确实很难知道可以用多久结束
  125. servicedInfo = {'billingType': 'power'}
  126. newValue[str(port)].update({'power':10}) # 端口下的数据依赖心跳上报,但是心跳有时间间隔,这样查看服务的时候,因为功率是0,会认为已经结束,这里给一个初始值
  127. newValue.update({'finishedTime': finishedTime})
  128. Device.clear_port_control_cache(self._device['devNo'], port)
  129. Device.update_dev_control_cache(self._device['devNo'], newValue)
  130. devInfo['finished_time'] = finishedTime
  131. devInfo['sequanceNo'] = devInfo['data']['data']['transaction_id']
  132. devInfo['servicedInfo'] = servicedInfo
  133. return devInfo
  134. # 获取设备配置参数
  135. def get_dev_setting(self):
  136. if not self.node_index:
  137. raise ServiceException({'result': 2, 'description': u'读取失败!请您先把插座本身作为子节点添加进来,选择扫码增加子节点,然后扫码添加即可'})
  138. devInfo = self.send_request('cmd/get-node-config', {'node_index': self.node_index},3)
  139. if devInfo['data']['code'] != 0:
  140. raise ServiceException({'result': 2, 'description': u'设备服务器返回错误,建议您重试,或者联系客服'})
  141. config = devInfo['data']['data']
  142. billDict = {'0': 'elec', '1': 'time', '2': 'power'}
  143. config.update({
  144. 'chargeType': billDict.get(str(self.billingType)),
  145. 'once_card': self.onceCard*0.01,
  146. 'time': self.cardTime,
  147. 'elec': self.cardElec,
  148. 'config_list': self.config_list,
  149. 'charge_full_timeout':config['charge_full_time_threshold'],
  150. 'no_load_timeout':config['no_load_time_threshold'],
  151. })
  152. return config
  153. # 设置设备配置参数
  154. def set_dev_setting(self, setConf):
  155. setConf.update({'node_index': self.node_index})
  156. devObj = Device.objects.get(devNo=self._device['devNo'])
  157. billDict = {'elec': 0, 'time': 1, 'power': 2}
  158. devObj.otherConf.update({
  159. 'billingType': billDict.get(setConf['chargeType']),
  160. 'onceCard': int(float(setConf['once_card'])*100),
  161. 'cardTime': int(setConf['time']),
  162. 'cardElec': int(setConf['elec']),
  163. 'config_list': setConf['config_list'],
  164. })
  165. devObj.save()
  166. setConf.pop('chargeType')
  167. setConf.pop('once_card')
  168. setConf.pop('time')
  169. setConf.pop('elec')
  170. setConf.pop('config_list')
  171. setConf.update({
  172. 'charge_full_time_threshold':int(setConf['charge_full_timeout']),
  173. 'no_load_time_threshold':int(setConf['no_load_timeout'])
  174. })
  175. setConf.pop('charge_full_timeout')
  176. setConf.pop('no_load_timeout')
  177. devInfo = self.send_request('cmd/set-node-config', setConf,3)
  178. if devInfo['data']['code'] != 0:
  179. raise ServiceException({'result': 2, 'description': u'设备服务器返回错误,建议您重试,或者联系客服'})
  180. return devInfo
  181. def get_port_status_from_dev(self):
  182. # 先到设备上,把所有子节点的信息取出来,记录到主节点的缓存
  183. devInfo = self.send_request('cmd/get-status', {'node_index': self.node_index})
  184. if devInfo['data']['code'] != 0:
  185. raise ServiceException({'result': 2, 'description': u'设备服务器返回错误,建议您重试,或者联系客服'})
  186. allPorts, usedPorts = 0, 0
  187. result = {}
  188. for portInfo in devInfo['data']['data']['port_list']:
  189. portId = str(portInfo['index'])
  190. portDict = {
  191. 'status': self.__translate_status_from_str(str(portInfo['charge_status'])),
  192. 'watt': portInfo['power'],
  193. 'ampr': portInfo['current'] * 0.001,
  194. 'voltage': portInfo['voltage'],
  195. 'elec': portInfo['energy_consumed'] * 0.001,
  196. 'usedTime': portInfo['time_consumed'],
  197. 'duration': portInfo['time_consumed']
  198. }
  199. sequanceNo = portInfo['transaction_id']
  200. if sequanceNo:
  201. try:
  202. rcd = ConsumeRecord.objects.get(sequanceNo=sequanceNo)
  203. portDict['openId'] = rcd['openId']
  204. portDict['coins'] = float(str(rcd['coin'])) # 都用coins
  205. portDict['money'] = float(str(rcd['money']))
  206. portDict['sequanceNo'] = sequanceNo
  207. if u'虚拟卡' in rcd.remarks:
  208. portDict['consumeType'] = 'mobile_vcard'
  209. elif u'刷卡' in rcd.remarks:
  210. portDict['consumeType'] = 'card'
  211. portDict['cardNo'] = rcd.servicedInfo.get('cardNo')
  212. else:
  213. portDict['consumeType'] = 'mobile'
  214. portDict['billingType'] = 'power'
  215. if rcd.servicedInfo['billingType'] == 'time':
  216. portDict['billingType'] = 'time'
  217. portDict['needTime'] = rcd.servicedInfo['needTime']
  218. portDict['leftTime'] = rcd.servicedInfo['needTime'] - portInfo['time_consumed'] or 0
  219. if rcd.servicedInfo['billingType'] == 'elec':
  220. portDict['billingType'] = 'elec'
  221. portDict['needElec'] = rcd.servicedInfo['needElec']
  222. user = MyUser.objects(openId=portDict['openId']).first()
  223. if user:
  224. portDict['nickName'] = user.nickname
  225. except Exception, e: # IC卡,如果没有绑定,不会有consumeRcd,应该直接从订单中拿数据
  226. pass
  227. result[portId] = portDict
  228. if portInfo['charge_status'] == 1:
  229. usedPorts += 1
  230. allPorts += 1
  231. result.update({'usedPorts': usedPorts, 'allPorts': allPorts, 'usePorts': allPorts - usedPorts})
  232. Device.update_dev_control_cache(self._device['devNo'], result)
  233. return result
  234. def get_port_info(self, line):
  235. line = self.get_port_from_ab(line)
  236. portCache = Device.get_dev_control_cache(self.device.devNo)
  237. return portCache.get(str(line), {})
  238. def __translate_status_from_str(self, status):
  239. dictConf = {
  240. '0': Const.DEV_WORK_STATUS_IDLE,
  241. '1': Const.DEV_WORK_STATUS_WORKING,
  242. '2': Const.DEV_WORK_STATUS_IDLE,
  243. '3': Const.DEV_WORK_STATUS_IDLE,
  244. }
  245. return dictConf.get(status, Const.DEV_WORK_STATUS_FAULT)
  246. def get_port_status(self, force=False):
  247. if force:
  248. self.get_port_status_from_dev()
  249. portCache = Device.get_dev_control_cache(self._device['devNo'])
  250. result = {}
  251. for ii in range(5):
  252. if str(ii) in portCache:
  253. if ii == 0:
  254. result['A'] = portCache[str(ii)]
  255. elif ii == 1:
  256. result['B'] = portCache[str(ii)]
  257. elif ii == 2:
  258. result['C'] = portCache[str(ii)]
  259. return result
  260. def lock_unlock_port(self, port, lock=True):
  261. port = self.get_port_from_ab(port)
  262. portInfo = self.get_port_info(port)
  263. if portInfo['status'] == Const.DEV_WORK_STATUS_WORKING:
  264. raise ServiceException({'result': 2, 'description': u'当前端口正在使用,请您先关闭掉后,再操作'})
  265. if lock:
  266. Device.update_dev_control_cache(self._device['devNo'],
  267. {str(port): {'status': Const.DEV_WORK_STATUS_FORBIDDEN}})
  268. else:
  269. Device.update_dev_control_cache(self._device['devNo'], {str(port): {'status': Const.DEV_WORK_STATUS_IDLE}})
  270. # 停止该端口下的所有任务
  271. def stop_charging_port(self, port):
  272. port = self.get_port_from_ab(port)
  273. portInfo = self.get_port_info(port)
  274. if portInfo.get('billingType', None) == 'power':
  275. devInfo = self.send_request('cmd/write-node-with-power',
  276. {'node_index': self.node_index, 'port_index': port, 'switch_state': 0,
  277. 'power': {'money': 0,
  278. 'config_list': [{'power': 200, 'price': 100, 'time': 240}]}})
  279. else:
  280. devInfo = self.send_request('cmd/write-node',
  281. {'node_index': self.node_index, 'port_index': port, 'switch_state': 0,
  282. 'charge_mode': 0, 'charge_time': 0, 'charge_energy': 0})
  283. if devInfo['data']['code'] != 0:
  284. raise ServiceException({'result': 2, 'description': u'设备服务器返回错误,建议您重试,或者联系客服'})
  285. if devInfo['rst'] == 0:
  286. Device.update_dev_control_cache(self._device['devNo'], {str(port): {'status': Const.DEV_WORK_STATUS_IDLE}})
  287. return True if devInfo['rst'] == 0 else False
  288. def add_to_gateway(self, gatewayDevNo):
  289. self.add_node(self._device['devNo'])
  290. def remove_from_gateway(self, gatewayDevNo):
  291. self.remove_node(self._device['devNo'])
  292. # 如果
  293. # 柏来有两条刷卡事件,一个是查询余额,对应的是余额播报回复;一个是开始充电事件,对应的是启动设备充电
  294. def response_card_start(self, portIndex, orderNo):
  295. jsonParas = {'node_index': self.node_index, 'port_index': portIndex, 'transaction_id': orderNo,
  296. 'switch_state': 1, 'charge_type': self.billingType}
  297. if self.billingType == 0: # 按电量
  298. jsonParas.update({'charge_type': 2})
  299. jsonParas.update({'charge_energy': self.onceCard * 0.01 * self.cardElec * 1000})
  300. elif self.billingType == 1: # 按时间
  301. jsonParas.update({'charge_type': 1})
  302. jsonParas.update({'charge_time': self.onceCard * 0.01 * self.cardTime})
  303. else: # 按功率
  304. jsonParas.update(
  305. {'charge_type': 3, 'charge_power': {'money': self.onceCard, 'config_list': self.config_list}})
  306. devInfo = self.send_request('cmd/control-nfc-charge', jsonParas)
  307. if devInfo['data']['code'] != 0:
  308. raise ServiceException({'result': 2, 'description': u'设备服务器返回错误,建议您重试,或者联系客服'})
  309. return devInfo
  310. def active_deactive_port(self, port, active):
  311. port = self.get_port_from_ab(port)
  312. if active:
  313. raise ServiceException({'result': 2, 'description': u'该设备不支持直接打开端口'})
  314. return self.stop_charging_port(port)
  315. def set_device_function_param(self, request, lastSetConf):
  316. newConf = copy.deepcopy(request.POST)
  317. newConf.pop('logicalCode', None)
  318. self.set_dev_setting(newConf)
  319. def response_card_balance(self, cardNo, nodeIndex, portIndex, balance):
  320. devInfo = self.send_request('cmd/write-card-query-response',
  321. {
  322. 'node_index': nodeIndex,
  323. 'port_index': portIndex,
  324. 'card_no': cardNo,
  325. 'balance': int(float(balance * 100)),
  326. 'timeout': 10
  327. })
  328. if devInfo['data']['code'] != 0:
  329. raise ServiceException({'result': 2, 'description': u'设备服务器返回错误,建议您重试,或者联系客服'})
  330. return devInfo
  331. def get_node(self, node):
  332. nodeInfo = {
  333. 'signal':node.signal,
  334. 'rssi':node.get('rssi',39),
  335. 'online':node.online,
  336. }
  337. if 'suid' in node:
  338. nodeInfo.update({
  339. 'devNo':node['suid'],
  340. 'suid':node['suid'],
  341. })
  342. if 'devNo' in node:
  343. nodeInfo.update({
  344. 'devNo':node['devNo'],
  345. 'suid':node['devNo'],
  346. })
  347. return nodeInfo
  348. def get_dev_online_status(self):
  349. devInfo = self.send_request('device/gateway/detail', {})
  350. return devInfo['data']['plug_gateway']['is_online']
  351. def update_dev_info(self):
  352. devInfo = self.send_request('device/gateway/update',
  353. {'province': '', 'city': '', 'county': '',
  354. 'address': '', 'longitude': 0.0, 'latitude': 0.0})
  355. if devInfo['data']['code'] != 0:
  356. raise ServiceException({'result': 2, 'description': u'设备服务器返回错误,建议您重试,或者联系客服'})
  357. def add_node(self, nodeDevNo):
  358. # 首先检查子节点是否已经注册了
  359. devObj = Device.objects.filter(devNo = nodeDevNo).first()
  360. if devObj is None:
  361. raise ServiceException({'result': 2, 'description': u'请先扫码注册子设备插座,然后再加入网关'})
  362. # 检查是否绑定在其他主节点,如果绑定了其他主节点,必须提示
  363. if devObj.gatewayNode and devObj.gatewayNode != self._device['devNo']:
  364. raise ServiceException({'result': 2, 'description': u'该子节点已经绑定在其他主节点下,请先解绑后,再重新添加'})
  365. nodeList = self.get_node_list()
  366. # 首先检查是否已经添加进来了
  367. exist = devObj.gatewayNode == self._device['devNo']
  368. sendList = [{'node_index': node['node_index'], 'plug_id': node['suid']} for node in nodeList]
  369. if exist: # 如果已经存在了,直接下发就Ok
  370. pass
  371. else:
  372. nodeIndexList = [node['node_index'] for node in nodeList]
  373. ii = 1
  374. for ii in range(1, 50):
  375. if ii not in nodeIndexList:
  376. break
  377. else:
  378. continue
  379. sendList.append({'node_index': ii, 'plug_id': nodeDevNo})
  380. devInfo = self.send_request('cmd/build-network',
  381. {'channel': random.randint(1, 8),
  382. 'plug_list': sendList, 'timeout': 15})
  383. if devInfo['data']['code'] != 0:
  384. raise ServiceException({'result': 2, 'description': u'设备服务器返回错误,建议您重试,或者联系客服'})
  385. # 主节点内容更新
  386. devObj = Device.objects.get(devNo = self._device['devNo'])
  387. for sendInfo in sendList:
  388. devObj.nodeDict.update({str(sendInfo['node_index']):sendInfo['plug_id']})
  389. devObj.save()
  390. # 子节点的也要更新
  391. devObj = Device.objects.get(devNo = nodeDevNo)
  392. devObj.gatewayNode = self._device['devNo']
  393. devObj.save()
  394. def remove_node(self, nodeDevNo):
  395. nodeList = self.get_node_list()
  396. sendList = []
  397. index = -1
  398. nodeDict = {}
  399. for node in nodeList:
  400. if node['suid'] != nodeDevNo:
  401. sendList.append({'node_index': node['node_index'], 'plug_id': node['suid']})
  402. nodeDict[str(node['node_index'])] = node['suid']
  403. else:
  404. index = node['node_index']
  405. if index == -1:
  406. return
  407. devInfo = self.send_request('cmd/build-network',
  408. {'channel': random.randint(1, 8),
  409. 'plug_list': sendList, 'timeout': 5})
  410. if devInfo['data']['code'] != 0:
  411. raise ServiceException({'result': 2, 'description': u'设备服务器返回错误,建议您重试,或者联系客服'})
  412. # 主节点更新
  413. try:
  414. devObj = Device.objects.get(devNo = self._device['devNo'])
  415. devObj.nodeDict = nodeDict
  416. devObj.save()
  417. except Exception, e:
  418. pass
  419. # 子节点更新
  420. try:
  421. devObj = Device.objects.get(devNo = nodeDevNo)
  422. devObj.gatewayNode = ''
  423. devObj.save()
  424. except Exception, e:
  425. return
  426. # 如果是一体板的,需要把端口删除掉
  427. if nodeDevNo == self._device['devNo']:
  428. Device.invalid_device_control_cache(nodeDevNo)
  429. def get_signal(self):
  430. result = {'rst':0}
  431. devInfo = Device.get_dev(self._device['devNo'])
  432. if devInfo['online']:
  433. result.update({'signal':devInfo['signal']})
  434. return result
  435. else:
  436. return {'signal':0,'rst':0}
  437. def get_node_list(self):
  438. result = []
  439. for index,nodeDevNo in self.nodeDict.items():
  440. nodeDevInfo = Device.get_dev(nodeDevNo)
  441. nodeInfo = self.get_node(nodeDevInfo)
  442. nodeInfo.update({'node_index':int(index)})
  443. result.append(nodeInfo)
  444. return result
  445. def get_node_devNo(self, nodeIndex):
  446. return self.nodeDict.get(str(nodeIndex))