yongxin.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import binascii
  4. import datetime
  5. import logging
  6. import time
  7. from apilib.monetary import VirtualCoin
  8. from apps.web.constant import DeviceCmdCode, Const
  9. from apps.web.core.adapter.base import SmartBox, fill_2_hexByte
  10. from apps.web.core.device_define.yongxin import DefaultParams, ChargeMode
  11. from apps.web.core.exceptions import ServiceException
  12. from apps.web.core.networking import MessageSender
  13. from apps.web.device.models import Device
  14. from apps.web.user.models import MyUser, ConsumeRecord
  15. logger = logging.getLogger(__name__)
  16. class ChargingYongxinBox(SmartBox):
  17. def __init__(self, device):
  18. super(ChargingYongxinBox, self).__init__(device)
  19. def _send_data(self, funCode, data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, timeout=30):
  20. result = MessageSender.send(
  21. device=self.device,
  22. cmd=cmd,
  23. payload={"IMEI": self.device.devNo, "funCode": funCode, "data": data},
  24. timeout=timeout
  25. )
  26. if result.get("rst") == 0:
  27. return result
  28. if result.get("rst") == -1:
  29. raise ServiceException({'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  30. if result.get('rst') == 1:
  31. raise ServiceException({'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
  32. raise ServiceException({'result': 2, 'description': u'通讯错误'})
  33. @staticmethod
  34. def _to_ascii(s):
  35. return binascii.hexlify(s).upper()
  36. @staticmethod
  37. def _to_str(h):
  38. """
  39. 将 16进制ascii 转换成 字符串
  40. :param h:
  41. :return:
  42. """
  43. return binascii.unhexlify(h).lower()
  44. @staticmethod
  45. def _suit_package(package):
  46. """
  47. 返回充电模式以及充电参数
  48. """
  49. unit = package["unit"]
  50. coins = VirtualCoin(package['coins'])
  51. userAcquire = package["time"]
  52. nowTime = int(time.time())
  53. # 暂缺一种充满自停模式
  54. if int(userAcquire) == 999 and unit == u"分钟":
  55. mode = ChargeMode.AUTO_STOP
  56. finishedTime = nowTime + 12 * 60 * 60
  57. chargeParam = 0xFFFF
  58. return mode, chargeParam, finishedTime
  59. if unit == u"天":
  60. mode = ChargeMode.TIME_QUOTA
  61. chargeParam = userAcquire * 24 * 60
  62. finishedTime = nowTime + chargeParam * 60
  63. return mode, chargeParam, finishedTime
  64. elif unit == u"小时":
  65. mode = ChargeMode.TIME_QUOTA
  66. chargeParam = userAcquire * 60
  67. finishedTime = nowTime + chargeParam * 60
  68. return mode, chargeParam, finishedTime
  69. elif unit == u"分钟":
  70. mode = ChargeMode.TIME_QUOTA
  71. chargeParam = userAcquire
  72. finishedTime = nowTime + chargeParam * 60
  73. return mode, chargeParam, finishedTime
  74. elif unit == u"度":
  75. mode = ChargeMode.ELEC_QUOTA
  76. chargeParam = userAcquire
  77. finishedTime = nowTime + 12 * 60 * 60
  78. return mode, chargeParam, finishedTime
  79. elif unit == u"次":
  80. mode = ChargeMode.COIN_QUOTA
  81. chargeParam = int((coins * 100).amount)
  82. finishedTime = nowTime + 12 * 60 * 60
  83. return mode, chargeParam, finishedTime
  84. else:
  85. raise ServiceException({"result": 2, "description": u"错误的套餐配置,请联系经销商"})
  86. @property
  87. def isHaveStopEvent(self):
  88. return True
  89. def _response_login(self, devType):
  90. """
  91. 回复设备的登录
  92. :return:
  93. """
  94. # 生成时间戳
  95. loginTime = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
  96. # 生成桩编号
  97. loginLogicalCode = "01{:0>10}".format(self.device.logicalCode.replace("G", "")) if self.device.logicalCode.startswith("G") else "{:0>12}".format(self.device.logicalCode)
  98. # 生成 url 的连接
  99. loginUrl = "{:0<128}".format(self._to_ascii(DefaultParams.DEFAULT_LOGIN_URL_FORMAT))
  100. otherConf = self.device.get("otherConf", dict())
  101. # 获取充电口数量 设备上传指令的时候 会上传当前桩的类型 如果没有设置的,就使用桩上传的
  102. portNum = otherConf.get("portNum", devType % 10)
  103. # 获取输出功率
  104. outputPower = otherConf.get("outputPower", 70)
  105. # 获取电子锁模式
  106. lockMode = otherConf.get('lockMode', 0)
  107. hexLockMode = fill_2_hexByte(hex(int(lockMode)), 2)
  108. hexPortNum = fill_2_hexByte(hex(int(portNum)), 2)
  109. hexOutputPower = fill_2_hexByte(hex(int(outputPower)), 2)
  110. data = loginTime + loginLogicalCode + loginUrl + '10' + hexPortNum + hexOutputPower + hexLockMode + '10'
  111. self._send_data(funCode="12", data=data, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  112. def _response_elec_price(self):
  113. """
  114. 设备请求电量费用的时候回复电量费用
  115. """
  116. priceList = self.device.get("otherConf", dict()).get("priceList")
  117. if not priceList:
  118. priceList = [DefaultParams.DEFAULT_ELEC_PRICE] * DefaultParams.DEFAULT_ELEC_PRICE_INTERVAL_NUM
  119. priceHex = "".join(map(lambda x: "{:0>4X}".format(int(100 * x)), priceList))
  120. data = "{}0000000000000000".format(priceHex)
  121. self._send_data(funCode="40", data=data, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  122. def _response_card_start(self, port, seqNo):
  123. portHex = fill_2_hexByte(hex(int(port)), 2)
  124. data = portHex + seqNo
  125. return self._send_data("18", data, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, timeout=10)
  126. def _response_finished(self, port, seqNo, result):
  127. portHex = fill_2_hexByte(hex(int(port)), 2)
  128. seqNo = seqNo
  129. resultHex = fill_2_hexByte(hex(int(result)), 2)
  130. data = portHex + seqNo + resultHex
  131. self._send_data("1C", data, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, timeout=5)
  132. def _control_device(self, fault):
  133. data = "0001" if fault else "0000"
  134. result = self._send_data(funCode="20", data=data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC)
  135. if result["data"][20:22] != "00":
  136. raise ServiceException({"result": 2, "description": u"重启失败"})
  137. def _start_remote(self, port, seqNo, mode, chargeParam, balance):
  138. """
  139. 远程启动设备
  140. """
  141. portHex = fill_2_hexByte(hex(int(port)), 2)
  142. seqNoHex = seqNo
  143. modeHex = fill_2_hexByte(hex(int(mode)), 2)
  144. chargeParamHex = fill_2_hexByte(hex(int(chargeParam)), 4)
  145. balanceHex = fill_2_hexByte(hex(int(balance*100)), 8)
  146. data = portHex + seqNoHex + modeHex + chargeParamHex + balanceHex + '{:0>16}'.format(0)
  147. result = self._send_data("16", data=data)
  148. code = result["data"][54:56]
  149. if code == "00":
  150. return result
  151. if code == '01':
  152. raise ServiceException({'result': 2, 'description': u'您没有插充电枪,请先插好充电枪,然后再操作哦!'})
  153. if code == '02':
  154. raise ServiceException({'result': 2, 'description': u'您使用的充电枪已经在充电了,请您换其他空闲的充电枪吧!'})
  155. if code == '03':
  156. raise ServiceException({'result': 2, 'description': u'设备故障,暂时无法使用,您本次操作不会扣费,您可以试试其他可用的设备。'})
  157. raise ServiceException({'result': 2, 'description': u'设备返回一个未知的错误,本次操作不会扣费,请您重试看是否能够解决'})
  158. def _stop_remote(self, port, seqNo):
  159. """
  160. 远程停止
  161. """
  162. portHex = fill_2_hexByte(hex(int(port)), 2)
  163. seqNoHex = seqNo
  164. data = portHex + seqNoHex
  165. result = self._send_data("1A", data)
  166. if result['data'][54:56] != "00":
  167. raise ServiceException({'result': 2, 'description': u'该充电枪已经空闲,没有充电'})
  168. return result
  169. def get_elec_price_from_device(self):
  170. """
  171. 从设备上获取电价
  172. :return:
  173. """
  174. data = "{:016X}".format(0)
  175. result = self._send_data("42", data)
  176. return result.get("data", "")[20:212]
  177. def compare_elec_price(self, devElecPrice):
  178. # 首先看版本 低版本直接是正常的
  179. if self.device.driverVersion != "v2.0.0":
  180. return True
  181. localPriceList = self.device.get("otherConf", dict()).get("priceList")
  182. if not localPriceList:
  183. localPriceList = [DefaultParams.DEFAULT_ELEC_PRICE] * DefaultParams.DEFAULT_ELEC_PRICE_INTERVAL_NUM
  184. localPrice = "".join(map(lambda x: "{:0>4X}".format(int(100 * x)), localPriceList))
  185. # 比较的是原始的报文
  186. return devElecPrice == localPrice
  187. def start_device(self, package, openId, attachParas):
  188. devElecPrice = self.get_elec_price_from_device()
  189. if not self.compare_elec_price(devElecPrice):
  190. raise ServiceException({"result": "2", "description": u"启动错误(10008)"})
  191. chargeIndex = attachParas.get("chargeIndex")
  192. if not chargeIndex:
  193. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'})
  194. if not openId:
  195. raise ServiceException({"result": 2, "description": u"本设备暂不支持上分"})
  196. mode, chargeParam, finishedTime = self._suit_package(package)
  197. # 余额统统变成 该用户的此地址下的所有的金额
  198. order = ConsumeRecord.objects.get(orderNo=attachParas["orderNo"]) # type: ConsumeRecord
  199. user = MyUser.objects.get(openId=openId, groupId=self.device.groupId) # type: MyUser
  200. userBalance = user.balance
  201. # 留下订单的钱给service扣除
  202. # TODO 用心的是将 用户的全部余额下发 然后用完后退还 最好单独起个service 现在没有 所以先简陋处理 临时发布前的补丁
  203. balance = float(userBalance.amount)
  204. # seqNo 之前的做法是放到模块侧生成 暂时不明白为什么
  205. seqNo = "{:0>32}".format(0)
  206. result = self._start_remote(chargeIndex, seqNo, mode, chargeParam, balance)
  207. user.pay(userBalance-order.coin)
  208. result["finishedTime"] = finishedTime
  209. # 后 4位 的序列号 可能是16进制的 之前直接永序列号当作订单 所以需要将16进制转换为10 进制 现在不需要转换了
  210. result["sequanceNo"] = result['data'][22:54]
  211. portDict = {
  212. 'startTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  213. 'status': Const.DEV_WORK_STATUS_WORKING,
  214. 'finishedTime': finishedTime,
  215. 'coins': float(balance),
  216. 'isStart': True,
  217. 'sequanceNo': result["sequanceNo"],
  218. 'orderNo': attachParas['orderNo'],
  219. 'useType': 'mobile',
  220. 'openId': openId,
  221. 'refunded': False,
  222. 'chargeMode': mode,
  223. 'need': chargeParam,
  224. }
  225. Device.update_dev_control_cache(self._device['devNo'], {str(chargeIndex): portDict})
  226. return result
  227. def stop(self, port=None):
  228. if not port:
  229. raise ServiceException({"result": 2, "description": u"请选择需要结束的充电端口"})
  230. portCache = Device.get_port_control_cache(self.device.devNo, str(port))
  231. seqNo = portCache.get("sequanceNo")
  232. if not seqNo:
  233. raise ServiceException({"result": 2, "description": u"当前端口无工作"})
  234. return self._stop_remote(port, seqNo)
  235. def analyze_event_data(self, data):
  236. cmdCode = data[2:4]
  237. if cmdCode == '11': # 登录请求
  238. return {'cmdCode': cmdCode, "devType": int(data[20:22], 16)}
  239. if cmdCode == "41": # 这个是请求电价数据
  240. return {"cmdCode": cmdCode}
  241. if cmdCode == "19": # 充电数据实时上传
  242. port = int(data[20:22], 16)
  243. outputVoltage = int(data[22:26], 16) / 10.0
  244. outputElec = int(data[26:30], 16) / 10.0
  245. elec = int(data[30:34], 16) / 100.0
  246. spendMoney = int(data[34:38], 16) / 100.0
  247. duration = int(data[40:44], 16)
  248. return {
  249. 'cmdCode': cmdCode,
  250. 'port': port,
  251. 'outputVoltage': outputVoltage,
  252. 'outputElec': outputElec,
  253. 'elec': elec,
  254. 'spendMoney': spendMoney,
  255. 'duration': duration,
  256. }
  257. if cmdCode == '17': # 刷卡启动的 也就是本地启动
  258. port = int(data[20:22], 16)
  259. sequanceNo = data[22:54]
  260. cardNo = data[54:70]
  261. balance = int(data[70:78], 16) / 100.0
  262. return {
  263. 'cmdCode': cmdCode,
  264. 'port': port,
  265. 'sequanceNo': sequanceNo,
  266. 'cardNo': cardNo,
  267. # 'isStart': True,
  268. # 'useType': 'card',
  269. 'balance': balance
  270. }
  271. if cmdCode == '1D': # 结算账单上传
  272. port = int(data[20:22], 16)
  273. sequanceNo = data[22:54]
  274. elec = int(data[54:58], 16) / 100.0
  275. spendMoney = int(data[58:62], 16) / 100.0
  276. duration = int(data[62:66], 16)
  277. balance = int(data[98:106], 16) / 100.0
  278. reason = int(data[106: 108], 16)
  279. return {
  280. 'port': port,
  281. 'sequanceNo': sequanceNo,
  282. 'elec': elec,
  283. 'spendMoney': spendMoney,
  284. 'duration': duration,
  285. 'cmdCode': '1D',
  286. 'balance': balance,
  287. 'reason': reason
  288. }
  289. def set_device_function_param(self, request, lastSetConf):
  290. if request.POST.has_key('lockMode') and request.POST.has_key('outputPower'):
  291. lockMode = int(request.POST.get('lockMode'))
  292. portNum = int(request.POST.get('portNum'))
  293. outputPower = 35 if int(request.POST.get('outputPower')) == '1' else 70
  294. dev = Device.objects.get(devNo = self._device['devNo'])
  295. dev.otherConf['lockMode'] = lockMode
  296. dev.otherConf['portNum'] = portNum
  297. dev.otherConf['outputPower'] = outputPower
  298. dev.save()
  299. def set_device_function(self, request, lastSetConf):
  300. lock = request.POST.get("lock")
  301. if lock:
  302. return self._control_device(True)
  303. unlock = request.POST.get("unlock")
  304. if unlock:
  305. return self._control_device(False)
  306. def get_dev_setting(self):
  307. resultDict = {}
  308. dev = Device.objects.get(devNo = self._device['devNo'])
  309. resultDict['lockMode'] = dev.otherConf.get('lockMode', 0)
  310. resultDict['portNum'] = dev.otherConf.get('portNum', 1)
  311. resultDict['outputPower'] = 1 if dev.otherConf.get('outputPower', 70) == 35 else 2
  312. return resultDict
  313. def get_port_info(self, line):
  314. ctrInfo = Device.get_dev_control_cache(self._device['devNo'])
  315. value = ctrInfo.get(line, {})
  316. value.update({'port': line})
  317. if 'sequanceNo' in value:
  318. sequanceNo = value['sequanceNo']
  319. consumeRcd = ConsumeRecord.objects.get(sequanceNo = sequanceNo)
  320. elif 'orderNo' in value:
  321. orderNo = value['orderNo']
  322. consumeRcd = ConsumeRecord.objects.get(orderNo = orderNo)
  323. else:
  324. consumeRcd = None
  325. if consumeRcd:
  326. order = {'coin': float(consumeRcd.coin)}
  327. value.update({'order': order})
  328. return value
  329. def get_port_status(self, force=False):
  330. if force:
  331. return self.get_port_status_from_dev()
  332. statusDict = {}
  333. self.get_port_status_from_dev()
  334. ctrInfo = Device.get_dev_control_cache(self._device['devNo'])
  335. allPorts = ctrInfo.get('allPorts', 2)
  336. for ii in range(allPorts):
  337. tempDict = ctrInfo.get(str(ii + 1), {})
  338. if tempDict.has_key('status'):
  339. statusDict[str(ii + 1)] = {'status': tempDict.get('status')}
  340. elif tempDict.has_key('isStart'):
  341. if tempDict['isStart']:
  342. statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_WORKING}
  343. else:
  344. statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE}
  345. else:
  346. statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE}
  347. allPorts, usedPorts, usePorts = self.get_port_static_info(statusDict)
  348. Device.update_dev_control_cache(self._device['devNo'],
  349. {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts})
  350. return statusDict
  351. # 这个指令 好像是驱动特供的
  352. def get_port_status_from_dev(self):
  353. devInfo = MessageSender.send(self.device, DeviceCmdCode.GET_DEVINFO, {'IMEI': self._device['devNo']})
  354. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  355. if devInfo['rst'] == -1:
  356. raise ServiceException(
  357. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  358. elif devInfo['rst'] == 1:
  359. raise ServiceException(
  360. {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
  361. devObj = Device.objects.get(devNo = self._device['devNo'])
  362. chargerType = devInfo.get('chargerType', None)
  363. if chargerType is not None:
  364. devObj['otherConf']['chargerType'] = int(chargerType)
  365. devObj.save()
  366. else:
  367. chargerType = devObj['otherConf'].get('chargerType', None)
  368. if chargerType is None:
  369. raise ServiceException({'result': 2, 'description': u'请您重启下充电桩(10001)'})
  370. if "port1Status" not in devInfo:
  371. raise ServiceException({'result': 2, 'description': u'请您重启下充电桩(10002)'})
  372. result = {}
  373. usedPorts, usePorts = 0, 0
  374. if int(chargerType) in [129, 145]:
  375. portNum = 1
  376. port1Status = devInfo['port1Status']
  377. if port1Status == 0:
  378. result['1'] = {'status': Const.DEV_WORK_STATUS_IDLE}
  379. usePorts += 1
  380. elif port1Status == 1:
  381. result['1'] = {'status': Const.DEV_WORK_STATUS_IDLE}
  382. usePorts += 1
  383. elif port1Status == 2:
  384. result['1'] = {'status': Const.DEV_WORK_STATUS_WORKING}
  385. usedPorts += 1
  386. else:
  387. result['1'] = {'status': Const.DEV_WORK_STATUS_FINISHED}
  388. usedPorts += 1
  389. else:
  390. portNum = 2
  391. port1Status = devInfo['port1Status']
  392. if port1Status == 0:
  393. result['1'] = {'status': Const.DEV_WORK_STATUS_IDLE}
  394. usePorts += 1
  395. elif port1Status == 1:
  396. result['1'] = {'status': Const.DEV_WORK_STATUS_CONNECTED}
  397. usePorts += 1
  398. else:
  399. result['1'] = {'status': Const.DEV_WORK_STATUS_WORKING}
  400. usedPorts += 1
  401. port2Status = devInfo['port2Status']
  402. if port2Status == 0:
  403. result['2'] = {'status': Const.DEV_WORK_STATUS_IDLE}
  404. usePorts += 1
  405. elif port2Status == 1:
  406. result['2'] = {'status': Const.DEV_WORK_STATUS_CONNECTED}
  407. usePorts += 1
  408. else:
  409. result['2'] = {'status': Const.DEV_WORK_STATUS_WORKING}
  410. usedPorts += 1
  411. Device.update_dev_control_cache(self._device['devNo'],
  412. {'allPorts': portNum, 'usedPorts': usedPorts, 'usePorts': usePorts})
  413. ctrInfo = Device.get_dev_control_cache(self._device['devNo'])
  414. for strPort, info in result.items():
  415. if ctrInfo.has_key(strPort):
  416. ctrInfo[strPort].update({'status': info['status']})
  417. else:
  418. ctrInfo[strPort] = info
  419. Device.update_dev_control_cache(self._device['devNo'], ctrInfo)
  420. return result
  421. def active_deactive_port(self, port, active):
  422. if not active:
  423. self.stop_charging_port(port)
  424. devInfo = Device.get_dev_control_cache(self._device['devNo'])
  425. portCtrInfo = devInfo.get(str(port), {})
  426. portCtrInfo.update({'isStart': False, 'status': Const.DEV_WORK_STATUS_IDLE,
  427. 'endTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)})
  428. newValue = {str(port): portCtrInfo}
  429. Device.update_dev_control_cache(self._device['devNo'], newValue)
  430. else:
  431. raise ServiceException({'result': 2, 'description': u'此设备不支持直接打开端口'})