changyuanFive.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import re
  5. import time
  6. import logging
  7. from decimal import Decimal
  8. from apilib.monetary import RMB
  9. from apps.web.common.models import TempValues
  10. from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT
  11. from apps.web.core.adapter.base import SmartBox, fill_2_hexByte
  12. from apps.web.core.exceptions import ServiceException
  13. from apps.web.core.networking import MessageSender
  14. from apps.web.device.models import Device
  15. from apps.web.user.models import Card
  16. logger = logging.getLogger(__name__)
  17. class ChangyuanFive(SmartBox):
  18. FINISH_REASON_MAP = {
  19. '01': '设备充满自停或拔掉插头',
  20. '02': '超功率停止',
  21. '03': '时间用完停止',
  22. '04': '远程停止',
  23. '05': '预付卡的扣费金额用完',
  24. '06': '到达安全充电时间',
  25. '07': '正常结束充电',
  26. '09': '电量超限',
  27. '10': '设备充满停止充电'
  28. }
  29. PAY_TYPE_MAP = {
  30. 'cash': '01',
  31. 'vCard': '02',
  32. 'coin': '03'
  33. }
  34. DEFAULT_DISCOUNT = '0'
  35. @staticmethod
  36. def _parse_D0_data(data):
  37. '''
  38. 解析 设备参数 返还的数据
  39. :param data:
  40. :return:
  41. '''
  42. powerTime1 = ChangyuanFive.decode_str(data[6:10])
  43. powerTime2 = ChangyuanFive.decode_str(data[10:14])
  44. powerTime3 = ChangyuanFive.decode_str(data[14:18])
  45. powerTime4 = ChangyuanFive.decode_str(data[18:22])
  46. power1 = ChangyuanFive.decode_str(data[22:26])
  47. power2 = ChangyuanFive.decode_str(data[26:30])
  48. power3 = ChangyuanFive.decode_str(data[30:34])
  49. power4 = ChangyuanFive.decode_str(data[34:38])
  50. noloadPower = ChangyuanFive.decode_str(data[38:40])
  51. noloadTime = ChangyuanFive.decode_str(data[40:42])
  52. volume = ChangyuanFive.decode_str(data[42:44])
  53. cardFee = ChangyuanFive.decode_str(data[44:48], ratio=0.01)
  54. floatPower = ChangyuanFive.decode_str(data[48:50])
  55. floatTime = ChangyuanFive.decode_str(data[50:54])
  56. cardFree = True if data[54:56] == 'AA' else False
  57. return locals()
  58. @staticmethod
  59. def _parse_D3_data(data):
  60. """
  61. 解析同步结果指令上报
  62. :param data:
  63. :return:
  64. """
  65. cardType = data[6:8]
  66. cardNo = data[8:16]
  67. cardBalance = RMB(int(data[16:22], 16))
  68. result = True if data[22:24] == 'AA' else False
  69. sid = int(data[24:28], 16)
  70. if cardType == '01':
  71. cardBalance = cardBalance * 0.01
  72. return {
  73. "cardType": cardType,
  74. "cardNo": cardNo,
  75. "cardBalance": cardBalance,
  76. "result": result,
  77. "sid": sid
  78. }
  79. @staticmethod
  80. def _parse_D4_data(data):
  81. """
  82. 解析 卡余额同步 结果
  83. :param data:
  84. :type data:
  85. :return:
  86. :rtype:
  87. """
  88. cardType = data[6:8]
  89. cardNo = data[8:16]
  90. cardBalance = RMB(int(data[16:22], 16))
  91. if cardType == '01':
  92. cardBalance = cardBalance * 0.01
  93. return {
  94. "cardType": cardType,
  95. "cardNo": cardNo,
  96. "cardBalance": cardBalance
  97. }
  98. @staticmethod
  99. def _parse_D7_data(data):
  100. '''
  101. 火警数据解析
  102. :param data:
  103. :type data:
  104. :return:
  105. :rtype:
  106. '''
  107. return {'data': data[6:8]}
  108. @staticmethod
  109. def _parse_D8_data(data):
  110. '''
  111. 定时上传 桩的工作状态
  112. :param data:
  113. :type data:
  114. :return:
  115. :rtype:
  116. '''
  117. if len(data) == 18:
  118. # voltage = int(data[6:10], 16) / 10.0
  119. temperature = int(data[8:10], 16)
  120. if data[6:8] == '01':
  121. temperature = temperature * -1
  122. defaultPortInfo = {'status': Const.DEV_WORK_STATUS_IDLE}
  123. portInfo = {}.fromkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', ], defaultPortInfo)
  124. return {'temperature': temperature, 'portInfo': portInfo}
  125. else:
  126. all_port_status = ChangyuanFive.decode_long_hex_to_list(data[6:26], transformer=False)
  127. all_port_power = ChangyuanFive.decode_long_hex_to_list(data[26:66],split=4)
  128. all_port_left = ChangyuanFive.decode_long_hex_to_list(data[66:106],split=4)
  129. temperature = int(data[108:110], 16)
  130. if data[106:108] == '01':
  131. temperature = temperature * -1
  132. portInfo = {}
  133. for i in xrange(len(all_port_status)):
  134. item = {}
  135. item['power'] = all_port_power[i]
  136. item['leftTime'] = all_port_left[i]
  137. tempStatus = all_port_status[i]
  138. if tempStatus == '00':
  139. status = Const.DEV_WORK_STATUS_IDLE
  140. elif tempStatus == '01':
  141. status = Const.DEV_WORK_STATUS_WORKING
  142. elif tempStatus == '11':
  143. status = Const.DEV_WORK_STATUS_WORKING
  144. del item['leftTime']
  145. item['leftMoney'] = round(all_port_left[i] * 0.01, 2)
  146. else:
  147. status = Const.DEV_WORK_STATUS_IDLE
  148. item['status'] = status
  149. portInfo[str(i + 1)] = item.copy()
  150. return {'temperature': temperature, 'portInfo': portInfo}
  151. @staticmethod
  152. def _parse_D9_data(data):
  153. '''
  154. 充电开始 上传
  155. :param data:
  156. :type data:
  157. :return:
  158. :rtype:
  159. '''
  160. power = ChangyuanFive.decode_str(data[20:24])
  161. coins = ChangyuanFive.decode_str(data[24:28], ratio=0.01)
  162. port = ChangyuanFive.decode_str(data[28:30])
  163. payType = data[30:32]
  164. if payType == '00': # 次卡
  165. cardNo = data[6:14]
  166. cardType = "00"
  167. cardBalance = ChangyuanFive.decode_str(data[14:20])
  168. needTime = ChangyuanFive.decode_str(data[32:36])
  169. elif payType == '01': # 扫码
  170. needTime = ChangyuanFive.decode_str(data[32:36])
  171. elif payType == '02': # 投币
  172. needTime = ChangyuanFive.decode_str(data[32:36])
  173. elif payType == '03': # 预付卡
  174. cardNo = data[6:14]
  175. cardType = "01"
  176. cardBalance = ChangyuanFive.decode_str(data[14:20], ratio=0.01)
  177. leftMoney = ChangyuanFive.decode_str(data[32:36], ratio=0.01)
  178. elif payType == '04':
  179. cardNo = data[6:14]
  180. cardType = "0A"
  181. cardBalance = ChangyuanFive.decode_str(data[14:20], ratio=0.01)
  182. needTime = ChangyuanFive.decode_str(data[32:36])
  183. else:
  184. pass
  185. return locals()
  186. @staticmethod
  187. def _parse_DA_data(data):
  188. '''
  189. 充电结束上传
  190. :param data:
  191. :type data:
  192. :return:
  193. :rtype:
  194. '''
  195. port = ChangyuanFive.decode_str(data[6:8])
  196. reasonCode = data[8:10]
  197. usedElec = ChangyuanFive.decode_str(data[10:14], ratio=0.001)
  198. left = ChangyuanFive.decode_str(data[14:18])
  199. # 在线卡版本设备充满自停和插座掉落分离开
  200. # if ChangyuanFive.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_CHANGYUAN_FIVE_ONLINECARD:
  201. # if reasonCode == '01':
  202. # reasonCode = '10'
  203. reason = ChangyuanFive.FINISH_REASON_MAP.get(reasonCode, u'未知停止方式')
  204. return locals()
  205. @staticmethod
  206. def _parse_DE_data(data):
  207. '''
  208. 实体卡返费 的指令
  209. :param data:
  210. :type data:
  211. :return:
  212. :rtype:
  213. '''
  214. cardNo = data[6:14]
  215. beforeRefund = int(data[14:20], 16) / 100.0
  216. refund = int(data[20:24], 16) / 100.0
  217. afterRefund = int(data[24:30], 16) / 100.0
  218. return locals()
  219. @staticmethod
  220. def _parse_F0_data(data):
  221. cardType = data[6:8]
  222. cardNo = data[8:16]
  223. port = data[16:18]
  224. return locals()
  225. @staticmethod
  226. def _parse_COMMON_data(data):
  227. '''
  228. 解析 通用的数据返回 一般表示成功还是失败
  229. :param data:
  230. :type data:
  231. :return:
  232. :rtype:
  233. '''
  234. return True if data[6:10] == '4F4B' else False
  235. def _send_data(self, funCode, data, cmd=DeviceCmdCode.OPERATE_DEV_SYNC, timeout=MQTT_TIMEOUT.NORMAL):
  236. result = MessageSender.send(device=self.device, cmd=cmd, payload={
  237. 'IMEI': self.device.devNo,
  238. 'funCode': funCode,
  239. 'data': data
  240. }, timeout=timeout)
  241. if result['rst'] != 0:
  242. if result['rst'] == -1:
  243. raise ServiceException({'result': 2, 'description': u'充电桩正在玩命找网络,请稍候再试'})
  244. elif result['rst'] == 1:
  245. raise ServiceException({'result': 2, 'description': u'充电桩主板连接故障'})
  246. else:
  247. raise ServiceException({'result': 2, 'description': u'系统错误'})
  248. return result
  249. def _start(self, payMoney, port, time='0000', discount='0'):
  250. '''
  251. 启动设备
  252. :param payMoney:
  253. :return:
  254. '''
  255. data = ''
  256. data += self.encode_str(payMoney, ratio=10)
  257. data += self.encode_str(time, length=4)
  258. data += self.encode_str(discount, length=1)
  259. data += self.encode_str(port, length=1)
  260. result = self._send_data('D2', data, timeout=MQTT_TIMEOUT.START_DEVICE)
  261. if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')):
  262. raise ServiceException({'result': '2', 'description': u'设备启动错误,请联系经销商协助解决(设备返回支付不成功)'})
  263. return result
  264. @staticmethod
  265. def transform_password(password):
  266. passwordHex = ''
  267. while password:
  268. passwordHex += fill_2_hexByte(hex(int(password[: 2])), 2)
  269. password = password[2:]
  270. return passwordHex
  271. def _set_password(self, password):
  272. """
  273. 设置设备的小区密码
  274. 密码是否还需要再校验,以及是否是0开头
  275. """
  276. if not password.isdigit():
  277. raise ServiceException({'result': '2', 'description': u'密码必须必须为0-9数字'})
  278. if len(password) != 10:
  279. raise ServiceException({'result': 0, 'description': u'密码长度必须为10位'})
  280. passwordHex = self.transform_password(password)
  281. result = self._send_data('DB', data=passwordHex)
  282. if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')):
  283. raise ServiceException({'result': '2', 'description': u'设置小区密码失败,请重新试试'})
  284. return result
  285. def _set_send_card(self, oldPassword, newPassword, FaKacardType):
  286. '''
  287. 设置卡机模式 用于重置 实体卡的小区密码
  288. '''
  289. if not oldPassword.isdigit() or not newPassword.isdigit():
  290. raise ServiceException({'result': '2', 'description': u'密码必须必须为0-9数字'})
  291. if len(oldPassword) != 10 or len(newPassword) != 10:
  292. raise ServiceException({'result': 0, 'description': u'密码长度必须为10位'})
  293. if not FaKacardType:
  294. raise ServiceException({'result': 0, 'description': u'请选择要发行卡的类型'})
  295. oldPasswordHex = self.transform_password(oldPassword)
  296. newPasswordHex = self.transform_password(newPassword)
  297. FaKacardType = self.encode_str(FaKacardType)
  298. result = self._send_data('DC', data=oldPasswordHex + newPasswordHex + FaKacardType)
  299. if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')):
  300. raise ServiceException({'result': '2', 'description': u'设置发卡机模式失败,请重新试试'})
  301. return result
  302. def _reboot_device(self):
  303. result = self._send_data('D5', '0000')
  304. if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')):
  305. raise ServiceException({'result': '2', 'description': u'设备复位错误,请重新试试'})
  306. return result
  307. def _async_card_balance(self, cardType, cardNo, asyncMoney):
  308. """
  309. 同步卡内余额
  310. :param cardType:
  311. :param cardNo:
  312. :param asyncMoney:
  313. :return:
  314. """
  315. # 如果是预付费的卡 同步的金额需要变换为分
  316. balance = asyncMoney if cardType == '00' else asyncMoney * 100
  317. # 获取随机流水号
  318. sidKey = '{}-{}'.format(self.device.devNo, cardNo)
  319. sid = TempValues.get(sidKey)
  320. balanceHex = self.encode_str(int(balance), length=6)
  321. sidHex = self.encode_str(sid, length=4)
  322. MessageSender.send(device=self.device, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, payload={
  323. 'IMEI': self.device.devNo,
  324. 'funCode': 'D3',
  325. 'data': cardType + balanceHex + sidHex
  326. })
  327. def _ack_finished_massage(self, daid):
  328. return MessageSender.send(device=self.device, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, payload={
  329. "IMEI": self.device.devNo,
  330. "data": "",
  331. "daid": daid,
  332. "funCode": "FA"})
  333. def _ack(self, ack_id):
  334. self._send_data(funCode='AK', data=ack_id, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  335. # 获取10条未返费的记录 暂时未使用
  336. def _no_refund_record(self):
  337. result = self._send_data('DE', '00')
  338. data = result['data']
  339. noRefund = list()
  340. data = data[6: -8]
  341. for i in xrange(0, 120, 12):
  342. tempData = data[i:i + 12]
  343. cardNo = tempData[:8]
  344. amount = tempData[8:]
  345. if cardNo == 'FFFFFFFF' or cardNo == '00000000': continue
  346. amount = int(amount, 16) / 100.0
  347. noRefund.append({'cardNo': cardNo, 'amount': amount})
  348. return noRefund
  349. def _set_all_settings(self, settings):
  350. powerTime1 = settings.get('powerTime1')
  351. powerTime2 = settings.get('powerTime2')
  352. powerTime3 = settings.get('powerTime3')
  353. powerTime4 = settings.get('powerTime4')
  354. power1 = settings.get('power1')
  355. power2 = settings.get('power2')
  356. power3 = settings.get('power3')
  357. power4 = settings.get('power4')
  358. noloadPower = settings.get('noloadPower')
  359. noloadTime = settings.get('noloadTime')
  360. volume = settings.get('volume')
  361. cardFee = settings.get('cardFee')
  362. floatPower = settings.get('floatPower')
  363. floatTime = settings.get('floatTime')
  364. cardFree = settings.get('cardFree')
  365. #
  366. powerTime1 = self.check_params_range(params=powerTime1, minData=0, maxData=999, desc='第1段功率时间')
  367. powerTime2 = self.check_params_range(params=powerTime2, minData=0, maxData=999, desc='第2段功率时间')
  368. powerTime3 = self.check_params_range(params=powerTime3, minData=0, maxData=999, desc='第3段功率时间')
  369. powerTime4 = self.check_params_range(params=powerTime4, minData=0, maxData=999, desc='第4段功率时间')
  370. power1 = self.check_params_range(params=power1, minData=0, maxData=999, desc='第1段功率值')
  371. power2 = self.check_params_range(params=power2, minData=0, maxData=999, desc='第2段功率值')
  372. power3 = self.check_params_range(params=power3, minData=0, maxData=999, desc='第3段功率值')
  373. power4 = self.check_params_range(params=power4, minData=0, maxData=999, desc='第4段功率值')
  374. noloadPower = self.check_params_range(params=noloadPower, minData=0, maxData=50, desc='空载检测功率')
  375. noloadTime = self.check_params_range(params=noloadTime, minData=0, maxData=255, desc='空载功率检测时间')
  376. volume = self.check_params_range(params=volume, minData=0, maxData=8, desc='音量')
  377. cardFee = self.check_params_range(params=cardFee, minData=0, maxData=655, desc='预付卡预扣金额')
  378. floatPower = self.check_params_range(params=floatPower, minData=0, maxData=50, desc='浮动功率')
  379. floatTime = self.check_params_range(params=floatTime, minData=0, maxData=60000, desc='浮动功率延长时间')
  380. data = ''
  381. data += self.encode_str(powerTime1, length=4)
  382. data += self.encode_str(powerTime2, length=4)
  383. data += self.encode_str(powerTime3, length=4)
  384. data += self.encode_str(powerTime4, length=4)
  385. data += self.encode_str(power1, length=4)
  386. data += self.encode_str(power2, length=4)
  387. data += self.encode_str(power3, length=4)
  388. data += self.encode_str(power4, length=4)
  389. data += self.encode_str(noloadPower, length=2)
  390. data += self.encode_str(noloadTime, length=2)
  391. data += self.encode_str(volume, length=2)
  392. data += self.encode_str(cardFee, length=4, ratio=100)
  393. data += self.encode_str(floatPower, length=2)
  394. data += self.encode_str(floatTime, length=4)
  395. data += cardFree
  396. result = self._send_data('D1', data)
  397. if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')):
  398. raise ServiceException({'result': '2', 'description': u'设置设备参数错误,请联系经销商协助解决'})
  399. return result
  400. def _set_elec_price(self, elecPrice):
  401. """
  402. 设置电量单价 这个参数是模块侧的 只下发到模块驱动 不到主板
  403. :param elecPrice:
  404. :return:
  405. """
  406. elecPriceHex = "{:0>8X}".format(int(3600 * 1000 * float(elecPrice)))
  407. self._send_data("EP", elecPriceHex)
  408. # 更新参数设置
  409. Device.get_collection().update_one({'devNo': self.device['devNo']}, {'$set': {'otherConf.elecPrice': elecPrice}})
  410. Device.invalid_device_cache(self.device.devNo)
  411. def _get_dev_port_info(self):
  412. result = self._send_data('E1', '0000')
  413. data = result['data']
  414. return ChangyuanFive._parse_D8_data(data)
  415. @staticmethod
  416. def check_params_range(params, minData=None, maxData=None, desc=''):
  417. # type:(str,float,float,str) -> str
  418. '''
  419. 检查参数,返回字符串参数
  420. '''
  421. if params is None:
  422. raise ServiceException({'result': 2, 'description': u'参数错误.'})
  423. if not isinstance(params, Decimal):
  424. params = Decimal(params)
  425. if not minData and maxData:
  426. if not isinstance(maxData, Decimal):
  427. maxData = Decimal(maxData)
  428. if params <= maxData:
  429. return '%g' % params
  430. else:
  431. raise ServiceException({'result': 2, 'description': u'%s超出可选范围,可选最大值为%g' % (desc, maxData)})
  432. if not maxData and minData:
  433. if not isinstance(minData, Decimal):
  434. minData = Decimal(minData)
  435. if minData <= params:
  436. return '%g' % params
  437. else:
  438. raise ServiceException({'result': 2, 'description': u'%s超出可选范围,可选最小值为%g' % (desc, minData)})
  439. if not minData and not maxData:
  440. return '%g' % params
  441. else:
  442. if not isinstance(minData, Decimal):
  443. minData = Decimal(minData)
  444. if not isinstance(maxData, Decimal):
  445. maxData = Decimal(maxData)
  446. if minData <= params <= maxData:
  447. return '%g' % params
  448. else:
  449. raise ServiceException(
  450. {'result': 2, 'description': u'%s参数超出可选范围,可取范围为%g-%g' % (desc, minData, maxData)})
  451. @staticmethod
  452. def reverse_hex(data):
  453. # type:(str) -> str
  454. if not isinstance(data, str):
  455. raise TypeError
  456. return "".join(list(reversed(re.findall(r".{2}", data))))
  457. @staticmethod
  458. def encode_str(data, length=2, ratio=1.0, base=16):
  459. # type:(any,int,float,int) -> str
  460. if not isinstance(data, Decimal):
  461. data = Decimal(data)
  462. if not isinstance(length, str):
  463. length = str(length)
  464. if not isinstance(ratio, Decimal):
  465. ratio = Decimal(ratio)
  466. end = 'X' if base == 16 else 'd'
  467. encodeStr = '%.' + length + end
  468. encodeStr = encodeStr % (data * ratio)
  469. return encodeStr
  470. @staticmethod
  471. def decode_str(data, ratio=1.0, base=16, reverse=False, to_int=True):
  472. '''
  473. ratio:比率单位转换
  474. '''
  475. if not isinstance(data, str):
  476. data = str(data)
  477. if reverse:
  478. data = "".join(list(reversed(re.findall(r".{2}", data))))
  479. if to_int:
  480. result = int(data, base) * ratio
  481. return int(result) if result == int(result) else round(result, 2)
  482. else:
  483. return '%.10g' % (int(data, base) * ratio)
  484. @staticmethod
  485. def decode_long_hex_to_list(data, split=2, ratio=1.0, base=16, transformer=True):
  486. # type:(str,int,float,int,bool) -> list
  487. '''
  488. return: list
  489. '''
  490. if len(data) % split != 0:
  491. raise Exception('Invalid data')
  492. pattern = r'.{%s}' % split
  493. hex_list = re.findall(pattern, data)
  494. if transformer:
  495. hex_list = map(lambda x: ChangyuanFive.decode_str(x, ratio=ratio, base=base), hex_list)
  496. return hex_list
  497. def test(self, coins):
  498. """
  499. 测试端口 测试机器启动 固定端口为 01
  500. {
  501. 'IMEI': '865650040606119',
  502. 'cmd': 210,
  503. 'data': '0303E8000001',
  504. 'funCode': 'D2'
  505. },
  506. """
  507. return self._start('coin', coins, 01)
  508. def get_dev_setting(self):
  509. """
  510. 获取设备参数
  511. :return:
  512. """
  513. result = self._send_data('D0', '00')
  514. data = result["data"]
  515. devSetting = self._parse_D0_data(data)
  516. disable = self.device.get('otherConf', {}).get('disableDevice')
  517. elecPrice = self.device.get("otherConf", {}).get("elecPrice", 0)
  518. needBindCard = self.device.get("otherConf", {}).get("needBindCard", True)
  519. vCardTime = self.device.get("otherConf", {}).get("vCardTime", 300)
  520. discount = self.device.get("otherConf", {}).get("discount", self.DEFAULT_DISCOUNT)
  521. onlineCardFee = self.device.get("otherConf", {}).get("onlineCardFee",0)
  522. maxTime = self.device.get("otherConf", {}).get("maxTime",720)
  523. devSetting.update(
  524. {'disable': disable, 'elecPrice': elecPrice, 'needBindCard': needBindCard, 'vCardTime': vCardTime, 'discount': discount,'onlineCardFee':onlineCardFee,'maxTime':maxTime})
  525. dev_control_cache = Device.get_dev_control_cache(self.device.devNo)
  526. devSetting['temperature'] = dev_control_cache.get('temperature', '获取中...')
  527. if self.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_CHANGYUAN_FIVE_ONLINECARD:
  528. if devSetting['temperature'] == '获取中...':
  529. devSetting['temperature'] = self._get_dev_port_info().get('temperature')
  530. devSetting['voltage'] = dev_control_cache.get('voltage', '获取中...')
  531. return devSetting
  532. def set_device_function_param(self, request, lastSetConf):
  533. """设备设备的参数"""
  534. if "pwd" in request.POST:
  535. return self._set_password(request.POST["pwd"])
  536. if "old_pwd" in request.POST:
  537. old_pwd = request.POST["old_pwd"]
  538. new_pwd = request.POST["new_pwd"]
  539. cardType = request.POST["FaKacardType"]
  540. return self._set_send_card(old_pwd, new_pwd, cardType)
  541. # 卡券启动时间
  542. if 'vCardTime' in request.POST:
  543. vCardTime = int(request.POST.get('vCardTime'))
  544. if vCardTime != int(self.device['otherConf'].get('vCardTime', 300)):
  545. Device.get_collection().update_one({'devNo': self.device['devNo']}, {'$set': {'otherConf.vCardTime': vCardTime}})
  546. Device.invalid_device_cache(self.device.devNo)
  547. # 防止盗电的功能参数
  548. elecPrice = request.POST.get("elecPrice", 0)
  549. self._set_elec_price(elecPrice)
  550. # 折扣参数设置
  551. discount = request.POST.get("discount", 0)
  552. self._set_discount(discount)
  553. # 对一下参数的设置
  554. new_settings = dict()
  555. for _paramKey, _paramValue in lastSetConf.items():
  556. if request.POST.get(_paramKey) is not None:
  557. _paramValue = request.POST[_paramKey]
  558. new_settings[_paramKey] = _paramValue
  559. cardFree = "AA" if new_settings["cardFree"] else "00"
  560. new_settings.update({"cardFree": cardFree})
  561. self._set_all_settings(new_settings)
  562. maxTime = request.POST.get("maxTime")
  563. if maxTime is not None:
  564. self.set_max_time(int(maxTime))
  565. if "onlineCardFee" in request.POST:
  566. onlineCardFee = request.POST['onlineCardFee']
  567. self.device.update_device_obj(**{
  568. 'otherConf.onlineCardFee': onlineCardFee})
  569. def set_device_function(self, request, lastSetConf):
  570. """开关类参数配置"""
  571. if "disable" in request.POST:
  572. return self.set_dev_disable(request.POST.get('disable'))
  573. if "reboot" in request.POST:
  574. return self._reboot_device()
  575. if "cardFree" in request.POST:
  576. cardFree = "AA" if request.POST["cardFree"] else "00"
  577. lastSetConf.update({"cardFree": cardFree})
  578. return self._set_all_settings(lastSetConf)
  579. if "needBindCard" in request.POST:
  580. needBindCard = request.POST.get('needBindCard')
  581. otherConf = self.device.get("otherConf", dict())
  582. otherConf.update({"needBindCard": needBindCard})
  583. Device.objects.get(devNo=self.device.devNo).update(otherConf=otherConf)
  584. Device.invalid_device_cache(self.device.devNo)
  585. def get_port_status(self, force=False):
  586. '''
  587. 获取设备状态 昌原的状态都是被动获取的
  588. :param force:
  589. :return:
  590. '''
  591. statusDict = dict()
  592. if self.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_CHANGYUAN_FIVE_ONLINECARD:
  593. portsInfo = self._get_dev_port_info().get('portInfo',{})
  594. allPorts = len(portsInfo)
  595. for portNum in range(allPorts):
  596. tempDict = portsInfo.get(str(portNum + 1), {})
  597. if 'status' in tempDict:
  598. statusDict[str(portNum + 1)] = {'status': tempDict.get('status')}
  599. elif 'isStart' in tempDict:
  600. if tempDict['isStart']:
  601. statusDict[str(portNum + 1)] = {'status': Const.DEV_WORK_STATUS_WORKING}
  602. else:
  603. statusDict[str(portNum + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE}
  604. else:
  605. statusDict[str(portNum + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE}
  606. allPorts, usedPorts, usePorts = self.get_port_static_info(statusDict)
  607. Device.update_dev_control_cache(self._device['devNo'],
  608. {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts})
  609. else:
  610. devCache = Device.get_dev_control_cache(self._device['devNo'])
  611. allPorts = devCache.get('allPorts', 10)
  612. for portNum in range(allPorts):
  613. tempDict = devCache.get(str(portNum + 1), {})
  614. if 'status' in tempDict:
  615. statusDict[str(portNum + 1)] = {'status': tempDict.get('status')}
  616. elif 'isStart' in tempDict:
  617. if tempDict['isStart']:
  618. statusDict[str(portNum + 1)] = {'status': Const.DEV_WORK_STATUS_WORKING}
  619. else:
  620. statusDict[str(portNum + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE}
  621. else:
  622. statusDict[str(portNum + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE}
  623. allPorts, usedPorts, usePorts = self.get_port_static_info(statusDict)
  624. Device.update_dev_control_cache(self._device['devNo'],
  625. {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts})
  626. return statusDict
  627. def get_port_status_from_dev(self):
  628. return self.get_port_info(False)
  629. def get_port_info(self, port):
  630. '''
  631. 获取 端口的详细信息
  632. :param port:
  633. :return:
  634. '''
  635. # 昌源五代机新增在线卡功能,并修改了部分协议
  636. devCache = Device.get_dev_control_cache(self.device.devNo)
  637. if self.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_CHANGYUAN_FIVE_ONLINECARD:
  638. portsInfo = self._get_dev_port_info()
  639. portInfo = portsInfo.get('portInfo',{}).get(port)
  640. if portInfo:
  641. portCache = devCache.get(port)
  642. startTime = portCache.get('startTime')
  643. needTime = portCache.get('needTime')
  644. leftTime = portInfo.pop('leftTime')
  645. usedTime = needTime - leftTime
  646. portInfo.update({"startTime": startTime, "usedTime": usedTime})
  647. temperature = portsInfo.get('temperature')
  648. devCache['temperature'] = temperature
  649. Device.update_dev_control_cache(self.device.devNo, devCache)
  650. return portInfo
  651. return devCache.get(str(port), dict())
  652. @property
  653. def isHaveStopEvent(self):
  654. return True
  655. def set_dev_disable(self, disable):
  656. if disable:
  657. status = '00AA'
  658. else:
  659. status = '0055'
  660. result = self._send_data(funCode='DD', data=status)
  661. if 'data' not in result or not ChangyuanFive._parse_COMMON_data(result.get('data')):
  662. raise ServiceException({'result': '2', 'description': u'设备停用错误,请联系厂商协助解决'})
  663. otherConf = self._device.get('otherConf', {})
  664. otherConf['disableDevice'] = disable
  665. Device.objects.filter(devNo=self._device['devNo']).update(otherConf=otherConf)
  666. Device.invalid_device_cache(self._device['devNo'])
  667. def stop(self, port=None):
  668. if not port:
  669. raise ServiceException({"result": "2", "description": u"请选择停止端口!"})
  670. self.stop_charging_port(port)
  671. def stop_charging_port(self, port):
  672. portHex = fill_2_hexByte(hex(int(port)), 2)
  673. result = self._send_data('D6', data=portHex)
  674. data = result.get('data')
  675. if data[6: 8] != '4F':
  676. raise ServiceException({'result': 2, 'description': u'停止充电失败,请重新试试'})
  677. # 这里只下发命令 不清理端口缓存,等上报事件清除
  678. # Device.clear_port_control_cache(self.device.devNo,int(port))
  679. def check_dev_status(self, attachParas = None):
  680. pass
  681. def start_device(self, package, openId, attachParas):
  682. chargeIndex = attachParas.get('chargeIndex')
  683. if not chargeIndex:
  684. raise ServiceException({'result': 2, 'description': u'请选择正确的充电端口'})
  685. coins = round(package.get('coins'), 2)
  686. price = round(package.get('price'), 2)
  687. rechargeRcdId = attachParas.get('linkedRechargeRecordId')
  688. orderNo = attachParas.get('orderNo')
  689. if self._vcard_id:
  690. vCardTime = self._device.get("otherConf", dict()).get("vCardTime", 300)
  691. result = self._start(payMoney='0', port=chargeIndex, time=vCardTime)
  692. result['finishedTime'] = int(time.time()) + vCardTime * 60 + 300
  693. else:
  694. discount = self.get_discount(rechargeRcdId)
  695. result = self._start(payMoney=coins, port=chargeIndex, discount=discount)
  696. result['finishedTime'] = int(time.time()) + 12 * 60 * 60
  697. lineInfo = Device.get_dev_control_cache(self.device.devNo).get(chargeIndex, {})
  698. is_continue = lineInfo.get('openId') == openId and lineInfo.get('status', Const.DEV_WORK_STATUS_IDLE) == Const.DEV_WORK_STATUS_WORKING
  699. portDict = {
  700. 'port': chargeIndex,
  701. 'openId': openId,
  702. 'isStart': True,
  703. 'coins': coins,
  704. 'price': price,
  705. 'orderNo': orderNo,
  706. "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  707. 'status': Const.DEV_WORK_STATUS_WORKING,
  708. 'consumeType': 'mobile', # 用来显示的 显示在端口管理里面
  709. 'payType': '01',
  710. }
  711. if self._vcard_id:
  712. portDict['vCardId'] = self._vcard_id
  713. if rechargeRcdId:
  714. item = {
  715. 'rechargeRcdId': rechargeRcdId
  716. }
  717. if is_continue:
  718. pay_info = lineInfo.get('payInfo', list())
  719. else:
  720. pay_info = list()
  721. pay_info.append(item)
  722. portDict['payInfo'] = pay_info
  723. if is_continue:
  724. portDict['startTime'] = lineInfo.get('startTime', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
  725. if 'coins' in lineInfo:
  726. portDict['coins'] = coins + lineInfo['coins']
  727. if 'price' in lineInfo:
  728. portDict['price'] = price + lineInfo['price']
  729. Device.update_port_control_cache(self.device.devNo, portDict)
  730. return result
  731. def analyze_event_data(self, data):
  732. '''
  733. 解析事件
  734. :param data:
  735. :return:
  736. '''
  737. cmdCode = data[2:4]
  738. if cmdCode == 'D3':
  739. eventData = ChangyuanFive._parse_D3_data(data)
  740. elif cmdCode == 'D4':
  741. eventData = ChangyuanFive._parse_D4_data(data)
  742. elif cmdCode == 'D7':
  743. eventData = ChangyuanFive._parse_D7_data(data)
  744. elif cmdCode == 'D8':
  745. eventData = ChangyuanFive._parse_D8_data(data)
  746. elif cmdCode == 'D9':
  747. eventData = ChangyuanFive._parse_D9_data(data)
  748. elif cmdCode == 'DA':
  749. eventData = ChangyuanFive._parse_DA_data(data)
  750. if eventData['reasonCode']== "01":
  751. # 在线卡版本设备充满自停和插座掉落分离开
  752. if self.device.devTypeCode == Const.DEVICE_TYPE_CODE_CHARGING_CHANGYUAN_FIVE_ONLINECARD:
  753. reason = ChangyuanFive.FINISH_REASON_MAP.get("10", u'未知停止方式')
  754. # eventData['reasonCode'] == "10"
  755. # eventData['reason'] == reason
  756. eventData.update({'reasonCode' : "10"})
  757. eventData.update({'reason': reason})
  758. elif cmdCode == 'DE':
  759. eventData = ChangyuanFive._parse_DE_data(data)
  760. elif cmdCode == 'F0':
  761. eventData = ChangyuanFive._parse_F0_data(data)
  762. else:
  763. logger.error('error cmdCode <{}>, data is <{}>'.format(cmdCode, data))
  764. return
  765. eventData.update({'cmdCode': cmdCode})
  766. return eventData
  767. def get_device_function_by_key(self, data):
  768. if data == 'noRefund':
  769. res = self._no_refund_record()
  770. if not res:
  771. return {}
  772. return {'noRefund': res}
  773. def active_deactive_port(self, port, active):
  774. if not active:
  775. self.stop_charging_port(port)
  776. else:
  777. raise ServiceException({'result': 2, 'description': u'此设备不支持直接打开端口'})
  778. def _check_package(self, package):
  779. """
  780. 获取设备启动的发送数据 根据设备的当前模式以及套餐获取
  781. :param package:
  782. :return:
  783. """
  784. unit = package.get("unit", u"分钟")
  785. _time = float(package.get("time", 0))
  786. # 按时间计费
  787. if unit == u"小时":
  788. billingType = "time"
  789. _time = _time * 60
  790. elif unit == u"天":
  791. billingType = "time"
  792. _time = _time * 24 * 60
  793. elif unit == u"秒":
  794. billingType = "time"
  795. _time = _time / 60
  796. elif unit == u"分钟":
  797. billingType = "time"
  798. _time = _time
  799. else:
  800. billingType = "elec"
  801. _time = _time
  802. return _time, unit, billingType
  803. def get_discount(self, rechargeRcdId):
  804. """
  805. 根据启动方式的不通获取不同的折扣 金币启动的不打折 其余的根据设备的折扣而定
  806. """
  807. if rechargeRcdId:
  808. return self._device.get("otherConf", dict()).get("discount", self.DEFAULT_DISCOUNT)
  809. else:
  810. return self.DEFAULT_DISCOUNT
  811. def set_max_time(self, maxTime):
  812. data = "{:04X}".format(maxTime)
  813. try:
  814. self._send_data("F8", data, timeout=5)
  815. self.device.update_device_obj(**{
  816. 'otherConf.maxTime': maxTime})
  817. except ServiceException as e:
  818. raise ServiceException({"result": 2, "description": u"该设备暂不支持充电时长,请联系厂家进行升级"})
  819. def _set_discount(self, discount):
  820. Device.get_collection().update_one({'devNo': self.device['devNo']},
  821. {'$set': {'otherConf.discount': discount}})
  822. Device.invalid_device_cache(self.device.devNo)
  823. def _response_F0(self, cardType, cardNo, port,balance):
  824. cardType = str(cardType)
  825. cardNo = str(cardNo)
  826. port = str(port)
  827. oldBalance = self.encode_str(float(balance) * 100,6)
  828. onlineCardFee = 3 if self.device.otherConf.get('onlineCardFee',0) == 0 else self.device.otherConf.get('onlineCardFee')
  829. if RMB(balance) < RMB(onlineCardFee):
  830. money = self.encode_str(float(balance) * 100,4)
  831. else:
  832. onlineCardFee = float(onlineCardFee) * 100
  833. money = self.encode_str(onlineCardFee,4)
  834. data = cardType+cardNo+port+money+oldBalance
  835. result = self._send_data('F1',data)
  836. if result['data'][6:10] != '4F4B':
  837. raise ServiceException({'result': 2, 'description': u'支付失败'})