anqihuandian.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. # 换电贵业务流程整理
  4. # 1. 用户扫码,服务器开启指定端口
  5. # 2. 用户将电池放入柜子中,同时关闭门锁,主板检测门锁关闭后,上报相应报文
  6. # 3. 服务器收到指令之后 需要检测 电池是否已经成功连接充电器 ,避免空仓的情况发生
  7. # 4. 检测空仓 的方法是读取该端口的电池IMEI ,主板会有延迟,所以一次没有读到需要继续再读取一次 最多三次没有读取到就算是没有放入电池
  8. # 5. 检测到电池的情况下,发送开锁指令,打开一个有电池的柜门
  9. # 6. 用户关闭柜门, 主板再次上报,完成一次换电操作
  10. # 注意事项
  11. # 1. 整个换电可拆解为两次独立的开关门事件 即 开门--关门--开门--关门
  12. # 2. 目前并没有比较可靠的方式将两次事件关联起来,完全依赖服务器的缓存以及订单的存储信息,实际最好的方式是在发送串口数据的时候携带订单号,但是目前主板并没有做到这一点
  13. # 3. 安骑科技会有一个单独的电池管理系统,该系统下电池编号 用户ID 代理商ID 构成唯一的一条数据,也就是说一个用户一个经销商下,同一时间只可能拥有一块电池
  14. # 4. 初代主板问题比较大,经常会出现明明电池放进去了,但是还是没有读到电池的情况。更换主板后情况得到缓解,但需要注意,目前还有少量的初代设备正在运行
  15. import datetime
  16. import logging
  17. from apps.web.core.adapter.base import SmartBox, fill_2_hexByte
  18. from apps.web.core.exceptions import ServiceException
  19. from apps.web.core.networking import MessageSender
  20. from apps.web.dealer.models import Dealer
  21. from apps.web.device.models import Battery, Device, Group
  22. from apps.web.user.models import MyUser
  23. from taskmanager.mediator import task_caller
  24. from apps.web.constant import MQTT_TIMEOUT, DeviceCmdCode, Const
  25. logger = logging.getLogger(__name__)
  26. class AnQiBox(SmartBox):
  27. DOOR_STATUS_MAP = {
  28. "00": "opened",
  29. "01": "closed"
  30. }
  31. DEFAULT_CAN_USE_VOLTAGE = 65
  32. DEFAULT_MAX_VOLTAGE = 75
  33. DEFAULT_MIN_VOLTAGE = 10
  34. DEFAULT_VOICE_NUM = 1
  35. DEFAULT_REPAIR_DOOR = 11
  36. DEFAULT_REVERSE_LOCK_STATUS = False
  37. MAX_NO_LOAD_ELEC = 0.5
  38. INVALID_BATTERY_SN_HEX = "".join(["0" for r in xrange(48)])
  39. BATTERY_SN_ASCII = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  40. def __init__(self, device):
  41. super(AnQiBox, self).__init__(device)
  42. self._devNo = device.get("devNo")
  43. self._portStatus = None
  44. def _send_data(self, funCode, data = None, cmd = None, timeout = MQTT_TIMEOUT.NORMAL):
  45. """
  46. 发送报文函数
  47. :return:
  48. """
  49. if cmd is None:
  50. cmd = DeviceCmdCode.OPERATE_DEV_SYNC
  51. if data is None:
  52. data = ""
  53. result = MessageSender.send(device = self.device,
  54. cmd = cmd,
  55. payload = {
  56. "IMEI": self._devNo,
  57. "funCode": funCode,
  58. "data": data
  59. }, timeout = timeout)
  60. if result.has_key('rst') and result['rst'] != 0:
  61. if result['rst'] == -1:
  62. raise ServiceException({'result': 2, 'description': u"网络故障"})
  63. elif result['rst'] == 1:
  64. raise ServiceException({'result': 2, 'description': u'主板无响应'})
  65. return result
  66. def _open_door(self, port):
  67. """
  68. 发送指令, 开启柜门
  69. :param port:
  70. :return:
  71. """
  72. try:
  73. self._turn_off_power(port)
  74. except Exception as e:
  75. pass
  76. portHex = fill_2_hexByte(hex(int(port)), 2)
  77. result = self._send_data("82", portHex, timeout = MQTT_TIMEOUT.START_DEVICE)
  78. data = result.get("data", "")
  79. if data[14: 16] != "00" or data[18: 20] != "00":
  80. return
  81. return result
  82. def _query_door_status(self):
  83. """
  84. 查询所有门锁的状态
  85. :return:
  86. """
  87. doorStatus = list()
  88. result = self._send_data("84")
  89. data = result.get("data", "")
  90. if data[12:14] != "00":
  91. return doorStatus
  92. lockNum = int(data[14: 16], 16)
  93. needData = data[16:-2]
  94. for i in xrange(lockNum):
  95. portStr = str(i + 1)
  96. doorStatus.append({
  97. portStr: self.DOOR_STATUS_MAP.get(needData[:2])
  98. })
  99. needData = needData[2:]
  100. return doorStatus
  101. def _query_all_status(self):
  102. """
  103. 查询所有的信息, 包括电压、电量、锁状态、电池SN号
  104. :return:
  105. """
  106. statusDict = dict()
  107. result = self._send_data("78")
  108. data = result.get("data", "")
  109. if data[14:16] != "00":
  110. return statusDict
  111. batteryNum = int(data[16:18], 16)
  112. needData = data[18: -2]
  113. for i in range(batteryNum):
  114. portStr = str(i + 1)
  115. batterySn = needData[8:56]
  116. if batterySn == self.INVALID_BATTERY_SN_HEX:
  117. batterySn = ""
  118. else:
  119. batterySn = needData[8:56 - 2].decode("hex")[8:]
  120. # 添加上电量百分比 为了不影响其他的逻辑 换个字段名称
  121. statusDict[portStr] = {
  122. "voltage": int(needData[: 4], 16) / 100.0,
  123. "elec": int(needData[4:6], 16),
  124. "doorStatus": self.DOOR_STATUS_MAP.get(needData[6:8]),
  125. "batteryImei": batterySn,
  126. }
  127. needData = needData[56:]
  128. return statusDict
  129. def _query_battery_imei(self, port):
  130. """
  131. 查询电池的 IMEI 号码
  132. :return:
  133. """
  134. imei = ""
  135. portHex = fill_2_hexByte(hex(int(port)), 2)
  136. result = self._send_data("77", portHex)
  137. data = result.get("data")
  138. if data[14:16] != "00":
  139. return imei
  140. return self.translate_battery_imei(data[20: -2].decode("hex")[8:])
  141. def _query_elec(self):
  142. """
  143. 查询当前的充电电流
  144. :return:
  145. """
  146. result = self._send_data("72")
  147. data = result["data"]
  148. if data[14: 16] != "00": # 读取电流信息失败
  149. raise ServiceException({'result': 2, 'description': u'无响应信息'})
  150. allPorts = int(data[16: 18], 16)
  151. statusDict = dict()
  152. for i in xrange(allPorts):
  153. portStr = str(i + 1)
  154. electric = int(data[18 + (i * 4):18 + (i * 4) + 4], 16)
  155. electric /= 1000.0
  156. chargeStatus = u"充电中" if electric > self.MAX_NO_LOAD_ELEC else u"停止充电"
  157. statusDict[portStr] = {"outputElec": "{:.2f}".format(electric), "chargeStatus": chargeStatus}
  158. return statusDict
  159. def _pay_voice(self):
  160. """
  161. 播报 音乐
  162. :return:
  163. """
  164. voiceNum = self._device.get("otherConf", dict()).get("voiceNum", self.DEFAULT_VOICE_NUM)
  165. voiceHex = fill_2_hexByte(hex(int(voiceNum)), 2)
  166. self._send_data("75", voiceHex, DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  167. def _turn_on_power(self, port):
  168. """
  169. 开启充电
  170. :return:
  171. """
  172. portHex = fill_2_hexByte(hex(int(port)), 2)
  173. timeHex = "0258"
  174. self._send_data("70", portHex + timeHex, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  175. def _get_charge_limit(self):
  176. """
  177. 读取充电电流限制
  178. :return:
  179. """
  180. result = self._send_data("76")
  181. data = result.get("data", "")
  182. if data[14:16] != "00":
  183. raise ServiceException({"result": 2, "description": u"读取充电电流限制失败"})
  184. maxElec = int(data[16:20], 16)
  185. minElec = int(data[20:24], 16)
  186. return {
  187. "maxElec": maxElec,
  188. "minElec": minElec
  189. }
  190. def _turn_off_power(self, port):
  191. """
  192. 关闭充电
  193. :return:
  194. """
  195. portHex = fill_2_hexByte(hex(int(port)), 2)
  196. self._send_data("71", portHex, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE)
  197. def _set_charge_limit(self, maxElec, minElec):
  198. """
  199. 设置充电电流限制
  200. :param maxElec:
  201. :param minElec:
  202. :return:
  203. """
  204. if maxElec and int(maxElec) > 10000:
  205. raise ServiceException({"result": 2, "description": u"可设置的最大充电电流为10000mA"})
  206. if minElec and int(minElec) < 0:
  207. raise ServiceException({"result": 2, "description": u"可设置的最小充电电流为0mA"})
  208. data = ""
  209. data += fill_2_hexByte(hex(int(maxElec)))
  210. data += fill_2_hexByte(hex(int(minElec)))
  211. result = self._send_data("74", data)
  212. if result.get("data", "")[14:16] != "00":
  213. raise ServiceException({"result": 2, "description": u"设置充电电流限制失败"})
  214. def _query_all_battery_imei(self):
  215. """
  216. 暂定 batteryImei 是 15位
  217. :return:
  218. """
  219. data = self._query_all_status()
  220. batteryInfo = dict()
  221. for port, item in data.items():
  222. batteryImei = item.get("batteryImei")
  223. if Battery.is_battery_sn_format(batteryImei):
  224. batteryInfo[port] = batteryImei
  225. return batteryInfo
  226. def _check_port_status(self, portStr, portInfo):
  227. """
  228. 根据 现在端口的内部信息(有无电池) 判定当前的端口状态 有电池为繁忙/无电池为空闲
  229. :param portStr:
  230. :param portInfo:
  231. :return:
  232. """
  233. # 先从设备缓存中读取被锁的设备端口列表
  234. lockPorts = self.device.get("otherConf", dict()).get("lockPorts", list())
  235. if portStr in lockPorts:
  236. return Const.DEV_WORK_STATUS_FAULT
  237. # 有电池的判定为繁忙 没有电池的判定为空闲
  238. if portInfo.get("batteryImei"):
  239. return Const.DEV_WORK_STATUS_WORKING
  240. else:
  241. return Const.DEV_WORK_STATUS_IDLE
  242. @staticmethod
  243. def _is_no_elec_port(portInfo):
  244. """
  245. 根据门锁状态以及电池状态 获取 可开启的仓门
  246. :param portInfo:
  247. :return:
  248. """
  249. doorStatus = portInfo.get("doorStatus")
  250. batteryImei = portInfo.get("batteryImei")
  251. if doorStatus == "closed" and not batteryImei:
  252. return True
  253. else:
  254. return False
  255. def _is_can_use_port(self, portStr, portInfo):
  256. """
  257. 检测 端口的电压是否可用
  258. :param portStr:
  259. :param portInfo:
  260. :return:
  261. """
  262. otherConf = self.device.get("otherConf", dict())
  263. lockPorts = otherConf.get("lockPorts", list())
  264. if portStr in lockPorts:
  265. return False, "", 0
  266. canUseVoltage = otherConf.get("canUseVoltage", self.DEFAULT_CAN_USE_VOLTAGE)
  267. voltage = portInfo.get("voltage")
  268. batteryImei = portInfo.get("batteryImei")
  269. # 用户拿电池的时候判断一下看是否被禁用 被禁用的电池直接跳过
  270. battery = Battery.get_one(self.device.ownerId, batteryImei)
  271. if battery and battery.disable:
  272. return False, "", voltage
  273. if all([voltage, batteryImei]) and voltage > float(canUseVoltage):
  274. return True, batteryImei, voltage
  275. else:
  276. return False, "", voltage
  277. def _on_point_start(self, chargeIndex):
  278. """
  279. 经销商远程上分
  280. :return:
  281. """
  282. otherConf = self._device.get("otherConf", dict())
  283. devCache = Device.get_dev_control_cache(self._devNo)
  284. currentUseInfo = devCache.get("currentUseInfo")
  285. if currentUseInfo and "orderNo" in currentUseInfo:
  286. raise ServiceException({'result': 2, 'description': u"用户正在使用, 请等待用户使用完成之后再远程上分"})
  287. result = self._open_door(chargeIndex)
  288. if not result:
  289. raise ServiceException({'result': 2, 'description': u'柜门开启失败,请重新试试'})
  290. # 经销商远程上分之后 也把设备锁死,需要清空一次信息
  291. currentUseInfo = {
  292. "openId": "dealer",
  293. "chargeIndex1": chargeIndex
  294. }
  295. Device.update_dev_control_cache(self._devNo, {"currentUseInfo": currentUseInfo})
  296. otherConf.update({"nextPort": chargeIndex})
  297. Device.objects.filter(devNo = self._devNo).update(otherConf = otherConf)
  298. Device.invalid_device_cache(self._devNo)
  299. return
  300. def _battery_voltage_warning(self, port, voltage, minVoltage, maxVoltage, batteryImei):
  301. """
  302. 电池电压故障报警给经销商
  303. :param port: 端口号
  304. :param voltage: 电池当前电压
  305. :param minVoltage: 允许最大电压
  306. :param maxVoltage: 允许最小电压
  307. :param batteryImei: 电池IMEI
  308. :return:
  309. """
  310. dealer = Dealer.objects.get(id = self._device["ownerId"])
  311. if not dealer or not dealer.managerialOpenId:
  312. return
  313. group = Group.get_group(self._device["groupId"])
  314. notifyData = {
  315. "title": "设备当前电池<{}>电压不正常".format(batteryImei),
  316. "device": u"{gNum}组-{lc}-{port}端口".format(gNum = self._device["groupNumber"],
  317. lc = self._device["logicalCode"], port = port),
  318. "location": u"{address}-{groupName}".format(address = group["address"], groupName = group["groupName"]),
  319. "fault": u"当前电压:{},允许最大电压:{},允许最小电压:{}".format(voltage, maxVoltage, minVoltage),
  320. "notifyTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  321. }
  322. task_caller(
  323. func_name = 'report_to_dealer_via_wechat',
  324. openId = dealer.managerialOpenId,
  325. dealerId = str(dealer.id),
  326. templateName = "device_fault",
  327. **notifyData
  328. )
  329. @property
  330. def no_elec_door_port(self):
  331. """
  332. 一定是直接从设备设置里面读取, 经销商远程上分的时候会重置下一个端口, 第一次一定是经销商上分
  333. :return:
  334. """
  335. portStatus = self.get_port_status_from_dev()
  336. # 这个地方先存入缓存, 鉴别电池电压之后立即删除
  337. self._portStatus = portStatus
  338. otherConf = self._device.get("otherConf", dict())
  339. nextPort = otherConf.get("nextPort")
  340. lockPorts = self.device.get("otherConf", dict()).get("lockPorts", list())
  341. # 双重校验 防止出错
  342. if nextPort not in lockPorts:
  343. return nextPort
  344. @property
  345. def can_use_voltage_port(self):
  346. """
  347. 获取 可用电压的端口号 以及相应的电池的IMEI
  348. 如果此次操作中 之前从设备上获取过端口的信息 就直接使用该信息 否则再从设备端请求一次
  349. :return:
  350. """
  351. if self._portStatus is None:
  352. self._portStatus = self.get_port_status_from_dev()
  353. portStatus = self._portStatus
  354. canUseList = list()
  355. for portStr, portInfo in portStatus.items():
  356. # elec in portInfo 是为了确认这个port表示的是端口信息
  357. if isinstance(portInfo, dict) and "elec" in portInfo:
  358. canUse, batteryImei, voltage = self._is_can_use_port(portStr, portInfo)
  359. if canUse:
  360. canUseList.append((portStr, batteryImei, voltage))
  361. Device.update_dev_control_cache(self.device.devNo, {"canUseBattery": len(canUseList)})
  362. if not canUseList:
  363. return None, ""
  364. canUseList.sort(key = lambda x: x[2], reverse = True)
  365. # 取第一个元素的前两个元素
  366. return canUseList[0][:2]
  367. @staticmethod
  368. def translate_door_status_to_unicode(doorStatus):
  369. """
  370. 将柜门状态更改为 汉字表示
  371. :param doorStatus:
  372. :return:
  373. """
  374. if doorStatus == "opened":
  375. return u"开启"
  376. elif doorStatus == "closed":
  377. return u"关闭"
  378. else:
  379. return u"读取失败,状态未知"
  380. @staticmethod
  381. def translate_battery_imei(battery):
  382. """
  383. 去掉battery中的非法字符
  384. :param battery:
  385. :return:
  386. """
  387. newBatterySn = ""
  388. for ch in battery:
  389. if ch in AnQiBox.BATTERY_SN_ASCII:
  390. newBatterySn += ch
  391. return newBatterySn
  392. def get_port_status_from_dev(self):
  393. """
  394. 从设备方获取 换电柜的基本信息 电量百分比 电压 电池IMEI 以及 门锁状态
  395. 每次获取的时候检测 电池的电压是否正常 非正常的电池电压上报经销商
  396. :return:
  397. """
  398. portStatus = self._query_all_status()
  399. otherConf = self._device.get("otherConf", dict())
  400. maxVoltage = float(otherConf.get("maxVoltage", self.DEFAULT_MAX_VOLTAGE))
  401. minVoltage = float(otherConf.get("minVoltage", self.DEFAULT_MIN_VOLTAGE))
  402. for portStr, portInfo in portStatus.items():
  403. voltage = portInfo.get("voltage")
  404. batteryImei = portInfo.get("batteryImei")
  405. if voltage and batteryImei and (voltage > maxVoltage or voltage < minVoltage):
  406. self._battery_voltage_warning(portStr, voltage, minVoltage, maxVoltage, batteryImei)
  407. return portStatus
  408. def dealer_get_port_status(self):
  409. """
  410. 经销商 上分的时候获取端口状态
  411. :return:
  412. """
  413. portStatus = self.get_port_status_from_dev()
  414. resultDict = dict()
  415. for portStr, portInfo in portStatus.items():
  416. tempStatus = self._check_port_status(portStr, portInfo)
  417. portInfo.update({"status": tempStatus})
  418. resultDict[portStr] = portInfo
  419. return resultDict
  420. def start_device(self, package, openId, attachParas):
  421. """
  422. 换电柜开门的时候,不需要用户选择几号门打开,系统自动选定空仓打开(开门规则在最下注释)
  423. 但是经销商上分的时候是一定会选择仓门进行打开的
  424. 除此之外,应当在每次设备启动的时候记录一次缓存信息,最终整个换电结束的时候将此次的信息清空掉,这个缓存信息伴随整个换电过程而生 currentUseInfo
  425. :param package:
  426. :param openId:
  427. :param attachParas:
  428. :return:
  429. """
  430. # 首先获取chargeIndex 如果没有chargeIndex 说明是终端用户使用 有chargeIndex没有openId的说明是经销商的远程上分
  431. chargeIndex = attachParas.get("chargeIndex")
  432. if chargeIndex and not openId:
  433. return self._on_point_start(chargeIndex)
  434. # 用户启动的,现在可以获取订单号 这个订单号伴随整个换电的过程直到结束
  435. orderNo = attachParas.get("orderNo")
  436. # 对于终端用户,需要校验身份认证是否通过
  437. user = MyUser.objects(openId = openId, groupId = self._device["groupId"]).first()
  438. if not user:
  439. raise ServiceException({'result': 2, 'description': u"无效的用户"})
  440. groupIds = Group.get_group_ids_of_dealer(str(self.device.ownerId))
  441. activeInfo = MyUser.get_active_info(openId, agentId = user.agentId, groupId__in = groupIds)
  442. if not activeInfo or not activeInfo.get("isMember"):
  443. raise ServiceException({'result': 2, 'description': u"请先完成身份激活信息"})
  444. # 查询缓存,如果当前正在使用的缓存存在,则说明上次的消费尚未结束
  445. devCache = Device.get_dev_control_cache(self._devNo)
  446. currentUseInfo = devCache.get("currentUseInfo", dict())
  447. if currentUseInfo:
  448. raise ServiceException({'result': 2, 'description': u'上一个客户尚未使用完成,或者尚未关闭柜门,暂时无法使用设备, 请检查是否有柜门尚未关闭。'})
  449. # 获取即将打开的空仓的信息
  450. chargeIndex = self.no_elec_door_port
  451. if not chargeIndex:
  452. raise ServiceException({'result': 2, 'description': u'当前设备没有可放置电池的柜门,请换个设备试试或联系经销商'})
  453. # 获取可以开的port以及Imei, 没有可用电池的情况下直接结束当前的服务
  454. toOpenChargeIndex, toOpenBattery = self.can_use_voltage_port
  455. if not toOpenChargeIndex:
  456. raise ServiceException({'result': 2, 'description': u'当前设备无可用电池,请换个设备试试'})
  457. # 记录下此次换电要开启的有电池的信息
  458. currentUseInfo.update({
  459. "toOpenChargeIndex": toOpenChargeIndex,
  460. "toOpenBattery": toOpenBattery
  461. })
  462. # 检验工作通过之后,生下来的就是记录缓存, 开启柜门 播放音乐等行为
  463. result = self._open_door(chargeIndex)
  464. if not result:
  465. raise ServiceException({'result': 2, 'description': u'柜门开启失败,请联系相应经销商解决'})
  466. self._pay_voice()
  467. # 记录下此次换电过程的当前信息,换电结束的时候清空掉
  468. currentUseInfo.update({
  469. "openId": openId,
  470. "chargeIndex1": chargeIndex,
  471. "orderNo": orderNo
  472. })
  473. Device.update_dev_control_cache(self._devNo, {"currentUseInfo": currentUseInfo})
  474. return result
  475. def analyze_event_data(self, data):
  476. """
  477. 换电柜的上报信息是只要门锁状态发生了变化就会上报,这个地方直接将信息透传给eventer,交由eventer处理
  478. :param data:
  479. :return:
  480. """
  481. cmd = data[12: 14]
  482. # 主动上传门锁状态变化 当门锁内部有电池的时候 会将电池编号一同上传
  483. if cmd == "85":
  484. try:
  485. batterySn = self.translate_battery_imei(data[20:-2].decode('hex')[8:])
  486. except Exception as e:
  487. batterySn = None
  488. result = {
  489. "portStr": str(int(data[14:16], 16)),
  490. "doorStatus": self.DOOR_STATUS_MAP.get(data[16:18])
  491. }
  492. if batterySn:
  493. result.update({"batteryImei": batterySn})
  494. return result
  495. else:
  496. logger.error("no use event cmd, cmd is {}".format(cmd))
  497. def get_dev_setting(self):
  498. """
  499. 返回设备设置的一些常量参数
  500. :return:
  501. """
  502. elecInfo = self._get_charge_limit()
  503. otherConf = self._device.get("otherConf", dict())
  504. repairDoorPort = otherConf.get("repairDoorPort", self.DEFAULT_REPAIR_DOOR)
  505. maxVoltage = otherConf.get("maxVoltage", self.DEFAULT_MAX_VOLTAGE)
  506. minVoltage = otherConf.get("minVoltage", self.DEFAULT_MIN_VOLTAGE)
  507. canUseVoltage = otherConf.get("canUseVoltage", self.DEFAULT_CAN_USE_VOLTAGE)
  508. voiceNum = otherConf.get("voiceNum", self.DEFAULT_VOICE_NUM)
  509. reverseLockStatus = otherConf.get("reverseLockStatus", self.DEFAULT_REVERSE_LOCK_STATUS)
  510. devCache = Device.get_dev_control_cache(self._devNo)
  511. currentUseInfo = devCache.get("currentUseInfo", dict())
  512. data = {
  513. "repairDoorPort": repairDoorPort,
  514. "maxVoltage": maxVoltage,
  515. "minVoltage": minVoltage,
  516. "canUseVoltage": canUseVoltage,
  517. "voiceNum": voiceNum,
  518. "reverseLockStatus": reverseLockStatus,
  519. }
  520. openId = currentUseInfo.get("openId", "")
  521. if openId == "invalid user":
  522. currentUser = u"非法开门"
  523. elif openId == "dealer":
  524. currentUser = u"经销商远程上分"
  525. else:
  526. user = MyUser.objects.filter(openId = openId).first()
  527. if openId and user:
  528. currentUser = user.nickname
  529. else:
  530. currentUser = u"无用户使用"
  531. data.update({"currentUser": currentUser})
  532. currentPort1 = currentUseInfo.get("chargeIndex1")
  533. currentPort2 = currentUseInfo.get("chargeIndex2")
  534. currentBatterySn1 = currentUseInfo.get("oldBatteryImei")
  535. currentBatterySn2 = currentUseInfo.get("newBatteryImei")
  536. if currentPort1:
  537. data.update({"currentPort1": currentPort1})
  538. if currentPort2:
  539. data.update({"currentPort2": currentPort2})
  540. if currentBatterySn1:
  541. data.update({"currentBatterySn1": currentBatterySn1})
  542. if currentBatterySn2:
  543. data.update({"currentBatterySn2": currentBatterySn2})
  544. data.update(elecInfo)
  545. return data
  546. def set_device_function_param(self, request, lastSetConf):
  547. """
  548. 设置设备参数
  549. :param request:
  550. :param lastSetConf:
  551. :return:
  552. """
  553. repairDoorPort = request.POST.get("repairDoorPort")
  554. maxVoltage = request.POST.get("maxVoltage")
  555. minVoltage = request.POST.get("minVoltage")
  556. canUseVoltage = request.POST.get("canUseVoltage")
  557. voiceNum = request.POST.get("voiceNum")
  558. maxElec = request.POST.get("maxElec")
  559. minElec = request.POST.get("minElec")
  560. otherConf = self._device.get("otherConf", dict())
  561. if repairDoorPort is not None:
  562. otherConf.update({"repairDoorPort": repairDoorPort})
  563. if maxVoltage is not None:
  564. otherConf.update({"maxVoltage": maxVoltage})
  565. if minVoltage is not None:
  566. otherConf.update({"minVoltage": minVoltage})
  567. if canUseVoltage is not None:
  568. otherConf.update({"canUseVoltage": canUseVoltage})
  569. if voiceNum is not None:
  570. otherConf.update({"voiceNum": voiceNum})
  571. if maxElec and minElec:
  572. otherConf.update({"maxElec": maxElec, "minElec": minElec})
  573. self._set_charge_limit(maxElec = maxElec, minElec = minElec)
  574. Device.objects.filter(devNo = self._devNo).update(otherConf = otherConf)
  575. Device.invalid_device_cache(self._devNo)
  576. def set_device_function(self, request, lastSetConf):
  577. """
  578. 设置设备功能参数
  579. :param request:
  580. :param lastSetConf:
  581. :return:
  582. """
  583. otherConf = self._device.get("otherConf", dict())
  584. repairDoorPort = otherConf.get("repairDoorPort", self.DEFAULT_REPAIR_DOOR)
  585. repairDoor = request.POST.get("repairDoor", False)
  586. reverseLockStatus = request.POST.get("reverseLockStatus", None)
  587. clearCurUse = request.POST.get("clearCurUse", False)
  588. if repairDoor:
  589. self._open_door(repairDoorPort)
  590. if reverseLockStatus is not None:
  591. otherConf.update({"reverseLockStatus": reverseLockStatus})
  592. Device.objects.filter(otherConf = otherConf)
  593. Device.invalid_device_cache(self._devNo)
  594. # 经销商清除当前使用的信息
  595. if clearCurUse:
  596. Device.clear_port_control_cache(self._devNo, "currentUseInfo")
  597. def lock_unlock_port(self, port, lock = True):
  598. """
  599. 禁用端口 换电柜禁用端口的时候就是做个标记,设备主板本身不支持禁用
  600. :param port:
  601. :param lock:
  602. :return:
  603. """
  604. otherConf = self._device.get("otherConf")
  605. lockPorts = otherConf.get("lockPorts", list())
  606. port = str(port)
  607. try:
  608. if lock:
  609. if port not in lockPorts:
  610. lockPorts.append(port)
  611. else:
  612. lockPorts.remove(port)
  613. except Exception as e:
  614. logger.error("lock or unlock port error: %s" % e)
  615. raise ServiceException({'result': 2, 'description': u'操作失败,请重新试试'})
  616. otherConf.update({"lockPorts": lockPorts})
  617. try:
  618. Device.objects(devNo = self._device["devNo"]).update(otherConf = otherConf)
  619. Device.invalid_device_cache(self._device["devNo"])
  620. except Exception as e:
  621. logger.error("update device %s lockPorts error the reason is %s " % (self._device["devNo"], e))
  622. raise ServiceException({'result': 2, 'description': u'操作失败,请重新试试'})
  623. def async_update_portinfo_from_dev(self):
  624. """
  625. 继承重写,换电柜每次都是获取的最新的端口信息,不再需要这个
  626. :return:
  627. """
  628. pass
  629. def active_deactive_port(self, port, charge):
  630. if charge:
  631. self._turn_on_power(port)
  632. else:
  633. self._turn_off_power(port)
  634. """
  635. 换电情况有以下种:
  636. 正常情况
  637. 用户扫码支付, 打开空仓, 放入电池, 关闭空仓, 取走电池 此时取走的端口号即为下一个开门的仓号
  638. 会重置下一个开门端口的情况:
  639. 1. 柜门被非法打开,不知道是什么原因柜门被打开,也不知道柜门被非法打开之后会发生什么情况,这个地方需要重置下一个端口,从主板读取
  640. 2. 经销商远程上分,同样不知道是远程上分打开的是什么柜子,到底有没有放入电池,这个地方需要重置下一个端口,从主板读取
  641. 3. 用户第二次没有拿走电池,或者用户第二次拿走了电池又放入了电池,必需重置下一个端口,否则会打开有电池的门
  642. 4. 经销商清除了正在使用的信息
  643. 5. 用户扫码放入电池后,有电池的没有打开,必须重置,否则会打开上一个用户扫码放入的电池的柜门
  644. 6. 经销商检修之后
  645. 7. 禁用某个端口之后
  646. """