yunchong.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import random
  6. import time
  7. from typing import TYPE_CHECKING
  8. from apilib.utils_datetime import timestamp_to_dt
  9. from apilib.utils_string import cn
  10. from apps.web.constant import DeviceCmdCode, Const, ErrorCode, MQTT_TIMEOUT, DeviceErrorCodeDesc
  11. from apps.web.core.adapter.base import SmartBox, fill_2_hexByte, start_error_timer
  12. from apps.web.core.exceptions import ServiceException
  13. from apps.web.core.networking import MessageSender
  14. from apps.web.device.models import Device, GroupDict
  15. if TYPE_CHECKING:
  16. pass
  17. logger = logging.getLogger(__name__)
  18. class ChargingYUNCHONGBox(SmartBox):
  19. """
  20. 协议格式和电川一致, 功能少很多
  21. """
  22. def __init__(self, device):
  23. super(ChargingYUNCHONGBox, self).__init__(device)
  24. def _check_package(self, package):
  25. """
  26. 获取设备启动的发送数据 根据设备的当前模式以及套餐获取
  27. :param package:
  28. :return:
  29. """
  30. unit = package.get('unit', u'分钟')
  31. needTime = int(package['time'])
  32. if unit in [u'分钟', u'小时', u'天', u'秒']:
  33. billingType = 'time'
  34. if unit == u'小时':
  35. needTime = int(package['time']) * 60
  36. elif unit == u'天':
  37. needTime = int(package['time']) * 1440
  38. elif unit == u'秒':
  39. needTime = int(package['time']) / 60
  40. hexTime = fill_2_hexByte(hex(needTime), 4)
  41. else:
  42. raise ServiceException(
  43. {
  44. 'result': 2,
  45. 'description': u'充电桩只支持按时间计费的方式,不支持其他方式'
  46. }
  47. )
  48. return needTime, unit, billingType
  49. def translate_funcode(self, funCode):
  50. funCodeDict = {
  51. '01': u'获取端口数量',
  52. '02': u'用户付款',
  53. '06': u'获取端口状态',
  54. '07': u'查询消费总额数据',
  55. '08': u'IC卡、投币、最大功率设置',
  56. '09': u'设置IC卡、投币器是否可用',
  57. '0A': u'锁定、解锁某一个端口',
  58. '0B': u'远程停止某个端口的充电',
  59. '0C': u'读取设备IC卡、投币、最大功率设置',
  60. '0D': u'上传设备故障',
  61. '14': u'设置充电站5档计费功率、比例',
  62. '15': u'读取设备5档计费功率、比例'
  63. }
  64. return funCodeDict.get(funCode, '')
  65. def translate_event_cmdcode(self, cmdCode):
  66. cmdDict = {
  67. '03': u'投币上报',
  68. '04': u'刷卡上报',
  69. '25': u'功率上报',
  70. }
  71. return cmdDict.get(cmdCode, '')
  72. def check_dev_status(self, attachParas=None):
  73. """
  74. 如果超过两个心跳周期没有报心跳,并且最后一次更新时间在2个小时内,需要从设备获取状态
  75. 否则以缓存状态为准。
  76. :param attachParas:
  77. :return:
  78. """
  79. if attachParas is None:
  80. raise ServiceException({'result': 0, 'description': u'请您选择合适的充电端口、电池类型信息'})
  81. if not attachParas.has_key('chargeIndex'):
  82. raise ServiceException({'result': 0, 'description': u'请您选择合适的充电端口'})
  83. if not self.device.need_fetch_online:
  84. raise ServiceException(
  85. {'result': 2, 'description': DeviceErrorCodeDesc.get(ErrorCode.DEVICE_CONN_CHECK_FAIL)})
  86. self.get_port_status_from_dev()
  87. group = self.device.group # type: GroupDict
  88. if group.is_free:
  89. logger.debug('{} is free. no need to check continue pay.'.format(repr(self.device)))
  90. return
  91. # 处理是否能够续充
  92. portDict = self.get_port_status()
  93. port = str(attachParas['chargeIndex'])
  94. if port in portDict:
  95. isCanAdd = self.device['devType'].get('payableWhileBusy', False)
  96. if portDict[port]['status'] == Const.DEV_WORK_STATUS_IDLE:
  97. return
  98. elif portDict[port]['status'] == Const.DEV_WORK_STATUS_FAULT:
  99. raise ServiceException({'result': 0, 'description': u'该端口故障,暂时不能使用'})
  100. elif portDict[port]['status'] == Const.DEV_WORK_STATUS_WORKING:
  101. if isCanAdd:
  102. return
  103. else:
  104. raise ServiceException({'result': 0, 'description': u'该端口正在工作不能使用,请您使用其他端口'})
  105. elif portDict[port]['status'] == Const.DEV_WORK_STATUS_FORBIDDEN:
  106. raise ServiceException({'result': 0, 'description': u'该端口已被禁止使用,请您使用其他端口'})
  107. elif portDict[port]['status'] == Const.DEV_WORK_STATUS_CONNECTED:
  108. return
  109. else:
  110. raise ServiceException({'result': 0, 'description': u'端口状态未知,暂时不能使用'})
  111. else:
  112. raise ServiceException({'result': 0, 'description': u'未知端口,暂时不能使用'})
  113. def test(self, coins):
  114. hexPort = fill_2_hexByte(hex(1), 2)
  115. hexGear = '00'
  116. hexTime = '01'
  117. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  118. {'IMEI': self._device['devNo'], "funCode": '02',
  119. 'data': hexPort + hexGear + hexTime})
  120. return devInfo
  121. def port_is_busy(self, port_dict):
  122. if not port_dict:
  123. return False
  124. if 'billingType' not in port_dict:
  125. return False
  126. if 'status' not in port_dict:
  127. return False
  128. if 'coins' not in port_dict:
  129. return False
  130. if 'price' not in port_dict:
  131. return False
  132. if port_dict['billingType'] not in ['time']:
  133. return False
  134. if port_dict['billingType'] == 'time':
  135. if 'needTime' not in port_dict:
  136. return False
  137. else:
  138. return False
  139. if port_dict['status'] == Const.DEV_WORK_STATUS_WORKING:
  140. return True
  141. else:
  142. return False
  143. @start_error_timer(missMessages=[u"请您选择合适的充电线路、电池类型信息", u"请您选择合适的充电线路", u"该端口正在使用中"])
  144. def start_device(self, package, openId, attachParas):
  145. if attachParas is None:
  146. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路、电池类型信息'})
  147. if 'chargeIndex' not in attachParas:
  148. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'})
  149. price = float(package['price'])
  150. coins = float(package['coins'])
  151. port = hex(int(attachParas['chargeIndex']))
  152. hexPort = fill_2_hexByte(port, 2)
  153. gear = hex(int(attachParas['powerGear']))
  154. hexGear = fill_2_hexByte(gear, 4)
  155. unit = package.get('unit', u'分钟')
  156. needTime = int(package['time'])
  157. if unit in [u'分钟', u'小时', u'天', u'秒']:
  158. billingType = 'time'
  159. if unit == u'小时':
  160. needTime = int(package['time']) * 60
  161. elif unit == u'天':
  162. needTime = int(package['time']) * 1440
  163. elif unit == u'秒':
  164. needTime = int(package['time']) / 60
  165. hexTime = fill_2_hexByte(hex(needTime), 4)
  166. else:
  167. raise ServiceException(
  168. {
  169. 'result': 2,
  170. 'description': u'充电桩只支持按时间计费的方式,不支持其他方式'
  171. }
  172. )
  173. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC, {
  174. 'IMEI': self._device['devNo'],
  175. "funCode": '02',
  176. 'data': hexPort + hexGear + hexTime
  177. }, timeout=MQTT_TIMEOUT.START_DEVICE)
  178. if devInfo['rst'] != 0:
  179. if devInfo['rst'] == -1:
  180. raise ServiceException(
  181. {
  182. 'result': 2,
  183. 'description': u'充电桩正在玩命找网络,您的金币还在,重试不需要重新付款,建议您试试旁边其他设备,或者稍后再试哦'
  184. })
  185. elif devInfo['rst'] == 1:
  186. self.check_serial_port_for_startcmd(attachParas['chargeIndex'])
  187. else:
  188. raise ServiceException(
  189. {
  190. 'result': 2,
  191. 'description': u'充电失败,您的金币还在,重试不需要重新付款,建议您试试旁边其他设备,或者稍后再试哦'
  192. })
  193. data = devInfo['data'][18::]
  194. usePort = int(attachParas['chargeIndex'])
  195. result = data[2:4]
  196. if result == '01': # 成功
  197. pass
  198. elif result == '02':
  199. newValue = {str(usePort): {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'充电站故障'}}
  200. Device.update_dev_control_cache(self._device['devNo'], newValue)
  201. raise ServiceException({'result': 2, 'description': u'充电站故障'})
  202. elif result == '03':
  203. newValue = {str(usePort): {'status': Const.DEV_WORK_STATUS_WORKING, 'statusInfo': u''}}
  204. Device.update_dev_control_cache(self._device['devNo'], newValue)
  205. raise ServiceException({'result': 2, 'description': u'该端口正在使用中'})
  206. start_timestamp = int(time.time())
  207. portDict = {
  208. 'startTime': timestamp_to_dt(start_timestamp).strftime('%Y-%m-%d %H:%M:%S'),
  209. 'status': Const.DEV_WORK_STATUS_WORKING,
  210. 'coins': float(coins),
  211. 'price': price,
  212. 'billingType': billingType,
  213. 'isStart': True,
  214. 'openId': openId,
  215. 'refunded': False,
  216. 'vCardId': self._vcard_id,
  217. 'payInfo': list()
  218. } # type: dict
  219. ctrInfo = Device.get_dev_control_cache(self.device.devNo)
  220. lastPortInfo = ctrInfo.get(str(usePort), {})
  221. is_continus = False
  222. if self.port_is_busy(lastPortInfo):
  223. if 'cardId' in lastPortInfo and lastPortInfo['cardId']:
  224. is_continus = False
  225. else:
  226. if lastPortInfo.get('billingType') == billingType and lastPortInfo.get('openId') == openId:
  227. is_continus = True
  228. else:
  229. is_continus = False
  230. if is_continus:
  231. portDict['coins'] = float(coins) + lastPortInfo['coins']
  232. portDict['price'] = float(price) + lastPortInfo['price']
  233. portDict['needTime'] = needTime + lastPortInfo['needTime']
  234. else:
  235. portDict['coins'] = float(coins)
  236. portDict['price'] = float(price)
  237. portDict.update({'needTime': needTime})
  238. if attachParas.get('redpackId'):
  239. if is_continus:
  240. redpackInfo = lastPortInfo.get("redpackInfo", list())
  241. if not redpackInfo:
  242. logger.warning("miss redpackInfo! {}-{}".format(self._device["devNo"], usePort))
  243. else:
  244. redpackInfo = list()
  245. redpackInfo.append({'redpackId': str(attachParas['redpackId'])})
  246. portDict['redpackInfo'] = redpackInfo
  247. else:
  248. if is_continus:
  249. portDict['redpackInfo'] = lastPortInfo.get("redpackInfo", list())
  250. else:
  251. portDict['redpackInfo'] = list()
  252. if 'linkedRechargeRecordId' in attachParas and attachParas.get('isQuickPay', False):
  253. if is_continus:
  254. payInfo = lastPortInfo.get("payInfo", list())
  255. if not payInfo:
  256. logger.warning("miss payInfo! {}-{}".format(self._device["devNo"], usePort))
  257. else:
  258. payInfo = list()
  259. payInfo.append({'rechargeRcdId': str(attachParas['linkedRechargeRecordId'])})
  260. portDict['payInfo'] = payInfo
  261. else:
  262. if is_continus:
  263. portDict['payInfo'] = lastPortInfo.get("payInfo", list())
  264. else:
  265. portDict['payInfo'] = list()
  266. finishedTime = int(time.time()) + int(portDict['needTime'] * 60)
  267. portDict.update({'finishedTime': finishedTime})
  268. if 'orderNo' in attachParas:
  269. portDict.update({'orderNo': attachParas['orderNo']})
  270. if 'extOrderNo' in attachParas:
  271. portDict.update({'extOrderNo': attachParas['extOrderNo']})
  272. Device.overwrite_port_control_cache(self.device.devNo, str(usePort), portDict)
  273. devInfo['finishedTime'] = finishedTime
  274. return devInfo
  275. def analyze_event_data(self, data):
  276. cmdCode = data[4:6]
  277. if cmdCode == '05':
  278. port = int(data[18:20], 16)
  279. leftTime = int(data[20:24], 16)
  280. reason = data[24:26]
  281. power = int(data[26:30], 16)
  282. desc_map = {
  283. '00': u'购买的充电时间或电量用完了。',
  284. '01': u'可能是插头被拔掉,或者电瓶已经充满。系统判断为异常断电,由于电瓶车充电器种类繁多,可能存在误差。如有问题,请您及时联系商家协助解决问题并恢复充电。',
  285. '02': u'恭喜您!电池已经充满电!',
  286. '03': u'设备或端口出现问题,为了安全起见,被迫停止工作。建议您根据已经充电的时间评估是否需要到现场换到其他端口充电。',
  287. '04': u'充电器功率超过充电站的单路最大输出功率,为了安全,系统强制停止充电。',
  288. '05': u'刷卡退费结束。'
  289. }
  290. return {
  291. 'status': Const.DEV_WORK_STATUS_IDLE,
  292. 'cmdCode': cmdCode,
  293. 'port': port,
  294. 'leftTime': leftTime,
  295. 'power': power,
  296. 'reason': desc_map[reason],
  297. 'reasonCode': reason
  298. }
  299. elif cmdCode == '0D':
  300. port = int(data[16:18], 16)
  301. errCode = data[18:20]
  302. if errCode == '01':
  303. return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'端口输出故障', 'cmdCode': cmdCode,
  304. 'port': port, 'FaultCode': errCode}
  305. elif errCode == '02':
  306. return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'机器整体充电功率过大', 'cmdCode': cmdCode,
  307. 'port': port, 'FaultCode': errCode}
  308. elif errCode == '03':
  309. return {'status': Const.DEV_WORK_STATUS_FAULT, 'statusInfo': u'电源故障', 'cmdCode': cmdCode, 'port': port,
  310. 'FaultCode': errCode}
  311. elif cmdCode == '28': # 用户刷卡上报的信息
  312. cardNo = data[18:26]
  313. cardNo = str(int(cardNo, 16))
  314. preFee = int(data[26:28], 16) / 10.0
  315. port = int(data[28:30], 16)
  316. randomCode = int(data[30:38], 16)
  317. return {'cardNo': cardNo, 'coins': preFee, 'cmdCode': cmdCode, 'port': port, 'randomCode': randomCode}
  318. elif cmdCode == '25': # 充电功率自动上报
  319. port = int(data[8:10], 16)
  320. leftTime = int(data[10:14], 16)
  321. power = int(data[14:18], 16)
  322. return {'cmdCode': cmdCode, 'port': port, 'leftTime': leftTime, 'power': power}
  323. elif cmdCode == '26': # 刷卡向模块发送卡号
  324. cardNo = data[18:26]
  325. cardNo = str(int(cardNo, 16))
  326. return {'cardNo': cardNo, 'cmdCode': cmdCode}
  327. elif cmdCode == '03': # 投币上报
  328. coin = int(data[18:20], 16)
  329. return {'cmdCode': cmdCode, 'coins': coin}
  330. elif cmdCode == '04': # 刷卡报文
  331. money = int(data[18:20], 16)
  332. return {'cmdCode': cmdCode, 'money': money}
  333. def get_dev_consume_count(self):
  334. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  335. {
  336. "funCode": '07', 'data': '00'
  337. })
  338. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  339. if devInfo['rst'] == -1:
  340. raise ServiceException(
  341. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  342. elif devInfo['rst'] == 1:
  343. raise ServiceException(
  344. {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
  345. data = devInfo['data'][16::]
  346. cardFee = int(data[2:6], 16) / 10.0 # 以角为单位
  347. coinFee = int(data[6:10], 16) # 以元为单位
  348. return {'cardFee': cardFee, 'coinFee': coinFee}
  349. def get_port_info(self, line):
  350. data = fill_2_hexByte(hex(int(line)), 2)
  351. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  352. {
  353. "funCode": '06', 'data': data
  354. })
  355. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  356. if devInfo['rst'] == -1:
  357. raise ServiceException(
  358. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  359. elif devInfo['rst'] == 1:
  360. raise ServiceException(
  361. {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
  362. data = devInfo['data'][16::]
  363. leftTime = int(data[4:8], 16)
  364. if data[8:12] == 'FFFF':
  365. power = 0
  366. else:
  367. power = int(data[8:12], 16)
  368. return {'port': line, 'leftTime': leftTime, 'power': power}
  369. def get_port_status_from_dev(self):
  370. """
  371. 访问设备,获取设备端口信息
  372. :return:
  373. """
  374. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  375. {
  376. "funCode": '01', 'data': '00'
  377. })
  378. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  379. if devInfo['rst'] == -1:
  380. raise ServiceException(
  381. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  382. elif devInfo['rst'] == 1:
  383. raise ServiceException(
  384. {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
  385. data = devInfo['data'][16::]
  386. result = {}
  387. portNum = int(data[2:4], 16)
  388. portData = data[4::]
  389. ii = 0
  390. while ii < portNum:
  391. statusTemp = portData[ii * 2:ii * 2 + 2]
  392. if statusTemp == '01':
  393. status = {'status': Const.DEV_WORK_STATUS_IDLE}
  394. elif statusTemp == '02':
  395. status = {'status': Const.DEV_WORK_STATUS_WORKING}
  396. elif statusTemp == '03':
  397. status = {'status': Const.DEV_WORK_STATUS_FORBIDDEN}
  398. elif statusTemp == '04':
  399. status = {'status': Const.DEV_WORK_STATUS_FAULT}
  400. elif statusTemp == '05':
  401. status = {'status': Const.DEV_WORK_STATUS_FAULT_OVERLOAD}
  402. else:
  403. status = Const.DEV_WORK_STATUS_IDLE
  404. ii += 1
  405. result[str(ii)] = status
  406. # 随便把功率配置数据拉上来
  407. gearConf = self.get_gear_conf()
  408. allPorts, usedPorts, usePorts = self.get_port_static_info(result)
  409. Device.update_dev_control_cache(self._device['devNo'],
  410. {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts,
  411. 'gearConf': gearConf})
  412. # 这里存在多线程更新缓存的场景,可能性比较低,但是必须先取出来,然后逐个更新状态,然后再记录缓存
  413. ctrInfo = Device.get_dev_control_cache(self._device['devNo'])
  414. for strPort, info in result.items():
  415. if ctrInfo.has_key(strPort):
  416. ctrInfo[strPort].update({'status': info['status']})
  417. else:
  418. ctrInfo[strPort] = info
  419. Device.update_dev_control_cache(self._device['devNo'], ctrInfo)
  420. return result
  421. def get_default_port_nums(self):
  422. return 10
  423. def get_port_status(self, force=False):
  424. if force:
  425. return self.get_port_status_from_dev()
  426. ctrInfo = Device.get_dev_control_cache(self.device.devNo)
  427. if 'allPorts' in ctrInfo and ctrInfo['allPorts'] > 0:
  428. allPorts = ctrInfo['allPorts']
  429. else:
  430. allPorts = self.get_default_port_nums()
  431. statusDict = {}
  432. for ii in range(allPorts):
  433. tempDict = ctrInfo.get(str(ii + 1), {})
  434. if tempDict.has_key('status'):
  435. statusDict[str(ii + 1)] = {'status': tempDict.get('status')}
  436. elif tempDict.has_key('isStart'):
  437. if tempDict['isStart']:
  438. statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_WORKING}
  439. else:
  440. statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE}
  441. else:
  442. statusDict[str(ii + 1)] = {'status': Const.DEV_WORK_STATUS_IDLE}
  443. allPorts, usedPorts, usePorts = self.get_port_static_info(statusDict)
  444. Device.update_dev_control_cache(self._device['devNo'],
  445. {'allPorts': allPorts, 'usedPorts': usedPorts, 'usePorts': usePorts})
  446. return statusDict
  447. def lock_unlock_port(self, port, lock=True):
  448. lockStr = '00' if lock else '01'
  449. portStr = fill_2_hexByte(hex(int(port)), 2)
  450. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  451. {'IMEI': self._device['devNo'], "funCode": '0A', 'data': portStr + lockStr})
  452. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  453. if devInfo['rst'] == -1:
  454. raise ServiceException(
  455. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  456. elif devInfo['rst'] == 1:
  457. raise ServiceException(
  458. {'result': 2, 'description': u'端口禁用功能只有带保险丝版本的才有。可能是您的设备版本过低,暂时不支持此功能,也可能是设备繁忙无响应。'})
  459. data = devInfo['data'][18::]
  460. if data[0:2] == '01': # 表示成功
  461. pass
  462. else:
  463. raise ServiceException({'result': 2, 'description': u'操作端口失败,请重试看能否解决'})
  464. if lock:
  465. Device.update_dev_control_cache(self._device['devNo'],
  466. {str(port): {'status': Const.DEV_WORK_STATUS_FORBIDDEN}})
  467. else:
  468. Device.update_dev_control_cache(self._device['devNo'], {str(port): {'status': Const.DEV_WORK_STATUS_IDLE}})
  469. def active_deactive_port(self, port, active):
  470. if active:
  471. raise ServiceException({'result': 2, 'description': u'该设备不支持直接打开端口'})
  472. self.stop_charging_port(port)
  473. devInfo = Device.get_dev_control_cache(self._device['devNo'])
  474. portCtrInfo = devInfo.get(str(port), {})
  475. portCtrInfo.update({'isStart': False, 'status': Const.DEV_WORK_STATUS_IDLE, 'needTime': 0, 'leftTime': 0,
  476. 'endTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)})
  477. newValue = {str(port): portCtrInfo}
  478. Device.update_dev_control_cache(self._device['devNo'], newValue)
  479. def stop_charging_port(self, port):
  480. portStr = fill_2_hexByte(hex(int(port)), 2)
  481. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  482. {'IMEI': self._device['devNo'], "funCode": '0B', 'data': portStr})
  483. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  484. if devInfo['rst'] == -1:
  485. raise ServiceException(
  486. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  487. elif devInfo['rst'] == 1:
  488. raise ServiceException(
  489. {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
  490. data = devInfo['data'][16::]
  491. port = int(data[2:4], 16)
  492. return {'port': port}
  493. def stop(self, port=None):
  494. infoDict = self.stop_charging_port(port)
  495. return infoDict
  496. @property
  497. def isHaveStopEvent(self):
  498. return True
  499. def response_card_balance(self, cardNo, balance):
  500. data = ''
  501. data += fill_2_hexByte(hex(int(cardNo)), 8)
  502. data += fill_2_hexByte(hex(int(balance) * 10), 4)
  503. data += fill_2_hexByte(hex(int(random.randint(0, 1000))), 8)
  504. data += '00'
  505. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_NO_RESPONSE,
  506. {'IMEI': self._device['devNo'], "funCode": '27', 'data': data})
  507. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  508. if devInfo['rst'] == -1:
  509. raise ServiceException(
  510. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  511. elif devInfo['rst'] == 1:
  512. raise ServiceException(
  513. {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
  514. # 获取IC卡、投币、最大功率设置
  515. def get_IC_coin_power_config(self):
  516. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  517. {'IMEI': self._device['devNo'], "funCode": '0C', 'data': '00'})
  518. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  519. if devInfo['rst'] == -1:
  520. raise ServiceException(
  521. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  522. elif devInfo['rst'] == 1:
  523. raise ServiceException(
  524. {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
  525. confData = devInfo['data'][18::]
  526. maxPower = int(confData[0:4], 16)
  527. icMoney = int(confData[4:6], 16)
  528. if len(confData) > 6:
  529. time1 = int(confData[6:10], 16)
  530. time2 = int(confData[10:14], 16)
  531. time3 = int(confData[14:18], 16)
  532. time4 = int(confData[18:22], 16)
  533. time5 = int(confData[22:26], 16)
  534. else:
  535. time1, time2, time3, time4, time5 = 0, 0, 0, 0, 0
  536. return {'maxPower': maxPower, 'icMoney': icMoney, 'time1': time1,
  537. 'time2': time2, 'time3': time3, 'time4': time4, 'time5': time5}
  538. def set_IC_coin_power_config(self, infoDict):
  539. data = ''
  540. data += fill_2_hexByte(hex(int(infoDict['maxPower'])), 4)
  541. data += fill_2_hexByte(hex(int(infoDict['icMoney'])), 2)
  542. data += fill_2_hexByte(hex(int(infoDict['time1'])), 4)
  543. data += fill_2_hexByte(hex(int(infoDict['time2'])), 4)
  544. data += fill_2_hexByte(hex(int(infoDict['time3'])), 4)
  545. data += fill_2_hexByte(hex(int(infoDict['time4'])), 4)
  546. data += fill_2_hexByte(hex(int(infoDict['time5'])), 4)
  547. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  548. {'IMEI': self._device['devNo'], "funCode": '08', 'data': data})
  549. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  550. if devInfo['rst'] == -1:
  551. raise ServiceException(
  552. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  553. elif devInfo['rst'] == 1:
  554. raise ServiceException(
  555. {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试'})
  556. def get_coin_card_enable(self):
  557. devs = Device.get_collection().find({'devNo': self._device['devNo']})
  558. if devs.count == 0:
  559. raise ServiceException(
  560. {'result': 2, 'description': u'没有找到设备哦'})
  561. return {'putCoins': devs[0].get('otherConf', {}).get('putCoins', False),
  562. 'icCard': devs[0].get('otherConf', {}).get('icCard', False)}
  563. def set_coin_card_enable(self, infoDict):
  564. data = ''
  565. if infoDict['putCoins']:
  566. data += '01'
  567. else:
  568. data += '00'
  569. if infoDict['icCard']:
  570. data += '01'
  571. else:
  572. data += '00'
  573. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  574. {'IMEI': self._device['devNo'], "funCode": '09', 'data': data})
  575. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  576. if devInfo['rst'] == -1:
  577. raise ServiceException(
  578. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  579. elif devInfo['rst'] == 1:
  580. raise ServiceException(
  581. {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
  582. try:
  583. conf = Device.objects.get(devNo=self._device['devNo']).otherConf
  584. conf.update({'putCoins': infoDict['putCoins'], 'icCard': infoDict['icCard']})
  585. Device.get_collection().update_one({'devNo': self._device['devNo']}, {'$set': {'otherConf': conf}})
  586. except Exception, e:
  587. logger.error('update dev=%s coin enable ic enable e=%s' % (self._device['devNo'], e))
  588. # 获取设备配置参数
  589. def get_dev_setting(self):
  590. resultDict = self.get_IC_coin_power_config()
  591. tempDict = self.get_coin_card_enable()
  592. resultDict.update(tempDict)
  593. tempDict = self.get_gear_conf()
  594. resultDict.update(tempDict)
  595. resultDict.update(tempDict)
  596. return resultDict
  597. # 获取设备配置参数
  598. def set_dev_setting(self, setConf):
  599. keys = setConf.keys()
  600. if 'putCoins' in keys or 'icCard' in keys:
  601. self.set_coin_card_enable(setConf)
  602. if 'maxPower' in keys or 'icMoney' in keys or 'time1' in keys or 'time2' in keys or 'time3' in keys:
  603. self.set_IC_coin_power_config(setConf)
  604. def get_gear_conf_from_cache(self):
  605. controlCache = Device.get_dev_control_cache(self._device['devNo'])
  606. gearConf = controlCache.get('gearConf', None)
  607. if gearConf is None:
  608. gearConf = self.get_gear_conf()
  609. Device.update_dev_control_cache(self._device['devNo'], {'gearConf': gearConf})
  610. return gearConf
  611. def get_gear_conf(self):
  612. resultDict = {'power1': 0, 'power1ratio': 0, 'power2': 0,
  613. 'power2ratio': 0, 'power3': 0, 'power3ratio': 0,
  614. 'power4': 0, 'power4ratio': 0, 'power5': 0, 'power5ratio': 0}
  615. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  616. {'IMEI': self._device['devNo'], "funCode": '15', 'data': '00'})
  617. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  618. if devInfo['rst'] == -1:
  619. raise ServiceException(
  620. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  621. elif devInfo['rst'] == 1:
  622. return resultDict
  623. confData = devInfo['data'][18::]
  624. power1 = int(confData[0:4], 16)
  625. power1ratio = int(confData[4:6], 16)
  626. power2 = int(confData[6:10], 16)
  627. power2ratio = int(confData[10:12], 16)
  628. power3 = int(confData[12:16], 16)
  629. power3ratio = int(confData[16:18], 16)
  630. power4 = int(confData[18:22], 16)
  631. power4ratio = int(confData[22:24], 16)
  632. power5 = int(confData[24:28], 16)
  633. power5ratio = int(confData[28:30], 16)
  634. result = {
  635. 'power1': power1, 'power1ratio': power1ratio, 'power2': power2,
  636. 'power2ratio': power2ratio, 'power3': power3, 'power3ratio': power3ratio,
  637. 'power4': power4, 'power4ratio': power4ratio, 'power5': power5, 'power5ratio': power5ratio
  638. }
  639. return result
  640. def set_gear_conf(self, infoDict):
  641. data = ''
  642. data += fill_2_hexByte(hex(int(infoDict['power1'])), 4)
  643. data += fill_2_hexByte(hex(int(infoDict['power1ratio'])), 2)
  644. data += fill_2_hexByte(hex(int(infoDict['power2'])), 4)
  645. data += fill_2_hexByte(hex(int(infoDict['power2ratio'])), 2)
  646. data += fill_2_hexByte(hex(int(infoDict['power3'])), 4)
  647. data += fill_2_hexByte(hex(int(infoDict['power3ratio'])), 2)
  648. data += fill_2_hexByte(hex(int(infoDict['power4'])), 4)
  649. data += fill_2_hexByte(hex(int(infoDict['power4ratio'])), 2)
  650. data += fill_2_hexByte(hex(int(infoDict['power5'])), 4)
  651. data += fill_2_hexByte(hex(int(infoDict['power5ratio'])), 2)
  652. devInfo = MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_SYNC,
  653. {'IMEI': self._device['devNo'], "funCode": '14', 'data': data})
  654. if devInfo.has_key('rst') and devInfo['rst'] != 0:
  655. if devInfo['rst'] == -1:
  656. raise ServiceException(
  657. {'result': 2, 'description': u'充电桩正在玩命找网络,请您稍候再试'})
  658. elif devInfo['rst'] == 1:
  659. raise ServiceException(
  660. {'result': 2, 'description': u'充电桩忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能'})
  661. def set_device_function(self, request, lastSetConf):
  662. if request.POST.has_key('putCoins'):
  663. putCoins = request.POST.get("putCoins", False)
  664. lastSetConf.update({'putCoins': putCoins})
  665. self.set_coin_card_enable(lastSetConf)
  666. if request.POST.has_key('icCard'):
  667. # icCard = True if request.POST.get('icCard') == 'true' else False
  668. icCard = request.POST.get("icCard", False)
  669. lastSetConf.update({'icCard': icCard})
  670. self.set_coin_card_enable(lastSetConf)
  671. def set_device_function_param(self, request, lastSetConf):
  672. if request.POST.has_key('maxPower') and request.POST.has_key('icMoney'):
  673. lastSetConf.update({'maxPower': int(request.POST.get('maxPower', 0))})
  674. lastSetConf.update({'icMoney': int(request.POST.get('icMoney', 0))})
  675. self.set_IC_coin_power_config(lastSetConf)
  676. if request.POST.has_key('time1') and request.POST.has_key('time2') and request.POST.has_key('time3'):
  677. lastSetConf.update({'time1': int(request.POST.get('time1', 0))})
  678. lastSetConf.update({'time2': int(request.POST.get('time2', 0))})
  679. lastSetConf.update({'time3': int(request.POST.get('time3', 0))})
  680. lastSetConf.update({'time4': int(request.POST.get('time4', 0))})
  681. lastSetConf.update({'time5': int(request.POST.get('time5', 0))})
  682. self.set_IC_coin_power_config(lastSetConf)
  683. if request.POST.has_key('power1') and request.POST.has_key('power1ratio'):
  684. lastSetConf.update({'power1': int(request.POST.get('power1', 0))})
  685. lastSetConf.update({'power1ratio': int(request.POST.get('power1ratio', 0))})
  686. lastSetConf.update({'power2': int(request.POST.get('power2', 0))})
  687. lastSetConf.update({'power2ratio': int(request.POST.get('power2ratio', 0))})
  688. lastSetConf.update({'power3': int(request.POST.get('power3', 0))})
  689. lastSetConf.update({'power3ratio': int(request.POST.get('power3ratio', 0))})
  690. lastSetConf.update({'power4': int(request.POST.get('power4', 0))})
  691. lastSetConf.update({'power4ratio': int(request.POST.get('power4ratio', 0))})
  692. lastSetConf.update({'power5': int(request.POST.get('power5', 0))})
  693. lastSetConf.update({'power5ratio': int(request.POST.get('power5ratio', 0))})
  694. self.set_gear_conf(lastSetConf)
  695. def start_device_realiable(self, order):
  696. try:
  697. attachParas = order.attachParas
  698. package = order.package
  699. if attachParas is None:
  700. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路、电池类型信息'})
  701. if not attachParas.has_key('chargeIndex'):
  702. raise ServiceException({'result': 2, 'description': u'请您选择合适的充电线路'})
  703. price = float(package['price'])
  704. coins = float(package['coins'])
  705. port = hex(int(attachParas['chargeIndex']))
  706. hexPort = fill_2_hexByte(port, 2)
  707. gear = hex(int(attachParas['powerGear']))
  708. hexGear = fill_2_hexByte(gear, 4)
  709. unit = package.get('unit', u'分钟')
  710. needTime = int(package['time'])
  711. if unit in [u'分钟', u'小时', u'天', u'秒']:
  712. billingType = 'time'
  713. if unit == u'小时':
  714. needTime = int(package['time']) * 60
  715. elif unit == u'天':
  716. needTime = int(package['time']) * 1440
  717. elif unit == u'秒':
  718. needTime = int(package['time']) / 60
  719. hexTime = fill_2_hexByte(hex(needTime), 4)
  720. else:
  721. raise ServiceException(
  722. {
  723. 'result': 2,
  724. 'description': u'充电桩只支持按时间计费的方式,不支持其他方式'
  725. }
  726. )
  727. return MessageSender.send(device=self.device,
  728. cmd=DeviceCmdCode.OPERATE_DEV_SYNC,
  729. payload={
  730. "funCode": '02',
  731. 'order_id': order.orderNo,
  732. 'data': hexPort + '0000' + hexTime
  733. },
  734. timeout=45, retry=3)
  735. except ServiceException as e:
  736. logger.exception(e)
  737. return {
  738. 'rst': ErrorCode.EXCEPTION,
  739. 'fts': int(time.time()),
  740. 'errorDesc': cn(e.result.get('description')),
  741. 'order_type': 'com_start',
  742. 'order_id': order.orderNo
  743. }
  744. except Exception as e:
  745. logger.exception(e)
  746. return {
  747. 'rst': ErrorCode.EXCEPTION,
  748. 'fts': int(time.time()),
  749. 'order_type': 'com_start',
  750. 'order_id': order.orderNo
  751. }