yuewantong.py 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  1. # coding=utf-8
  2. """
  3. 清远粤万通充电桩 2020-09-03修改
  4. 计费模式分为 按功率计费(服务器不断的算钱有没有用完, 服务器下发停止)
  5. 按时间计费(一次性下发时间,不需要服务器停止)
  6. 按电量计费(用到最后收集电量 进行判定)
  7. chargeType
  8. 收费模式分为 预付费方式(服务器先收钱, 收完了之后在启动设备, 如果存在没有使用完的钱 需要退还到用户的账户)
  9. 后付费方式(先不收钱, 等用户使用完了之后再计算总的钱,然后结束这笔订单,将所需的费用写入, 同时执行订单支付,如账户余额不足支付, 标记等待下次支付)
  10. 套餐模式分为 正常套餐(标记好多少钱,最多就用这么多)
  11. 充满自停套餐(不知道多少钱,反正一直充,充满为止,一般的处理方式是下发一个极大值)
  12. fill_2_hexByte 函数统一修改为 int_to_hex
  13. 在发送断电指令之后,主板不会新上报端口信息,而是直接结束
  14. """
  15. import binascii
  16. import datetime
  17. import logging
  18. import simplejson as json
  19. from pandas import Interval
  20. from apilib.monetary import RMB
  21. from apps.common.utils import int_to_hex
  22. from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT
  23. from apps.web.core.adapter.base import SmartBox, reverse_hex
  24. from apps.web.core.exceptions import ServiceException
  25. from apps.web.core.networking import MessageSender
  26. from apps.web.device.models import Device, DevicePortReport
  27. from apps.web.user.models import ServiceProgress
  28. from apps.web.utils import concat_user_login_entry_url
  29. logger = logging.getLogger(__name__)
  30. class YueWanTong(SmartBox):
  31. PORT_STATUS_MAP = {
  32. "0": Const.DEV_WORK_STATUS_IDLE,
  33. "1": Const.DEV_WORK_STATUS_WORKING,
  34. "2": Const.DEV_WORK_STATUS_APPOINTMENT,
  35. "3": Const.DEV_WORK_STATUS_FAULT,
  36. "4": Const.DEV_WORK_STATUS_FAULT,
  37. "5": Const.DEV_WORK_STATUS_FAULT,
  38. "6": Const.DEV_WORK_STATUS_FAULT
  39. }
  40. DEFAULT_MIN_CHARGE_TIME = 5
  41. DEFAULT_PORT_NUM = 12
  42. DEFAULT_MIN_POWER = 20
  43. DEFAULT_MAX_POWER = 500
  44. DEFAULT_FLOAT_TIME = 2
  45. DEFAULT_IC_CODE = "0001"
  46. DEFAULT_MIN_CONSUME = 0
  47. DEFAULT_POWER_PACKAGE = [
  48. {
  49. "lowLimit": 0,
  50. "upLimit": 250,
  51. "price": 1,
  52. },
  53. {
  54. "lowLimit": 250,
  55. "upLimit": 360,
  56. "price": 1,
  57. },
  58. {
  59. "lowLimit": 360,
  60. "upLimit": 500,
  61. "price": 1,
  62. }
  63. ]
  64. DEFAULT_ELEC_PRICE = 1
  65. DEFAULT_PAY_AFTER_USE = False
  66. DEFAULT_CHARGE_TYPE = "time" # 默认的计费模式 是时间计费 (功率不分档)
  67. # 服务费电费
  68. DEFAULT_ELEC_CHARGE = 0.5
  69. DEFAULT_SERVICE_CHARGE = 0.5
  70. @staticmethod
  71. def _suit_power_package(powerPackage):
  72. """
  73. 对于功率套餐的适配
  74. 旧版本是 100-200w x 元 y 分钟
  75. 新版本是 100-200w x 元 1 小时
  76. :param powerPackage:
  77. :return:
  78. """
  79. newPowerPackage = list()
  80. for _item in powerPackage:
  81. # 旧版本的
  82. if "time" not in _item:
  83. newPowerPackage.append(_item)
  84. else:
  85. # 做一个数据的适配
  86. price = float(_item["price"]) / (int(_item["time"]) / 60.0)
  87. newPowerPackage.append({
  88. "upLimit": _item["upLimit"], "lowLimit": _item["lowLimit"], "price": str(RMB(price))
  89. })
  90. return newPowerPackage
  91. @staticmethod
  92. def _to_ascii(s):
  93. """
  94. 将 字符串 转换为 16进制ascii 表达形式
  95. :param s: 原始字符串
  96. :return:
  97. """
  98. return binascii.hexlify(s)
  99. @staticmethod
  100. def _to_str(h):
  101. """
  102. 将 16进制ascii 转换成 字符串
  103. :param h:
  104. :return:
  105. """
  106. return binascii.unhexlify(h)
  107. @staticmethod
  108. def _transform_time(needTime, unit):
  109. """
  110. 根据单位 将套餐时间转换成分钟
  111. :param needTime: int
  112. :param unit: unicode
  113. :return:
  114. """
  115. if unit == u"小时":
  116. needTime = needTime * 60
  117. elif unit == u"分钟":
  118. needTime = needTime
  119. elif unit == u"秒":
  120. needTime = needTime // 60
  121. elif unit == u"次":
  122. needTime = None
  123. else:
  124. raise ServiceException({'result': 2, 'description': u'套餐错误'})
  125. return needTime
  126. def _send_data(self, funCode, sendData, cmd=None, timeout=MQTT_TIMEOUT.NORMAL, orderNo=None):
  127. """
  128. 发送报文封装
  129. :param funCode:
  130. :param sendData:
  131. :param cmd:
  132. :param timeout:
  133. :param orderNo:
  134. :return:
  135. """
  136. if cmd is None:
  137. cmd = DeviceCmdCode.OPERATE_DEV_SYNC
  138. result = MessageSender.send(device = self.device, cmd = cmd, payload = {
  139. "IMEI": self._device["devNo"],
  140. "funCode": funCode,
  141. "data": sendData
  142. }, timeout = timeout)
  143. if result.has_key("rst"):
  144. if result["rst"] == -1:
  145. raise ServiceException({'result': 2, 'description': u'充电桩正在玩命找网络,请稍候再试'})
  146. elif result["rst"] == 1:
  147. raise ServiceException({'result': 2, 'description': u'充电桩主板连接故障'})
  148. elif result["rst"] == 0:
  149. return result
  150. else:
  151. raise ServiceException({'result': 2, 'description': u'系统错误'})
  152. else:
  153. raise ServiceException({'result': 2, 'description': u'系统错误'})
  154. def _start_device(self, orderNo, port, chargeTime=None):
  155. """
  156. 开始充电
  157. :param orderNo: 下发的订单编号
  158. :param port: 开启的端口号
  159. :param chargeTime: 充电的最大时间,如果是充满自停,则设置为最大时间
  160. :return:
  161. """
  162. if chargeTime is None:
  163. chargeTime = 0xFFFF
  164. if orderNo is None:
  165. orderNo = 0
  166. orderNoHex = int_to_hex(int(orderNo), 14, reverse=True)
  167. portHex = int_to_hex(int(port), 2, reverse=True)
  168. chargeTimeHex = int_to_hex(int(chargeTime), 4, reverse=True)
  169. sendData = "{}{}{}010000".format(orderNoHex, portHex, chargeTimeHex)
  170. result = self._send_data("B2", sendData, timeout=MQTT_TIMEOUT.START_DEVICE, orderNo=orderNo)
  171. return result
  172. def _stop_device(self, orderNo, port):
  173. """
  174. 停止充电
  175. :param orderNo: 提供开始充电的时候的订单号
  176. :param port: 端口号
  177. :return:
  178. """
  179. orderNoHex = int_to_hex(int(orderNo), 14, reverse=True)
  180. portHex = int_to_hex(int(port), 2, reverse=True)
  181. sendData = "{}{}0000120000".format(orderNoHex, portHex)
  182. result = self._send_data("B2", sendData, timeout=MQTT_TIMEOUT.START_DEVICE, orderNo=orderNo)
  183. return result
  184. @staticmethod
  185. def _parse_device_stop(data):
  186. """
  187. 主动下发停止指令设备的回复 与结束上报不一样
  188. :param data:
  189. :return:
  190. """
  191. statusMap = {
  192. "00": Const.DEV_WORK_STATUS_IDLE,
  193. "01": Const.DEV_WORK_STATUS_WORKING,
  194. "02": Const.DEV_WORK_STATUS_APPOINTMENT,
  195. "03": Const.DEV_WORK_STATUS_FAULT, # 故障
  196. "04": Const.DEV_WORK_STATUS_FAULT, # 烟感
  197. "05": Const.DEV_WORK_STATUS_FAULT, # 雨感
  198. "06": Const.DEV_WORK_STATUS_FAULT # 设备锁定
  199. }
  200. orderHexNo = reverse_hex(data[8:22])
  201. portHex = data[22:24]
  202. voltage = int(reverse_hex(data[24:28]), 16)
  203. power = int(reverse_hex(data[32:36]), 16)
  204. elec = int(reverse_hex(data[36:44]), 16) / 1000.0
  205. chargeTime = int(reverse_hex(data[44:48]), 16)
  206. status = statusMap.get(data[52: 54], Const.DEV_WORK_STATUS_IDLE)
  207. return {
  208. "orderNoHex": orderHexNo,
  209. "portHex": portHex,
  210. "orderNo": int(orderHexNo, 16),
  211. "portStr": str(int(portHex, 16)),
  212. "voltage": voltage,
  213. "power": power,
  214. "elec": elec,
  215. "chargeTime": chargeTime,
  216. "status": status
  217. }
  218. def _read_port_status(self):
  219. """
  220. 从设备端读取端口状态 仅仅只包含状态量
  221. :return:
  222. """
  223. result = self._send_data("B3", sendData="00")
  224. data = result.get("data", "")[8: -4]
  225. lockPorts = self.device.otherConf.get("lockPorts") or list()
  226. portInfo = dict()
  227. while data:
  228. tempPort = str(int(data[0: 2], 16))
  229. tempStatus = self.PORT_STATUS_MAP.get(data[34: 36], Const.DEV_WORK_STATUS_IDLE) if tempPort not in lockPorts else Const.DEV_WORK_STATUS_FORBIDDEN
  230. portInfo.update({tempPort: tempStatus})
  231. data = data[4: ]
  232. return portInfo
  233. def _read_port_charge_status(self):
  234. """
  235. 从设备读取设备端口的状态 详细数据,包含电流 功率 电压 电量 时间 等等
  236. :return:
  237. """
  238. result = self._send_data("B4", sendData="00")
  239. data = result.get("data", "")[8: -4]
  240. lockPorts = self.device.otherConf.get("lockPorts") or list()
  241. portInfo = dict()
  242. while data:
  243. tempPort = str(int(data[0: 2], 16))
  244. tempVoltage = int(reverse_hex(data[2: 6]), 16)
  245. tempPower = int(reverse_hex(data[10: 14]), 16)
  246. tempElec = int(reverse_hex(data[14: 22]), 16) / 1000.0
  247. tempTime = int(reverse_hex(data[22: 26]), 16)
  248. tempTemper = int(reverse_hex(data[30: 34]), 16)
  249. tempStatus = self.PORT_STATUS_MAP.get(data[34: 36], Const.DEV_WORK_STATUS_IDLE) if tempPort not in lockPorts else Const.DEV_WORK_STATUS_FORBIDDEN
  250. portInfo.update({
  251. tempPort: {
  252. "voltage": tempVoltage,
  253. "elec": tempElec,
  254. "power": tempPower,
  255. "chargeTime": tempTime,
  256. "temperature": tempTemper,
  257. "status": tempStatus
  258. }
  259. })
  260. data = data[36: ]
  261. return portInfo
  262. def _reboot_device(self):
  263. """
  264. 重启设备
  265. :return:
  266. """
  267. result = self._send_data("B5", sendData="00")
  268. status = result["data"][8: 10]
  269. if status != "01":
  270. raise ServiceException({'result': 2, 'description': u'设备重启失败,请重新操作试试'})
  271. def _lock_unlock_device(self, lock):
  272. """
  273. 锁定/解锁设备
  274. :param lock: True / False
  275. :return:
  276. """
  277. # 设备锁定发送 00 / 解锁发送 01
  278. sendData = int_to_hex(int(lock), 2, reverse=True)
  279. message = "设备锁定失败" if lock else "设备解锁失败"
  280. result = self._send_data("B6", sendData=sendData)
  281. status = result["data"][8: 10]
  282. if status != "01":
  283. raise ServiceException({'result': 2, 'description': u'{},请重新操作试试'.format(message)})
  284. def _sync_device_time(self):
  285. """
  286. 开机后设备向服务器请求同步时间,服务器回复设备
  287. :return:
  288. """
  289. nowTime = datetime.datetime.now()
  290. yearHex = int_to_hex(nowTime.year, 4, reverse=True)
  291. monthHex = int_to_hex(nowTime.month, 2)
  292. dayHex = int_to_hex(nowTime.day, 2)
  293. hourHex = int_to_hex(nowTime.hour, 2)
  294. minuteHex = int_to_hex(nowTime.minute, 2)
  295. secondHex = int_to_hex(nowTime.second, 2)
  296. sendData = yearHex + monthHex + dayHex + hourHex + minuteHex + secondHex + "00"
  297. self._send_data("A1", sendData=sendData, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  298. def _sync_device_settings(self):
  299. """
  300. 开机后设备向服务器请求同步参数 包括需要扫描的二维码,刷卡前缀 最小功率最大功率等
  301. :return:
  302. """
  303. otherConf = self._device.get("otherConf", dict())
  304. # 扫描的设备二维码
  305. qr_code_url = concat_user_login_entry_url(l = self._device["logicalCode"])
  306. snCode = otherConf.get("snCode", self._device["logicalCode"].replace("G", ""))
  307. minPower = otherConf.get("minPower", YueWanTong.DEFAULT_MIN_POWER)
  308. maxPower = otherConf.get("maxPower", YueWanTong.DEFAULT_MAX_POWER)
  309. floatTime = otherConf.get("floatTime", YueWanTong.DEFAULT_FLOAT_TIME)
  310. ICSetupCode = otherConf.get("ICSetupCode", YueWanTong.DEFAULT_IC_CODE)
  311. qrCodeLenHex = int_to_hex(len(qr_code_url), 2)
  312. minPowerHex = int_to_hex(int(minPower), 4, reverse=True)
  313. maxPowerHex = int_to_hex(int(maxPower), 4, reverse=True)
  314. floatTimeHex = int_to_hex(int(floatTime), 4, reverse=True)
  315. qrCodeHex = self._to_ascii(qr_code_url)
  316. snCodeHex = self._to_ascii(snCode)
  317. ICSetupCodeHex = self._to_ascii(ICSetupCode)
  318. sendData = snCodeHex + minPowerHex + maxPowerHex + floatTimeHex + ICSetupCodeHex + qrCodeLenHex + qrCodeHex
  319. self._send_data("A2", sendData=sendData, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  320. @staticmethod
  321. def _parse_card_start(data):
  322. """
  323. 解析刷卡启动的数据
  324. :param data:
  325. :return:
  326. """
  327. cardPre = YueWanTong._to_str(data[8: 16])
  328. cardNo = YueWanTong._to_str(data[16: 32])
  329. portStr = str(int(data[32: 34], 16))
  330. return {
  331. "cardPre": cardPre,
  332. "cardNo": cardNo,
  333. "portStr": portStr,
  334. "portStrHex": data[32:34],
  335. "cardCode": data[8: 32]
  336. }
  337. @staticmethod
  338. def _parse_card_stop(data):
  339. """
  340. 解析刷卡结束 数据
  341. :param data:
  342. :return:
  343. """
  344. cardInfo = YueWanTong._parse_card_start(data)
  345. orderNoHex = data[34: 48]
  346. orderNo = str(int(reverse_hex(orderNoHex), 16))
  347. cardInfo.update({
  348. "orderNo": orderNo,
  349. "orderNoHex": orderNoHex
  350. })
  351. return cardInfo
  352. @staticmethod
  353. def _parse_report(data):
  354. """
  355. 设备 每10分钟 每个端口 上报一次充电状态
  356. 电压 电流 功率 端口状态 温度 是 当前值
  357. 电量 时间(断电保护) 是累计值
  358. 电流 暂时用不上 不用解析
  359. :param data:
  360. :return:
  361. """
  362. statusMap = {
  363. "00": Const.DEV_WORK_STATUS_IDLE,
  364. "01": Const.DEV_WORK_STATUS_WORKING,
  365. "02": Const.DEV_WORK_STATUS_APPOINTMENT,
  366. "03": Const.DEV_WORK_STATUS_FAULT, # 故障
  367. "04": Const.DEV_WORK_STATUS_FAULT, # 烟感
  368. "05": Const.DEV_WORK_STATUS_FAULT, # 雨感
  369. "06": Const.DEV_WORK_STATUS_FAULT # 设备锁定
  370. }
  371. orderNoHex = data[8: 22]
  372. orderNo = str(int(reverse_hex(orderNoHex), 16))
  373. portStr = str(int(data[22: 24], 16))
  374. voltage = int(reverse_hex(data[24: 28]), 16)
  375. power = int(reverse_hex(data[32: 36]), 16)
  376. elec = int(reverse_hex(data[36: 44]), 16) / 1000.0
  377. chargeTime = int(reverse_hex(data[44: 48]), 16)
  378. temperature = int(reverse_hex(data[52: 56]), 16)
  379. status = statusMap.get(data[56: 58], Const.DEV_WORK_STATUS_IDLE)
  380. return {
  381. "orderNo": orderNo,
  382. "orderNoHex": orderNoHex,
  383. "portStr": portStr,
  384. "voltage": voltage,
  385. "power": power,
  386. "elec": elec,
  387. "chargeTime": chargeTime,
  388. "temperature": temperature,
  389. "status": status
  390. }
  391. @staticmethod
  392. def _parse_all_report(data):
  393. data = data[8: -4]
  394. result = list()
  395. while data:
  396. orderNoHex = data[: 14]
  397. orderNo = str(int(reverse_hex(data[:14]), 16))
  398. portStr = str(int(data[14: 16], 16))
  399. voltage = int(reverse_hex(data[16: 20]), 16)
  400. power = int(reverse_hex(data[24: 28]), 16)
  401. elec = int(reverse_hex(data[28: 36]), 16) / 1000.0
  402. chargeTime = int(reverse_hex(data[36: 40]), 16)
  403. temperature = int(reverse_hex(data[44: 48]), 16)
  404. status = YueWanTong.PORT_STATUS_MAP.get(data[48: 50], Const.DEV_WORK_STATUS_IDLE)
  405. data = data[50: ]
  406. result.append({
  407. "orderNo": orderNo,
  408. "orderNoHex": orderNoHex,
  409. "portStr": portStr,
  410. "voltage": voltage,
  411. "power": power,
  412. "elec": elec,
  413. "chargeTime": chargeTime,
  414. "temperature": temperature,
  415. "status": status
  416. })
  417. return {"subChargeList": result}
  418. @staticmethod
  419. def _parse_fault(data):
  420. """
  421. 解析故障上报数据
  422. :param data:
  423. :return:
  424. """
  425. # TODO zjl 目前只有三个,后续可能会增加
  426. faultMap = {
  427. "1001": u"烟雾报警",
  428. "1002": u"雨感报警",
  429. "1003": u"温度报警"
  430. }
  431. portStr = str(int(data[8: 10], 16))
  432. faultHex = data[10: 14]
  433. fault = str(int(faultHex[2: 4] + faultHex[: 2], 16))
  434. faultReason = faultMap.get(fault)
  435. return {
  436. "portHex": data[8: 10],
  437. "portStr": portStr,
  438. "faultCode": fault,
  439. "statusInfo": faultReason
  440. }
  441. @staticmethod
  442. def _parse_stop(data):
  443. """
  444. 解析充电结束上报事件,与状态上报类似
  445. :param data:
  446. :return:
  447. """
  448. reasonMap = {
  449. "00": u"充满自停",
  450. "01": u"未检测到充电设备",
  451. "02": u"过载结束",
  452. "03": u"电压输出故障",
  453. "04": u"刷卡结束",
  454. "05": u"充满自停",
  455. "06": u"密码开锁结束订单"
  456. }
  457. orderNoHex = data[8: 22]
  458. orderNo = str(int(reverse_hex(orderNoHex), 16))
  459. portHex = data[22: 24]
  460. portStr = str(int(data[22: 24], 16))
  461. voltage = int(reverse_hex(data[24: 28]), 16)
  462. power = int(reverse_hex(data[32: 36]), 16)
  463. elec = int(reverse_hex(data[36: 44]), 16) / 1000.0
  464. chargeTime = int(reverse_hex(data[44: 48]), 16)
  465. temperature = int(reverse_hex(data[52: 56]), 16)
  466. reasonCode = data[56: 58]
  467. reason = reasonMap.get(reasonCode)
  468. return {
  469. "orderNoHex": orderNoHex,
  470. "orderNo": orderNo,
  471. "portHex": portHex,
  472. "portStr": portStr,
  473. "voltage": voltage,
  474. "power": power,
  475. "elec": elec,
  476. "chargeTime": chargeTime,
  477. "temperature": temperature,
  478. "reasonCode": reasonCode,
  479. "reason": reason
  480. }
  481. def _ack(self, ack_id):
  482. self._send_data(funCode='AK', sendData=ack_id, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  483. def get_port_status_from_dev(self):
  484. """
  485. 从设备端 获取端口信息 充电/空闲
  486. :return:
  487. """
  488. portInfo = self._read_port_status()
  489. portDict = dict()
  490. for portStr, status in portInfo.items():
  491. portDict[portStr] = {"status": status}
  492. # 更新可用端口数量
  493. allPorts, usedPorts, usePorts = self.get_port_static_info(portDict)
  494. Device.update_dev_control_cache(
  495. self._device["devNo"],
  496. {
  497. "allPorts": allPorts,
  498. "usedPorts": usedPorts,
  499. "usePorts": usePorts
  500. }
  501. )
  502. # 更新端口状态
  503. devCache = Device.get_dev_control_cache(self._device["devNo"])
  504. for port, info in portDict.items():
  505. if port in devCache and isinstance(info, dict):
  506. devCache[port].update({"status": info["status"]})
  507. else:
  508. devCache[port] = info
  509. Device.update_dev_control_cache(self._device["devNo"], devCache)
  510. return portDict
  511. def get_port_status(self, force = False):
  512. """
  513. 获取端口状态
  514. :param force:
  515. :return:
  516. """
  517. self.get_port_status_from_dev()
  518. # 获取端口缓存
  519. devCache = Device.get_dev_control_cache(self._device["devNo"])
  520. if "allPorts" not in devCache:
  521. self.get_port_status_from_dev()
  522. devCache = Device.get_dev_control_cache(self._device["devNo"])
  523. allPorts = devCache.get("allPorts")
  524. if allPorts is None:
  525. raise ServiceException({'result': 2, 'description': u'充电端口信息获取失败'})
  526. # 获取显示端口的数量 客户要求可以设置 显示给用户多少个端口
  527. showPortNum = self._device.get("otherConf", {}).get("actualPortNum", self.DEFAULT_PORT_NUM)
  528. statusDict = dict()
  529. showStatusDict = dict()
  530. for portNum in xrange(allPorts):
  531. portStr = str(portNum + 1)
  532. tempDict = devCache.get(portStr, {})
  533. if "status" in tempDict:
  534. statusDict[portStr] = {"status": tempDict["status"]}
  535. elif "isStart" in tempDict:
  536. if tempDict["isStart"]:
  537. statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_WORKING}
  538. else:
  539. statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_IDLE}
  540. else:
  541. statusDict[portStr] = {"status": Const.DEV_WORK_STATUS_IDLE}
  542. if int(portStr) <= int(showPortNum):
  543. showStatusDict[portStr] = {"status": statusDict[portStr]["status"]}
  544. allPorts, usedPorts, usePorts = self.get_port_static_info(statusDict)
  545. portsDict = {"allPorts": allPorts, "usedPorts": usedPorts, "usePorts": usePorts}
  546. Device.update_dev_control_cache(self._device["devNo"], portsDict)
  547. # 返还的是要显示的端口数量
  548. return showStatusDict
  549. def get_port_info(self, line=None):
  550. """
  551. 获取单一的端口状态
  552. :param line:
  553. :return:
  554. """
  555. if line is None:
  556. return
  557. portStr = str(line)
  558. # 设备端读取端口状态
  559. result = self._read_port_charge_status()
  560. portInfo = result.get(portStr, {})
  561. # 缓存端读取设备端口状态
  562. devCache = Device.get_dev_control_cache(self._device["devNo"])
  563. portCache = devCache.get(portStr, {})
  564. portCache.update(portInfo)
  565. Device.update_dev_control_cache(self._device["devNo"], {portStr: portCache})
  566. if portCache.get('chargeType') == 'billAsService':
  567. elecCharge = round(self.device.otherConf.get("elecCharge", YueWanTong.DEFAULT_ELEC_CHARGE), 2)
  568. serviceCharge = round(self.device.otherConf.get("serviceCharge", YueWanTong.DEFAULT_SERVICE_CHARGE), 2)
  569. portCache['elecFee'] = round(portInfo.get('elec', 0) * elecCharge, 2)
  570. portCache['serviceFee'] = round(portInfo.get('elec', 0) * serviceCharge, 2)
  571. return portCache
  572. def get_dev_setting(self):
  573. """
  574. 读取设备设置
  575. 显示的有: IC卡设置码 最小充电功率 最大充电功率 浮充时间 显示的端口数量
  576. :return:
  577. """
  578. otherConf = self._device.get("otherConf", {})
  579. powerPackage = otherConf.get("powerPackage", YueWanTong.DEFAULT_POWER_PACKAGE)
  580. newPowerPackage = YueWanTong._suit_power_package(powerPackage)
  581. payAfterUse = int(otherConf.get("payAfterUse", False)) # 前台接受的是数值模型
  582. # 电费 + 服务费
  583. elecCharge = round(otherConf.get("elecCharge", YueWanTong.DEFAULT_ELEC_CHARGE), 2)
  584. serviceCharge = round(otherConf.get("serviceCharge", YueWanTong.DEFAULT_SERVICE_CHARGE), 2)
  585. return {
  586. "minPower": otherConf.get("minPower", YueWanTong.DEFAULT_MIN_POWER),
  587. "maxPower": otherConf.get("maxPower", YueWanTong.DEFAULT_MAX_POWER),
  588. "floatTime": otherConf.get("floatTime", YueWanTong.DEFAULT_FLOAT_TIME),
  589. "ICSetupCode": otherConf.get("ICSetupCode", YueWanTong.DEFAULT_IC_CODE),
  590. "actualPortNum": otherConf.get("actualPortNum", YueWanTong.DEFAULT_PORT_NUM),
  591. "minConsume": otherConf.get("minConsume", YueWanTong.DEFAULT_MIN_CONSUME),
  592. "refundProtectTime": otherConf.get("refundProtectTime", YueWanTong.DEFAULT_MIN_CHARGE_TIME),
  593. "minAfterStartCoins": otherConf.get("minAfterStartCoins", 2),
  594. "payAfterUse": payAfterUse,
  595. "chargeType": otherConf.get("chargeType", YueWanTong.DEFAULT_CHARGE_TYPE),
  596. "elecPrice": otherConf.get("elecPrice", YueWanTong.DEFAULT_ELEC_PRICE),
  597. "powerPackage": newPowerPackage,
  598. # 电费 + 服务费
  599. "elecCharge": elecCharge,
  600. "serviceCharge": serviceCharge,
  601. }
  602. def update_device_conf(self, data):
  603. minPower = data.get("minPower")
  604. maxPower = data.get("maxPower")
  605. floatTime = data.get("floatTime")
  606. ICSetupCode = data.get("ICSetupCode")
  607. actualPortNum = data.get("actualPortNum")
  608. minConsume = data.get("minConsume")
  609. payAfterUse = data.get("payAfterUse")
  610. refundProtectTime = data.get("refundProtectTime")
  611. minAfterStartCoins = data.get("minAfterStartCoins")
  612. powerPackage = data.get("powerPackage")
  613. chargeType = data.get("chargeType")
  614. elecPrice = data.get("elecPrice")
  615. # 电费 + 服务费
  616. elecCharge = round(data.get("elecCharge", YueWanTong.DEFAULT_ELEC_CHARGE), 2)
  617. serviceCharge = round(data.get("serviceCharge", YueWanTong.DEFAULT_SERVICE_CHARGE), 2)
  618. otherConf = self._device.get("otherConf", {})
  619. otherConf.update({
  620. "minPower": int(minPower),
  621. "maxPower": int(maxPower),
  622. "floatTime": int(floatTime),
  623. "ICSetupCode": ICSetupCode,
  624. "actualPortNum": int(actualPortNum),
  625. "minConsume": float(minConsume),
  626. "minAfterStartCoins": float(minAfterStartCoins),
  627. "refundProtectTime": int(refundProtectTime),
  628. "payAfterUse": bool(int(payAfterUse)),
  629. "chargeType": chargeType,
  630. "elecPrice": float(elecPrice),
  631. "powerPackage": powerPackage,
  632. # 服务费 + 电量
  633. "elecCharge": elecCharge,
  634. "serviceCharge": serviceCharge,
  635. })
  636. devNo = self._device["devNo"]
  637. try:
  638. Device.objects.filter(devNo=devNo).update(otherConf=otherConf)
  639. except Exception as e:
  640. logger.error(e)
  641. raise ServiceException({'result': 2, 'description': u'设置错误,请重新操作试试'})
  642. Device.invalid_device_cache(devNo)
  643. def set_device_function(self, request, lastSetConf):
  644. """
  645. 设备功能
  646. :param request:
  647. :param lastSetConf:
  648. :return:
  649. """
  650. lockDevice = request.POST.get("lockDevice", None)
  651. rebootDevice = request.POST.get("rebootDevice", None)
  652. setToDevice = request.POST.get("setToDevice", None)
  653. if rebootDevice is not None:
  654. self._reboot_device()
  655. return
  656. if lockDevice is not None and lockDevice in ("false", "true"):
  657. lockDevice = json.loads(lockDevice)
  658. self._lock_unlock_device(lockDevice)
  659. return
  660. # 下发所有参数到设备,这个地方做一个保护 ,设备正在工作的时候不让设置参数
  661. if setToDevice is not None:
  662. self._sync_device_settings()
  663. return
  664. def set_device_function_param(self, request, lastSetConf):
  665. """
  666. 设置设备参数 需要对参数规则做校验
  667. :param request:
  668. :param lastSetConf:
  669. :return:
  670. """
  671. # 校验端口数量
  672. actualPortNum = request.POST.get("actualPortNum", None)
  673. if actualPortNum is not None and int(actualPortNum) > 36:
  674. raise ServiceException({"result": 2, "description": u"实际端口数量最大36"})
  675. powerPackage = request.POST.get("powerPackage", None)
  676. if powerPackage is not None:
  677. if len(powerPackage) > 6:
  678. raise ServiceException({"result": 2, "description": u"功率计费不得大于六档"})
  679. # 这个地方开始各种计费模式收费模式
  680. chargeType = request.POST.get("chargeType")
  681. if chargeType not in ["power", "time", "elec", "billAsService"]:
  682. raise ServiceException({"result": 2, "description": u"不支持的计费模式"})
  683. self.update_device_conf(request.POST)
  684. def start_device(self, package, openId, attachParas):
  685. """
  686. :param package:
  687. :param openId:
  688. :param attachParas:
  689. :return:
  690. """
  691. # 在此次改版的时候, 凡是 payAfterUse == True 的设备 都说明是使用了按功率付费的设备,这个地方做一个转换 为下一步切换做准备
  692. otherConf = self.device.get("otherConf", dict())
  693. if "chargeType" not in otherConf:
  694. if otherConf.get("payAfterUse", YueWanTong.DEFAULT_PAY_AFTER_USE):
  695. otherConf["chargeType"] = "power"
  696. else:
  697. otherConf["chargeType"] = "time"
  698. Device.objects.filter(devNo=self.device.devNo).update(otherConf=otherConf)
  699. Device.invalid_device_cache(self.device.devNo)
  700. # 由于参数重新更新过了 这个地方就在加载一次
  701. dev = Device.get_dev(self.device.devNo)
  702. self._device = dev
  703. # 从启动参数中获取相关的 参数 订单号 以及 充电端口等等
  704. portStr = attachParas.get("chargeIndex")
  705. orderNo = attachParas.get("orderNo")
  706. if portStr is None:
  707. raise ServiceException({'result': 2, 'description': u'未知端口'})
  708. lockPorts = self.device.otherConf.get("lockPorts") or list()
  709. if str(portStr) in lockPorts:
  710. raise ServiceException({"result": 2, "description": u"当前端口已被禁用"})
  711. # 计算充电时间 如果套餐是充满自停 则会在启动的时候设置为最大值
  712. unit = package.get("unit")
  713. needTime = package.get("time")
  714. coins = package.get("coins")
  715. needTime = self._transform_time(needTime, unit)
  716. # 获取当前的计费模式
  717. otherConf = self.device.get("otherConf", dict())
  718. chargeType = otherConf.get("chargeType")
  719. payAfterUse = otherConf.get("payAfterUse")
  720. if chargeType == "time" and not needTime:
  721. raise ServiceException({"result": 2, "description": u"时间套餐设置错误,请联系经销商解决"})
  722. # 发送指令
  723. result = self._start_device(orderNo=orderNo, port=portStr, chargeTime=needTime)
  724. portDict = {
  725. "status": Const.DEV_WORK_STATUS_WORKING,
  726. "vCardId": self._vcard_id,
  727. "isStart": True,
  728. "openId": openId,
  729. "orderNo": orderNo,
  730. "coins": coins,
  731. "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  732. "chargeType": chargeType,
  733. "payAfterUse": payAfterUse
  734. }
  735. if needTime:
  736. portDict["needTime"] = needTime
  737. Device.update_dev_control_cache(self._device["devNo"], {str(portStr): portDict})
  738. result["consumeOrderNo"] = orderNo
  739. return result
  740. def analyze_event_data(self, data):
  741. """
  742. 解析设备上报的各种事件
  743. :param data:
  744. :return:
  745. """
  746. funcCode = data[6: 8]
  747. # 主板请求同步时间
  748. if funcCode == "A1":
  749. self._sync_device_time()
  750. return
  751. # 主板请求同步设备参数
  752. elif funcCode == "A2":
  753. self._sync_device_settings()
  754. return
  755. # 刷卡请求充电
  756. elif funcCode == "B0":
  757. ret = self._parse_card_start(data)
  758. ret.update({'cmdCode': 'B0'})
  759. return ret
  760. # 刷卡结束充电
  761. elif funcCode == "B1":
  762. ret = self._parse_card_stop(data)
  763. ret.update({'cmdCode': 'B1'})
  764. return ret
  765. # 设备上报充电结束
  766. elif funcCode == "C1":
  767. ret = self._parse_stop(data)
  768. ret.update({'cmdCode': 'C1'})
  769. return ret
  770. # 端口状态上报
  771. elif funcCode == "C0":
  772. ret = self._parse_report(data)
  773. ret.update({'cmdCode': 'C0'})
  774. return ret
  775. elif funcCode == "CA":
  776. ret = self._parse_all_report(data)
  777. ret.update({'cmdCode': 'CA'})
  778. return ret
  779. # 故障上报
  780. elif funcCode == "E0":
  781. ret = self._parse_fault(data)
  782. ret.update({'cmdCode': 'E0'})
  783. return ret
  784. else:
  785. logger.error("error event! funCode is {}".format(funcCode))
  786. def _get_time_use_money(self, reportRecord): # type:(DevicePortReport) -> float
  787. """
  788. 时间计费模式下 预付费的充电时间由 下发的参数进行控制 这个地方不再予以控制
  789. :param reportRecord:
  790. :return:
  791. """
  792. portCache = Device.get_port_control_cache(self.device.devNo, reportRecord.port)
  793. needTime = portCache.get("needTime") # 单位分钟
  794. coins = portCache["coins"]
  795. # 时间模式下一定会有 needTime 如果没有needTime 一定是出错了 做个保护
  796. if not needTime:
  797. return coins
  798. return coins * float(reportRecord.chargeTime) / float(needTime)
  799. def _get_elec_use_money(self, reportRecord): # type:(DevicePortReport) -> float
  800. """
  801. 电量计费模式下 预付费模式下的已经使用的金额
  802. :param reportRecord:
  803. :return:
  804. """
  805. usedElec = reportRecord.elec
  806. elecPrice = self.device.get("otherConf", dict()).get("elecPrice", self.DEFAULT_ELEC_PRICE)
  807. return usedElec * elecPrice
  808. def _get_power_use_money(self, reportRecord): # type:(DevicePortReport) -> float
  809. records = DevicePortReport.billing_records(reportRecord.orderNo)
  810. intervalMap = self._get_power_interval_map()
  811. consumeMoney = DevicePortReport.calculate(records, intervalMap)
  812. return consumeMoney
  813. def _get_billAsService_use_money(self, reportRecord):
  814. """
  815. 服务费 + 电费模式
  816. :param reportRecord:
  817. :return:
  818. """
  819. usedElec = reportRecord.elec
  820. elecCharge = round(
  821. float(self.device.get("otherConf", dict()).get("elecCharge", self.DEFAULT_ELEC_CHARGE)) * usedElec, 2)
  822. serviceCharge = round(float(
  823. self.device.get("otherConf", dict()).get("serviceCharge", self.DEFAULT_SERVICE_CHARGE)) * usedElec, 2)
  824. return elecCharge, serviceCharge
  825. def _get_power_interval_map(self):
  826. """
  827. 获取计费区间---单价映射
  828. :return:
  829. """
  830. if hasattr(self, "_interval_map"):
  831. return self._interval_map
  832. powerPackage = self.device.get(
  833. "otherConf", dict()
  834. ).get("powerPackage", self.DEFAULT_POWER_PACKAGE)
  835. powerPackage = self._suit_power_package(powerPackage)
  836. mapDict = dict()
  837. for package in powerPackage:
  838. lowLimit = package.get("lowLimit")
  839. upLimit = package.get("upLimit")
  840. price = package.get("price")
  841. _time = package.get("time")
  842. mapDict.update({
  843. Interval(int(lowLimit), int(upLimit), closed = "left"):
  844. {
  845. "unitPrice": float(price),
  846. "time": 0
  847. }
  848. })
  849. setattr(self, "_interval_map", mapDict)
  850. return mapDict
  851. def stop(self, port = None):
  852. portCache = Device.get_port_control_cache(self.device.devNo, str(port))
  853. orderNo = portCache.get('orderNo', 0)
  854. openId = portCache.get('openId', None)
  855. if orderNo == 0 or openId is None:
  856. raise ServiceException({'result': 2, 'description': u'未知订单无法停止,请稍后再试。'})
  857. result = self._stop_device(orderNo, str(port))
  858. event_data = self.__parse_B2(result)
  859. from apps.web.eventer.yuewantong import YueWanTongWorkerEvent
  860. ywt = YueWanTongWorkerEvent(self,event_data)
  861. ywt.do()
  862. ServiceProgress.update_progress_and_consume_rcd(
  863. self.device['ownerId'],
  864. {
  865. 'open_id': openId, 'device_imei': self.device['devNo'],
  866. 'port': int(port), 'isFinished': False
  867. },{})
  868. infoDict = dict()
  869. infoDict['remainder_time'] = 0
  870. return infoDict
  871. @property
  872. def isHaveStopEvent(self):
  873. return True
  874. def __parse_B2(self,data):
  875. data = data.get("data")
  876. dataDict = {
  877. "cmdCode" : data[6:8],
  878. "orderNoHex" : data[8:22],
  879. "orderNo" :str(int(reverse_hex(data[8:22]), 16)),
  880. "portHex" : data[22:24],
  881. "portStr" : str(int(reverse_hex(data[22:24]), 16)),
  882. "voltage" : str(int(reverse_hex(data[24:28]), 16)),
  883. "power" : str(int(reverse_hex(data[32:36]), 16)),
  884. "elec" : int(reverse_hex(data[36:44]), 16) / 1000.0,
  885. "chargeTime" : int(reverse_hex(data[44:48]), 16),
  886. "reason" : u"远程手动停止充电!"
  887. }
  888. return dataDict
  889. def lock_unlock_port(self, port, lock=True):
  890. """
  891. 禁用端口
  892. 主板不支持 禁用端口 只能服务器实现
  893. """
  894. otherConf = self.device.get("otherConf")
  895. lockPorts = otherConf.get("lockPorts", list())
  896. port = str(port)
  897. try:
  898. if lock:
  899. if port not in lockPorts:
  900. lockPorts.append(port)
  901. else:
  902. lockPorts.remove(port)
  903. except Exception as e:
  904. logger.error(e)
  905. otherConf.update({"lockPorts": lockPorts})
  906. try:
  907. Device.objects(devNo=self.device.devNo).update(otherConf=otherConf)
  908. Device.invalid_device_cache(self.device.devNo)
  909. except Exception as e:
  910. logger.error("update device %s lockPorts error the reason is %s " % (self.device.devNo, e))
  911. raise ServiceException({'result': 2, 'description': u'操作失败,请重新试试'})