anqihuandian.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import time
  6. from mongoengine import DoesNotExist
  7. from typing import TYPE_CHECKING
  8. from apps.web.core.exceptions import ServiceException
  9. from apps.web.core.utils import delay_async_operation
  10. from apps.web.device.models import Device, Group, Battery
  11. from apps.web.eventer.base import WorkEvent
  12. from apps.web.eventer import EventBuilder
  13. from apps.web.user.models import ServiceProgress, ConsumeRecord, MyUser
  14. if TYPE_CHECKING:
  15. from apps.web.eventer import Event
  16. logger = logging.getLogger(__name__)
  17. class builder(EventBuilder):
  18. def __getEvent__(self, device_event):
  19. # type:(dict)->Event
  20. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  21. return AnQiEvent(self.deviceAdapter, event_data)
  22. class AnQiEvent(WorkEvent):
  23. def __init__(self, smartBox, event_data):
  24. super(AnQiEvent, self).__init__(smartBox, event_data) # zjl add smartBox
  25. self._devNo = self.device.devNo
  26. def do(self, **args):
  27. """
  28. 主要变量是柜门的开关
  29. :param **args:
  30. :return:
  31. """
  32. if not self.event_data:
  33. return
  34. logger.info("device <{}> door status has been charged, curInfo is <{}>".format(self._devNo, self.event_data))
  35. portStr = self.event_data.get("portStr")
  36. doorStatus = self.event_data.get("doorStatus")
  37. if doorStatus == "opened":
  38. self.hand_opened()
  39. elif doorStatus == "closed":
  40. self.hand_closed()
  41. else:
  42. logger.error(
  43. "invalid door status code ,dev is {}, port is {} status is {}".format(self._devNo, portStr, doorStatus))
  44. def hand_opened(self):
  45. """
  46. 通过非指令手段开门,锁控板才会上传开门指令
  47. :return:
  48. """
  49. portStr = self.event_data.get("portStr")
  50. batterySn = self.event_data.get("batteryImei", "")
  51. self.warning_to_dealer(portStr, batterySn)
  52. # 出现这种非法开门之后,直接更新一个无效的currentUseInfo 将设备锁死
  53. # currentUseInfo = {"openId": "invalid user"}
  54. # if batterySn:
  55. # currentUseInfo.update({"chargeIndex2": portStr, "newBatteryImei": batterySn})
  56. # else:
  57. # currentUseInfo.update({"chargeIndex1": portStr})
  58. # Device.clear_port_control_cache(self._devNo, "currentUseInfo")
  59. # Device.update_dev_control_cache(self._devNo, {"currentUseInfo": currentUseInfo})
  60. return
  61. def hand_closed(self):
  62. """
  63. 处理柜门被关闭的事件
  64. 完整的换电记录也会有两次关门事件上报
  65. 第一次上报的关门 是客户放入电池的关门 此次事件相对重要 需要反复校验电池是否存在
  66. 第二次上报的关门 是作为服务结束的标志
  67. 从事件中标志第一次还是第二次关门的有两种条件 第一个是记录是否完整 第二个是是否有电池
  68. :return:
  69. """
  70. devCache = Device.get_dev_control_cache(self._devNo)
  71. currentUseInfo = devCache.get("currentUseInfo", dict())
  72. portStr = self.event_data.get("portStr")
  73. batterySn = self.event_data.get("batteryImei")
  74. # 开启放入电池的电池编号
  75. try:
  76. self.deviceAdapter._turn_on_power(portStr)
  77. except Exception as e:
  78. pass
  79. # 首先检验 currentUseInfo 中的订单信息,如果没有订单信息 则是无效信息 这个地方直接将orderNo弹出 避免再次检测到有效事件
  80. orderNo = currentUseInfo.get("orderNo", "")
  81. if not orderNo:
  82. return
  83. try:
  84. ConsumeRecord.objects.get(orderNo = orderNo)
  85. except DoesNotExist:
  86. logger.error("orderNo does not exists, orderNo is <{}>".format(orderNo))
  87. # 没有订单的关门事件 不做任何处理 保留一切信息
  88. return
  89. # 首先检验当前使用信息中是否有 chargeIndex2 如果说已经存在chargeIndex2的情况下 那就一定是第二次上报的关门
  90. chargeIndex1 = currentUseInfo.get("chargeIndex1")
  91. chargeIndex2 = currentUseInfo.get("chargeIndex2")
  92. # 不应该出现的情况
  93. if not chargeIndex1 and not chargeIndex2:
  94. # 将下一个用户的扫码开启端口设置为None 下次扫码的时候再次获取
  95. currentUseInfo.pop("orderNo", None)
  96. Device.clear_port_control_cache(self._devNo, "currentUseInfo")
  97. Device.update_dev_control_cache(self._devNo, {"currentUseInfo": currentUseInfo})
  98. logger.error("currentUseInfo chargeIndex info missing, curInfo is {}".format(currentUseInfo))
  99. return
  100. # 第一次的关门事件
  101. # 2020-9-27 安琦新增需求 用户放入电池不是上一次取走的电池 直接给经销商告警
  102. elif chargeIndex1 and not chargeIndex2:
  103. # 上报的信息不是本订单的信息,直接忽略
  104. if portStr != chargeIndex1:
  105. self.delay_clear_current_info()
  106. return
  107. logger.info("<{}> order first closed".format(orderNo))
  108. # 反复检查电池是否已经放入了
  109. if not batterySn:
  110. batterySn = self.get_battery_sn(portStr)
  111. # 再次获取之后还是没有找到电池的SN说明电池没有被放入, 这个地方不应该在进行下一步的操作,直接将此次的行为关闭掉
  112. if not batterySn:
  113. consumeDict = {
  114. "chargeIndex2": "-1",
  115. "oldBatteryImei": "-1",
  116. "newBatteryImei": "-1",
  117. "chargeIndex1": chargeIndex1
  118. }
  119. ServiceProgress.update_progress_and_consume_rcd(
  120. self.device["ownerId"],
  121. {
  122. "open_id": currentUseInfo.get("openId"),
  123. "device_imei": self.device["devNo"],
  124. "isFinished": False
  125. },
  126. consumeDict
  127. )
  128. # 通知用户服务结束 不清除使用信息 需要经销商介入
  129. consumeDict.update({"openId": currentUseInfo.get("openId")})
  130. self.notify_to_user(consumeDict)
  131. currentUseInfo.pop("orderNo", None)
  132. Device.clear_port_control_cache(self._devNo, "currentUseInfo")
  133. Device.update_dev_control_cache(self._devNo, {"currentUseInfo": currentUseInfo})
  134. # 由于用户没有放入电池 这个地方下一个开启的, nextPort不变 延时后清除正在使用的信息
  135. self.delay_clear_current_info()
  136. return
  137. # 获取到了相应的电池的sn,继续下一步服务 进行柜门的开启, 开启的柜门在用户开启空门的时候已经确定下来了,在currentUseInfo字段里
  138. else:
  139. # 获取上一次的消费记录里面的电池编号
  140. openId = currentUseInfo.get("openId", "")
  141. lastBatterySn = Battery.get_user_last_battery_sn(openId, self.device.ownerId)
  142. if lastBatterySn and lastBatterySn != batterySn:
  143. self.warning_to_dealer_diff_battery(batterySn, lastBatterySn, openId)
  144. battery = Battery.get_one(self.device.ownerId, batterySn)
  145. if battery and battery.disable:
  146. consumeDict = {
  147. "chargeIndex2": "-1",
  148. "oldBatteryImei": batterySn,
  149. "newBatteryImei": "-1",
  150. "chargeIndex1": chargeIndex1
  151. }
  152. ServiceProgress.update_progress_and_consume_rcd(
  153. self.device["ownerId"],
  154. {
  155. "open_id": currentUseInfo.get("openId"),
  156. "device_imei": self.device["devNo"],
  157. "isFinished": False
  158. },
  159. consumeDict
  160. )
  161. # 通知用户服务结束 不清除使用信息 需要经销商介入
  162. consumeDict.update({"openId": currentUseInfo.get("openId")})
  163. self.notify_to_user(consumeDict)
  164. currentUseInfo.pop("orderNo", None)
  165. Device.clear_port_control_cache(self._devNo, "currentUseInfo")
  166. Device.update_dev_control_cache(self._devNo, {"currentUseInfo": currentUseInfo})
  167. # 将下一个空仓 置空
  168. self.update_next_port(None)
  169. self.delay_clear_current_info()
  170. # TODO 告知经销商 警告电池已经被锁定
  171. logger.info("disable battery <{}> has been locked!".format(battery.batterySn))
  172. self.warning_to_dealer_disable_battery(batterySn, openId)
  173. return
  174. chargeIndex2 = currentUseInfo.pop("toOpenChargeIndex")
  175. batterySn2 = currentUseInfo.pop("toOpenBattery")
  176. try:
  177. self.deviceAdapter._open_door(chargeIndex2)
  178. except ServiceException:
  179. # 有可能出现的是模块和设备交互失败导致开门失败 或者网络通信不好,这个时候直接通知用户联系经销商处理
  180. consumeDict = {
  181. "chargeIndex2": "-1",
  182. "oldBatteryImei": batterySn,
  183. "newBatteryImei": "-1",
  184. "chargeIndex1": chargeIndex1
  185. }
  186. ServiceProgress.update_progress_and_consume_rcd(
  187. self.device["ownerId"],
  188. {
  189. "open_id": currentUseInfo.get("openId"),
  190. "device_imei": self.device["devNo"],
  191. "isFinished": False
  192. },
  193. consumeDict
  194. )
  195. # 通知用户服务结束 但不清除用户的使用信息 此时一定需要经销商介入处理
  196. consumeDict.update({"openId": currentUseInfo.get("openId")})
  197. self.notify_to_user(consumeDict)
  198. currentUseInfo.pop("orderNo", None)
  199. Device.clear_port_control_cache(self._devNo, "currentUseInfo")
  200. Device.update_dev_control_cache(self._devNo, {"currentUseInfo": currentUseInfo})
  201. self.delay_clear_current_info()
  202. # 将下一个空仓 置空
  203. self.update_next_port(None)
  204. return
  205. currentUseInfo.update({
  206. "chargeIndex2": chargeIndex2,
  207. "oldBatteryImei": batterySn,
  208. "newBatteryImei": batterySn2
  209. })
  210. Device.update_dev_control_cache(self._devNo, {"currentUseInfo": currentUseInfo})
  211. self.update_next_port(chargeIndex2)
  212. # 这个地方更新电池的信息
  213. logger.error("the first battery sn is {}".format(batterySn))
  214. battery = Battery.get_one(self.device.ownerId, batterySn)
  215. if battery:
  216. battery.update_dev_info(self.device.devNo, portStr)
  217. return
  218. # 不应该出现的情况
  219. elif not chargeIndex1 and chargeIndex2:
  220. currentUseInfo.pop("orderNo", None)
  221. Device.clear_port_control_cache(self._devNo, "currentUseInfo")
  222. Device.update_dev_control_cache(self._devNo, {"currentUseInfo": currentUseInfo})
  223. logger.error("chargeIndex1 missing but chargeIndex2 exists, curInfo is {}".format(currentUseInfo))
  224. return
  225. # 第二次的关门事件
  226. else:
  227. # 上报的信息不是本订单的信息,直接忽略
  228. if portStr != chargeIndex2:
  229. return
  230. # 第二次关门的时候也需要检测里面是否有电池信息,是为了nextPort 防止nextPort开启有电池的门, 但是不再需要反复检测
  231. consumeDict = {
  232. "chargeIndex2": currentUseInfo.get("chargeIndex2"),
  233. "oldBatteryImei": currentUseInfo.get("oldBatteryImei"),
  234. "newBatteryImei": currentUseInfo.get("newBatteryImei"),
  235. "chargeIndex1": currentUseInfo.get("chargeIndex1")
  236. }
  237. ServiceProgress.update_progress_and_consume_rcd(
  238. self.device["ownerId"],
  239. {
  240. "open_id": currentUseInfo.get("openId"),
  241. "device_imei": self.device["devNo"],
  242. "isFinished": False
  243. },
  244. consumeDict
  245. )
  246. consumeDict.update({"openId": currentUseInfo.get("openId")})
  247. self.notify_to_user(consumeDict)
  248. # 如果用户有放入电池 这个地方就是正常的结束 清空使用信息,下一个用户继续使用 否则不清空,等待经销商介入
  249. if not batterySn:
  250. Device.clear_port_control_cache(self._devNo, "currentUseInfo")
  251. # 将电池信息更新
  252. battery = Battery.get_one(dealerId=self.device.ownerId, batterySn=currentUseInfo.get("newBatteryImei"))
  253. if battery:
  254. battery.update_user_info(currentUseInfo.get("openId"))
  255. else:
  256. # 保留一段时间的信息
  257. currentUseInfo.pop("orderNo", None)
  258. Device.clear_port_control_cache(self._devNo, "currentUseInfo")
  259. Device.update_dev_control_cache(self._devNo, {"currentUseInfo": currentUseInfo})
  260. self.delay_clear_current_info()
  261. # 电池还在柜子里面
  262. battery = Battery.get_one(dealerId=self.device.ownerId, batterySn=batterySn)
  263. if battery:
  264. battery.update_dev_info(self.device.devNo, portStr)
  265. self.update_next_port(portStr)
  266. # 更新下一个开门的端口
  267. logger.info("<{}> order second closed".format(orderNo))
  268. def update_next_port(self, nextPort):
  269. """
  270. 更新下一个开启的端口
  271. :param nextPort:
  272. :return:
  273. """
  274. otherConf = self.device.get("otherConf", dict())
  275. otherConf.update({"nextPort": nextPort})
  276. Device.objects.filter(devNo = self._devNo).update(otherConf = otherConf)
  277. Device.invalid_device_cache(self._devNo)
  278. def get_battery_sn(self, port):
  279. """
  280. 重复三次获取电池的SN,直到获取到了为止,超过三次还没获取到 就是真的没获取到,避免主板检测延迟问题
  281. :param port: 查询batterySn的端口号
  282. :return:
  283. """
  284. for times in xrange(1, 3):
  285. time.sleep(times)
  286. batterySn = self.deviceAdapter._query_battery_imei(port)
  287. if batterySn:
  288. return batterySn
  289. return None
  290. def warning_to_dealer(self, portStr, batterySn):
  291. """
  292. 向经销商告警 有非法柜门打开事件
  293. :return:
  294. """
  295. group = Group.get_group(self.device.get("groupId", ""))
  296. if batterySn:
  297. fault = u"{} 号柜门 ,内有电池, 电池编号为:{}".format(portStr, batterySn)
  298. else:
  299. fault = u"{} 号柜门, 内无电池".format(portStr)
  300. self.notify_dealer(
  301. templateName = "device_fault",
  302. title = "您的设备柜门被手动打开,如非设备人员正常检修,建议您去现场看看",
  303. device = u"{groupNumber}组-{logicalCode}".format(groupNumber=self.device["groupNumber"],
  304. logicalCode=self.device["logicalCode"]),
  305. location = u"{address}-{groupName}".format(address = group["address"], groupName = group["groupName"]),
  306. fault = fault,
  307. notifyTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  308. )
  309. def warning_to_dealer_diff_battery(self, batterySn, lastBatterySn, openId):
  310. """
  311. 经销告警 新旧电池不一样
  312. :param batterySn:
  313. :param lastBatterySn:
  314. :param openId:
  315. :return:
  316. """
  317. user = MyUser.objects.filter(openId=openId, groupId=self.device.groupId).first()
  318. activeInfo = MyUser.get_active_info(openId, agentId=user.agentId)
  319. phoneNumber = activeInfo.get("phoneNumber")
  320. group = Group.get_group(self.device.get("groupId", ""))
  321. fault = "当前放入电池编号 {}, 上次取出电池 {}, 用户 {} 联系方式 {}".format(batterySn, lastBatterySn, user.nickname, phoneNumber)
  322. self.notify_dealer(
  323. templateName = "device_fault",
  324. title = "用户电池编号出现异常",
  325. device = u"{groupNumber}组-{logicalCode}".format(groupNumber = self.device["groupNumber"],
  326. logicalCode = self.device["logicalCode"]),
  327. location = u"{address}-{groupName}".format(address = group["address"], groupName = group["groupName"]),
  328. fault = fault,
  329. notifyTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  330. )
  331. def warning_to_dealer_disable_battery(self, batterySn, openId):
  332. """
  333. 通知经销商 被锁定的电池已经入柜子 请及时的解锁
  334. :param batterySn:
  335. :param openId:
  336. :return:
  337. """
  338. user = MyUser.objects.filter(openId=openId, groupId=self.device.groupId).first()
  339. activeInfo = MyUser.get_active_info(openId, agentId=user.agentId)
  340. phoneNumber = activeInfo.get("phoneNumber")
  341. group = Group.get_group(self.device.get("groupId", ""))
  342. fault = "当前放入电池编号 {}, 用户 {} 联系方式 {}".format(batterySn, user.nickname, phoneNumber)
  343. self.notify_dealer(
  344. templateName = "device_fault",
  345. title = "禁用电池已被锁定",
  346. device = u"{groupNumber}组-{logicalCode}".format(groupNumber = self.device["groupNumber"],
  347. logicalCode = self.device["logicalCode"]),
  348. location = u"{address}-{groupName}".format(address = group["address"], groupName = group["groupName"]),
  349. fault = fault,
  350. notifyTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  351. )
  352. def notify_to_user(self, currentUseInfo):
  353. """
  354. 根据用户的换电行为提醒用户
  355. :param currentUseInfo:
  356. :return:
  357. """
  358. openId = currentUseInfo.get("openId")
  359. if not openId:
  360. return
  361. groupId = self.device.get("groupId")
  362. group = Group.get_group(groupId)
  363. user = MyUser.objects.filter(openId = openId, groupId = groupId).first()
  364. if not group or not user:
  365. return
  366. chargeIndex1 = currentUseInfo.get("chargeIndex1")
  367. chargeIndex2 = currentUseInfo.get("chargeIndex2")
  368. batterySn1 = currentUseInfo.get("oldBatteryImei")
  369. batterySn2 = currentUseInfo.get("newBatteryImei")
  370. numChecker = lambda x: False if x == "-1" else True
  371. # 整个流程走完了
  372. if numChecker(chargeIndex1) and numChecker(chargeIndex2) and numChecker(batterySn1) and numChecker(batterySn2):
  373. title = u"\\n\\n设备编号:\\t\\t{logicalCode}\\n\\n服务地址:\\t\\t{group}\\n\\n放入:\\t\\t{p1}端口-{oldB}电池\\n\\n取出:\\t\\t{p2}端口-{newB}电池".format(
  374. logicalCode = self.device.get("logicalCode", ""),
  375. group = group.get("groupName", ""),
  376. p1 = chargeIndex1,
  377. oldB = batterySn1,
  378. p2 = chargeIndex2,
  379. newB = batterySn2,
  380. )
  381. # 放入了电池但是开启有电池门的时候故障了
  382. elif numChecker(chargeIndex1) and not numChecker(chargeIndex2) and numChecker(batterySn1) and not numChecker(batterySn2):
  383. title = u"\\n\\n设备编号:\\t\\t{logicalCode}\\n\\n服务地址:\\t\\t{group}\\n\\n放入:\\t\\t{p1}端口-{oldB}电池".format(
  384. logicalCode = self.device.get("logicalCode", ""),
  385. group = group.get("groupName", ""),
  386. p1 = chargeIndex1,
  387. oldB = batterySn1,
  388. )
  389. # 空柜门没有放入电池
  390. elif numChecker(chargeIndex1) and not numChecker(chargeIndex2) and not numChecker(batterySn1) and not numChecker(batterySn2):
  391. title = u"\\n\\n设备编号:\\t\\t{logicalCode}\\n\\n服务地址:\\t\\t{group}\\n\\n放入:\\t\\t{p1}端口-未放入电池".format(
  392. logicalCode = self.device.get("logicalCode", ""),
  393. group = group.get("groupName", ""),
  394. p1 = chargeIndex1,
  395. )
  396. # 非法的情况
  397. else:
  398. logger.error("invalid notify to user, curInfo is <{}>".format(currentUseInfo))
  399. return
  400. self.notify_user(
  401. managerialOpenId = user.managerialOpenId if user else "",
  402. templateName = "service_complete",
  403. title = title,
  404. service = u"换电柜服务",
  405. remark = u'谢谢您的支持',
  406. finishTime = datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S")
  407. )
  408. def delay_clear_current_info(self):
  409. """
  410. 延时清除客户的使用信息,一般发生在换电错误的时候去处理
  411. :return:
  412. """
  413. seconds = int(self.device.get("otherConf", dict()).get("delayTime", 1)) * 60
  414. delay_async_operation(AnQiEvent.delay_func, seconds, self._devNo)
  415. @staticmethod
  416. def delay_func(devNo):
  417. """
  418. 延时函数
  419. :param devNo:
  420. :return:
  421. """
  422. Device.clear_port_control_cache(devNo, "currentUseInfo")