bolai_node.py 21 KB

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