jndzCar.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import binascii
  4. import datetime
  5. import logging
  6. import struct
  7. import time
  8. from apps.web.agent.models import Agent
  9. from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT
  10. from apps.web.core.adapter.base import SmartBox, fill_2_hexByte
  11. from apps.web.core.exceptions import ServiceException
  12. from apps.web.core.networking import MessageSender
  13. from apps.web.dealer.models import Dealer
  14. from apps.web.device.models import Device, Group, DeviceType
  15. from apps.web.device.timescale import FluentedEngine
  16. from apps.web.utils import concat_user_login_entry_url
  17. logger = logging.getLogger(__name__)
  18. class ChargingJNCar(SmartBox):
  19. PORT_STATUS_MAP = {
  20. "01": Const.DEV_WORK_STATUS_IDLE,
  21. "02": Const.DEV_WORK_STATUS_WORKING,
  22. "03": Const.DEV_WORK_STATUS_FORBIDDEN,
  23. # 劲能汽车桩 故障和连接状态没有区分开 咨询过主板 只能靠人工判断 插枪了如果还是故障 就是真的故障
  24. "04": Const.DEV_WORK_STATUS_CONNECTED
  25. }
  26. ERROR_MAP = {
  27. "0001": u"继电器粘连",
  28. "0002": u"其他错误"
  29. }
  30. FINISH_MAP = {
  31. '00': u'购买的充电时间或电量用完了。',
  32. '01': u'可能是插头被拔掉,或者电瓶已经充满。系统判断为异常断电,由于电瓶车充电器种类繁多,可能存在误差。如有问题,请您及时联系商家协助解决问题并恢复充电。',
  33. '02': u'恭喜您!电池已经充满电!',
  34. '03': u'警告!您的电池超功率,已经停止充电,为了公共安全,不建议您在该充电桩充电!提醒您,为了安全大功率的电池不要放入楼道、室内等位置充电哦',
  35. '04': u'远程断电。',
  36. '05': u'刷卡断电',
  37. '0B': u'设备或端口出现问题,为了安全起见,被迫停止工作。建议您根据已经充电的时间评估是否需要到现场换到其他端口充电。'
  38. }
  39. def _send_data(self, funCode, data, cmd=None, timeout=MQTT_TIMEOUT.NORMAL):
  40. """
  41. :param funCode:
  42. :param data:
  43. :param cmd:
  44. :param timeout:
  45. :return:
  46. """
  47. if cmd is None:
  48. cmd = DeviceCmdCode.OPERATE_DEV_SYNC
  49. result = MessageSender.send(device = self.device, cmd = cmd, payload = {
  50. "IMEI": self.device.devNo,
  51. "funCode": funCode,
  52. "data": data
  53. }, timeout = timeout)
  54. if "rst" in result and result.get("rst") != 0:
  55. if result.get("rst") == -1:
  56. raise ServiceException({"result": 2, "description": u"网络故障,请重新试试"})
  57. if result.get("rst") == 1:
  58. raise ServiceException({"result": 2, "description": u"充电桩无响应,请稍后再试试"})
  59. return result
  60. @property
  61. def _sessionId(self):
  62. return int(time.time())
  63. @staticmethod
  64. def _to_ascii(s):
  65. """
  66. 将字符串转换成ascii
  67. :param s:
  68. :return:
  69. """
  70. return binascii.hexlify(s)
  71. @staticmethod
  72. def _parse_event_26(data):
  73. """
  74. 解析 26 指令上报过来的数据
  75. 26 指令主要负责上传端口的实时状态 是将所有的端口一次性上传
  76. :return:
  77. """
  78. portNum = int(data[8:10], 16)
  79. portStatusDict = dict()
  80. data = data[10:]
  81. for i in xrange(portNum):
  82. portStr = str(int(data[:2], 16))
  83. portStatusDict[portStr] = {
  84. "status": ChargingJNCar.PORT_STATUS_MAP.get(data[4:6], Const.DEV_WORK_STATUS_IDLE),
  85. "usedTime": int(data[6:10], 16),
  86. "power": int(data[10:14], 16),
  87. "usedElec": int(data[14:18], 16) / 100.0,
  88. "voltage": int(data[18:22], 16) / 10.0,
  89. "a": int(data[22: 26], 16) / 100.0
  90. }
  91. data = data[26: ]
  92. portStatusDict["portNum"] = portNum
  93. return portStatusDict
  94. @staticmethod
  95. def _parse_event_0A(data):
  96. """
  97. 解析故障报警
  98. :param data:
  99. :return:
  100. """
  101. port = data[8:10]
  102. if port == "FFFF":
  103. portStr = 0
  104. else:
  105. portStr = str(int(port, 16))
  106. errorCode = data[10:14]
  107. errorDesc = ChargingJNCar.ERROR_MAP.get(errorCode, "")
  108. return {
  109. "portStr": portStr,
  110. "errorCode": errorCode,
  111. "errorDesc": errorDesc
  112. }
  113. def _start(self, port, money, elec, _type=None, _time=None):
  114. """
  115. 启动设备
  116. :param port:
  117. :param money:
  118. :param elec:
  119. :param _type:
  120. :param _time:
  121. :return:
  122. """
  123. # 目前只支持按电量计费
  124. if _type is None:
  125. chargeTypeHex = "02"
  126. else:
  127. chargeTypeHex = fill_2_hexByte(hex(int(_type)), 2)
  128. if _time is None:
  129. chargeTimeHex = "0000"
  130. else:
  131. chargeTimeHex = fill_2_hexByte(hex(int(_time)), 4)
  132. moneyHex = fill_2_hexByte(hex(int(float(money)*10)), 4)
  133. portHex = fill_2_hexByte(hex(int(port)), 2)
  134. elecHex = fill_2_hexByte(hex(int(float(elec)*100)), 4)
  135. sessionHex = fill_2_hexByte(hex(self._sessionId), 8)
  136. sendData = portHex + moneyHex + chargeTimeHex + elecHex + sessionHex + chargeTypeHex
  137. result = self._send_data("27", sendData, timeout=MQTT_TIMEOUT.START_DEVICE)
  138. data = result.get("data", "")
  139. if data[10:12] == "0B":
  140. raise ServiceException({"result": 0, "description": u"充电站故障,请重新试试"})
  141. elif data[10:12] == "0C":
  142. raise ServiceException({"result": 0, "description": u"该端口已被占用,请换个端口进行充电"})
  143. elif data[10:12] == "02":
  144. raise ServiceException({"result": 0, "description": u"暂不支持此充电模式,请联系经销商解决"})
  145. elif data[10:12] == "01":
  146. return result
  147. else:
  148. raise ServiceException({"result": 2, "description": u"未知充电错误"})
  149. def _stop(self, port):
  150. """
  151. 停止设备使用
  152. _type 表示 停止的订单类型 0x00/所有类型的订单 0x01/通过模块提交的充电信息 0x02/本地的支付订单,如刷卡、投币
  153. :return:
  154. """
  155. portHex = fill_2_hexByte(hex(int(port)), 2)
  156. _type = "00"
  157. self._send_data("0D", portHex+_type)
  158. def _set_device_qr_code(self):
  159. """
  160. 设置设备的二维码
  161. :return:
  162. """
  163. logicalCode = self.device.logicalCode[-6:]
  164. qr_code_url = concat_user_login_entry_url(l = self._device["logicalCode"])
  165. code = fill_2_hexByte(hex(int(logicalCode)), 8)
  166. urlBuf = ChargingJNCar._to_ascii(qr_code_url).upper()
  167. urlBuf = "{:0<140}".format(urlBuf) # 最长70个字节结尾为00
  168. self._send_data("2F", code+urlBuf, DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  169. # d = "000012A8687474703A2F2F646576656C6F702E3574616F3561692E636F6D2F757365724C6F67696E3F6C3D34343737363800000000000000000000000000000000000000000000000000"
  170. # self._send_data("2F", d, DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  171. def _get_device_settings_2B(self):
  172. """
  173. 获取设备的 一些设置
  174. :return:
  175. """
  176. result = self._send_data("2B", "00")
  177. data = result.get("data", "")
  178. cardCst = int(data[8:12], 16) # 单位为角
  179. elecPri = int(data[12:16], 16) # 单位是分
  180. return {
  181. "cardCst": cardCst,
  182. "elecPri": elecPri
  183. }
  184. def _set_device_settings_2A(self, cst, elecFee):
  185. """
  186. 设置设备的一些参数
  187. :return:
  188. """
  189. cstHex = fill_2_hexByte(hex(int(cst)), 4)
  190. elecFeeHex = fill_2_hexByte(hex(int(elecFee)), 4)
  191. return self._send_data("2A", cstHex+elecFeeHex)
  192. def _get_port_charge_status(self, port):
  193. """
  194. 查询当前的充电状态 电流暂时不要
  195. :return:
  196. """
  197. portHex = fill_2_hexByte(hex(int(port)), 2)
  198. result = self._send_data("2E", portHex)
  199. data = result.get("data")
  200. leftTime = data[10:14]
  201. power = data[14:18]
  202. leftElec = data[18:22]
  203. surp = data[22:26]
  204. voltage = data[26:30]
  205. maxTime = data[34:38]
  206. return {
  207. "leftTime": int(leftTime, 16) if leftTime else 0,
  208. "power": int(power, 16) if power else 0,
  209. "leftElec": int(leftElec, 16) / 100.0 if leftElec else 0,
  210. "surp": int(surp, 16) / 10.0 if surp else 0,
  211. "voltage": int(voltage, 16) if voltage else 0,
  212. "maxTime": int(maxTime, 16) if maxTime else 0
  213. }
  214. def _get_device_max_charge_time(self):
  215. """
  216. 获取设备的最长充电时间
  217. :return:
  218. """
  219. result = self._send_data("25", "00")
  220. data = result.get("data", "")
  221. maxChargeTime = int(data[8:12], 16)
  222. return {
  223. "maxChargeTime": maxChargeTime
  224. }
  225. def _set_device_max_charge_time(self, chargeTime):
  226. """
  227. 设置设备的最长充电时间 单位是分钟
  228. :return:
  229. """
  230. chargeTimeHex = fill_2_hexByte(hex(int(chargeTime)), 4)
  231. return self._send_data("24", chargeTimeHex)
  232. def _get_device_version(self):
  233. """
  234. 获取设备的主板信息 版本等等
  235. :return:
  236. """
  237. typeMap = {
  238. "01": u"十路智慧款",
  239. "02": u"双路电轿款",
  240. "04": u"离线充值机",
  241. "05": u"16路智慧款",
  242. "06": u"20路智慧款",
  243. "07": u"单路7kw交流桩"
  244. }
  245. result = self._send_data("23", "00")
  246. data = result.get("data", "")
  247. hdType = data[8:10]
  248. hwVer = data[10:14]
  249. swVer = data[14:18]
  250. idBuf = data[18:42]
  251. return {
  252. "hdType": typeMap.get(hdType, ""),
  253. "hwVer": hwVer,
  254. "swVer": swVer,
  255. "idBuf": idBuf
  256. }
  257. def _get_device_port_status(self):
  258. """
  259. 读取每个设备的状态
  260. :return:
  261. """
  262. result = self._send_data("0F", "00")
  263. data = result.get("data", "")
  264. portNum = int(data[8:10], 16)
  265. portHexData = data[10:-2]
  266. portStatusDict = dict()
  267. for i in range(portNum):
  268. offset = i * 4
  269. portStr = str(int(portHexData[0+offset: 2+offset], 16))
  270. portStatus = ChargingJNCar.PORT_STATUS_MAP.get(portHexData[2+offset: 4+offset])
  271. portStatusDict[portStr] = {"status": portStatus}
  272. return portStatusDict
  273. def _lock_port(self, port):
  274. """
  275. 锁定端口
  276. :param port:
  277. :return:
  278. """
  279. portHex = fill_2_hexByte(hex(int(port)), 2)
  280. statusHex = "00"
  281. self._send_data("0C", portHex+statusHex, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  282. def _unlock_port(self, port):
  283. """
  284. 解锁端口
  285. :param port:
  286. :return:
  287. """
  288. portHex = fill_2_hexByte(hex(int(port)), 2)
  289. statusHex = "01"
  290. self._send_data("0C", portHex + statusHex, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  291. def _get_consume_data_from_device(self):
  292. """
  293. 从 主板 侧获取消费数据
  294. :return:
  295. """
  296. result = self._send_data("07", "00")
  297. data = result.get("data", "")
  298. cardMoney = int(data[8:12], 16) / 10.0
  299. coinMoney = int(data[12:16], 16)
  300. return {
  301. "cardMoney": cardMoney,
  302. "coinMoney": coinMoney
  303. }
  304. def _get_all_port_num(self):
  305. """
  306. 获取端口总数
  307. :return:
  308. """
  309. result = self._send_data("01", "00")
  310. data = result.get("data", "")
  311. portNum = int(data[8:10], 16)
  312. return {
  313. "portNum": portNum
  314. }
  315. def _response_card_balance(self, cardBalance, res):
  316. """
  317. 回复刷卡余额的数据
  318. :param cardBalance:
  319. :param res:
  320. :return:
  321. """
  322. balanceHex = fill_2_hexByte(hex(int(cardBalance) * 10), 4)
  323. self._send_data("10", res+balanceHex, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  324. def _response_finished(self, port, sessionId):
  325. """
  326. 回复主板结束事件
  327. :param port:
  328. :param sessionId:
  329. :return:
  330. """
  331. portHex = fill_2_hexByte(hex(int(port)), 2)
  332. self._send_data("2C", portHex+sessionId, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  333. def lock_unlock_port(self, port, lock=True):
  334. """
  335. 禁用 解禁 端口
  336. :param port:
  337. :param lock:
  338. :return:
  339. """
  340. if lock:
  341. self._lock_port(port)
  342. else:
  343. self._unlock_port(port)
  344. def active_deactive_port(self, port, active):
  345. if not active:
  346. self._stop(port)
  347. else:
  348. raise ServiceException({'result': 2, 'description': u'此设备不支持直接打开端口'})
  349. def stop(self, port=None):
  350. """
  351. 停止设备运行
  352. :param port:
  353. :return:
  354. """
  355. self._stop(port)
  356. def get_port_info(self, port):
  357. """
  358. 获取端口运行的信息 可以从设备端获取 也可以从缓存获取
  359. :param port:
  360. :return:
  361. """
  362. devCache = Device.get_dev_control_cache(self.device.devNo)
  363. return devCache.get(str(port), dict())
  364. def get_port_status_from_dev(self):
  365. """
  366. 获取设备 端口的实时状态
  367. :return:
  368. """
  369. portDict = self._get_device_port_status()
  370. # 更新可用端口数量
  371. allPorts, usedPorts, usePorts = self.get_port_static_info(portDict)
  372. Device.update_dev_control_cache(
  373. self._device["devNo"],
  374. {
  375. "allPorts": allPorts,
  376. "usedPorts": usedPorts,
  377. "usePorts": usePorts
  378. }
  379. )
  380. # 更新端口状态
  381. devCache = Device.get_dev_control_cache(self._device["devNo"])
  382. for port, info in portDict.items():
  383. if port in devCache and isinstance(info, dict):
  384. devCache[port].update({"status": info["status"]})
  385. else:
  386. devCache[port] = info
  387. Device.update_dev_control_cache(self._device["devNo"], devCache)
  388. return portDict
  389. def get_port_status(self, force = False):
  390. """
  391. 获取 设备端口状态 一般是从缓存读取
  392. :param force:
  393. :return:
  394. """
  395. if force:
  396. return self.get_port_status_from_dev()
  397. devCache = Device.get_dev_control_cache(self._device["devNo"])
  398. if "allPorts" not in devCache:
  399. self.get_port_status_from_dev()
  400. devCache = Device.get_dev_control_cache(self._device["devNo"])
  401. allPorts = devCache.get("allPorts")
  402. if allPorts is None:
  403. raise ServiceException({'result': 2, 'description': u'充电端口信息获取失败'})
  404. # 获取显示端口的数量 客户要求可以设置 显示给用户多少个端口
  405. statusDict = dict()
  406. for portNum in xrange(allPorts):
  407. portStr = str(portNum + 1)
  408. tempDict = devCache.get(portStr, {})
  409. if "status" in tempDict:
  410. statusDict[portStr] = {"status": tempDict["status"]}
  411. elif "isStart" in tempDict:
  412. if tempDict["isStart"]:
  413. statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_WORKING}
  414. else:
  415. statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_IDLE}
  416. else:
  417. statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_IDLE}
  418. allPorts, usedPorts, usePorts = self.get_port_static_info(statusDict)
  419. portsDict = {"allPorts": allPorts, "usedPorts": usedPorts, "usePorts": usePorts}
  420. Device.update_dev_control_cache(self._device["devNo"], portsDict)
  421. # 返还的是要显示的端口数量
  422. return statusDict
  423. def check_dev_status(self, attachParas=None):
  424. """
  425. 汽车充电桩使用现金支付的时候,首先先检查一下设备的端口是否正常
  426. :param attachParas:
  427. :return:
  428. """
  429. chargeIndex = attachParas.get("chargeIndex", "")
  430. portStatus = self._get_device_port_status().get(chargeIndex, dict()).get("status")
  431. return portStatus == Const.DEV_WORK_STATUS_IDLE
  432. def test(self, coins):
  433. """
  434. 联网检测
  435. :param coins:
  436. :return:
  437. """
  438. port = "01"
  439. elec = "10"
  440. return self._start(port, coins, elec)
  441. def start_device(self, package, openId, attachParas):
  442. """
  443. 启动设备,目前仅支持按按电量计费
  444. :param package:
  445. :param openId:
  446. :param attachParas:
  447. :return:
  448. """
  449. if attachParas is None:
  450. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路、电池类型信息'})
  451. if "chargeIndex" not in attachParas:
  452. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'})
  453. chargeIndex = attachParas.get("chargeIndex")
  454. needElec = package.get("time")
  455. coins = package.get("coins")
  456. price = package.get("price")
  457. try:
  458. result = self._start(chargeIndex, price, needElec)
  459. except ServiceException as se:
  460. if se.result.get("result") == 0:
  461. # TODO zjl 执行退款事件
  462. se.result.update({"result": 2})
  463. raise ServiceException(se.result)
  464. devCache = Device.get_dev_control_cache(self.device.devNo)
  465. portCache = devCache.get(str(chargeIndex), dict())
  466. nowTimeStamp = int(time.time())
  467. portCache.update({
  468. "isStart": True,
  469. "status": Const.DEV_WORK_STATUS_WORKING,
  470. "openId": openId,
  471. "price": price,
  472. "coins": coins,
  473. "needElec": needElec,
  474. "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  475. "startTimeStamp": nowTimeStamp,
  476. "consumeType": "mobile",
  477. "vCardId": self._vcard_id,
  478. "power": 1 # 为power添加默认值 主要是getCurrentUse 的这个接口有一个判断power==0的时候直接将ServiceProgress结束了
  479. })
  480. if 'linkedRechargeRecordId' in attachParas and attachParas.get('isQuickPay', False):
  481. item = {
  482. 'rechargeRcdId': str(attachParas['linkedRechargeRecordId'])
  483. }
  484. portCache['payInfo'] = [item]
  485. Device.update_dev_control_cache(self.device.devNo, {str(chargeIndex): portCache})
  486. result["finishedTime"] = 24 * 60 * 60 + nowTimeStamp
  487. return result
  488. def check_and_do_card_number_reverse(self, cardNo, cardNoData):
  489. group = Group.get_group(self._device['groupId'])
  490. dealer = Dealer.get_dealer(group['ownerId'])
  491. agent = Agent.objects(id=dealer['agentId']).first()
  492. device = Device.objects(devNo=self._device['devNo']).first()
  493. devType = DeviceType.objects(id=device['devType']['id']).first()
  494. if agent is not None and 'cardNoReverse' in agent.features:
  495. cardData = [cardNoData[_*2:(_ + 1)*2] for _ in range(0, len(cardNoData) / 2)]
  496. cardData.reverse()
  497. cardData = ''.join(cardData)
  498. cardNo = str(int(cardData, 16))
  499. if 'cardNoReverse' in devType.features:
  500. if devType.features['cardNoReverse'] is True:
  501. cardData = [cardNoData[_ * 2:(_ + 1) * 2] for _ in range(0, len(cardNoData) / 2)]
  502. cardData.reverse()
  503. cardData = ''.join(cardData)
  504. cardNo = str(int(cardData, 16))
  505. else:
  506. cardData = [cardNoData[_ * 2:(_ + 1) * 2] for _ in range(0, len(cardNoData) / 2)]
  507. cardData = ''.join(cardData)
  508. cardNo = str(int(cardData, 16))
  509. else:
  510. pass
  511. return cardNo
  512. def analyze_event_data(self, data):
  513. """
  514. 接受事件
  515. :param data:
  516. :return:
  517. """
  518. cmdCode = data[4:6]
  519. # 请求二维码
  520. if cmdCode == "2F":
  521. self._set_device_qr_code()
  522. return
  523. elif cmdCode == "2C":
  524. portStr = str(int(data[8:10], 16))
  525. leftTime = int(data[10:14], 16)
  526. leftElec = int(data[14:18], 16) / 100.0
  527. cardNo = str(int(data[18:26], 16))
  528. cardNo = self.check_and_do_card_number_reverse(cardNo, data[18:26])
  529. cardLeftBalance = int(data[26:30], 16) / 10.0
  530. cardOpe = data[30:32]
  531. cardType = data[32:34]
  532. reasonCode = data[34:36]
  533. sessionId = data[36:44]
  534. return {
  535. "cmdCode": cmdCode,
  536. "portStr": portStr,
  537. "leftTime": leftTime,
  538. "leftElec": leftElec,
  539. "cardNo": cardNo,
  540. "cardLeftBalance": cardLeftBalance,
  541. "cardOpe": cardOpe,
  542. "cardType": cardType,
  543. "reasonCode": reasonCode,
  544. "desc": ChargingJNCar.FINISH_MAP.get(reasonCode),
  545. "sessionId": sessionId
  546. }
  547. elif cmdCode == "2D":
  548. portStr = str(int(data[8:10], 16))
  549. needTime = int(data[10:14], 16)
  550. needElec = int(data[14:18], 16) / 100.0
  551. chargeType = data[18:20]
  552. coinNum = int(data[20:22], 16)
  553. cardNo = str(int(data[22:30], 16))
  554. cardNo = self.check_and_do_card_number_reverse(cardNo, data[22:30])
  555. cardCst = int(data[30:34], 16) / 10.0
  556. cardOpe = data[34:36]
  557. cardType = data[36:38]
  558. sessionId = data[38:46]
  559. return {
  560. "cmdCode": cmdCode,
  561. "portStr": portStr,
  562. "needTime": needTime,
  563. "needElec": needElec,
  564. "chargeType": chargeType,
  565. "coinNum": coinNum,
  566. "cardNo": cardNo,
  567. "cardCst": cardCst,
  568. "cardOpe": cardOpe,
  569. "cardType": cardType,
  570. "sessionId": sessionId
  571. }
  572. elif cmdCode == "10":
  573. cardNo = str(int(data[8:16], 16))
  574. cardNo = self.check_and_do_card_number_reverse(cardNo, data[8:16])
  575. cardCst = int(data[16:18], 16) / 10.0
  576. return {
  577. "cmdCode": cmdCode,
  578. "cardNo": cardNo,
  579. "cardCst": cardCst
  580. }
  581. # 其余的event指令
  582. funcName = "_parse_event_{}".format(cmdCode.upper())
  583. func = getattr(ChargingJNCar, funcName, None)
  584. if not func:
  585. logger.error("<{}> device receive an invalid cmd <{}>".format(self.device.devNo, cmdCode))
  586. return
  587. eventData = func(data)
  588. eventData.update({"cmdCode": cmdCode})
  589. return eventData
  590. def get_dev_setting(self):
  591. """
  592. 汽车充电桩目前就两个设置
  593. :return:
  594. """
  595. return self._get_device_settings_2B()
  596. def set_device_function_param(self, request, lastSetConf):
  597. """
  598. 汽车桩的参数设置
  599. :param request:
  600. :param lastSetConf:
  601. :return:
  602. """
  603. cardCst = request.POST.get("cardCst", None) or lastSetConf.get("cardCst")
  604. elecPri = request.POST.get("elecPri", None) or lastSetConf.get("elecPri")
  605. self._set_device_settings_2A(cardCst, elecPri)
  606. @property
  607. def isHaveStopEvent(self):
  608. return True
  609. def do_heartbeat(self, value, ts):
  610. logger.debug('do heartbeat for {}. value = {}; ts = {}'.format(str(self.device), value, ts))
  611. cmd = struct.unpack_from('<B', value, 2)[0]
  612. if cmd != 0x26:
  613. return
  614. port_num = struct.unpack_from('<B', value, 4)[0]
  615. if port_num == 0:
  616. logger.warning('return port of {} is zero.'.format(str(self.device)))
  617. return
  618. logger.debug('port of {} is: {}'.format(str(self.device), port_num))
  619. update_cache = {}
  620. for i in range(1, port_num + 1):
  621. offset = (i - 1) * 13 + 5
  622. port, value_type, status, time_value, power, elec_value, v, a = \
  623. struct.unpack_from('<BBBHHHHH', value, offset)
  624. FluentedEngine().in_power_udp(devNo = self.device.devNo,
  625. port = str(port),
  626. ts = ts,
  627. power = power,
  628. voltage = None,
  629. current = None)
  630. update_cache[str(port)] = {
  631. "voltage": round(float(v) / 10, 2),
  632. "power": power
  633. }
  634. if value_type == 0x00:
  635. update_cache[str(port)].update({
  636. 'leftTime': time_value,
  637. 'leftElec': elec_value
  638. })
  639. else:
  640. update_cache[str(port)].update({
  641. 'usedTime': time_value,
  642. 'usedElec': elec_value
  643. })
  644. logger.debug('update cache of {} is: {}'.format(str(self.device), update_cache))
  645. Device.update_dev_control_cache(self.device.devNo, update_cache)
  646. if not self.device.support_power_graph:
  647. Device.get_collection().update_one({'devNo': self.device.devNo}, {'$set': {'otherConf.supportPG': True}})
  648. Device.invalid_device_cache(self.device.devNo)