xkd_policy.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. from typing import TYPE_CHECKING
  6. from apilib.monetary import RMB
  7. from apilib.utils_string import make_title_from_dict
  8. from apps.web.constant import DeviceCmdCode, CONSUMETYPE
  9. from apps.web.core.device_define.jndz import CMD_CODE
  10. from apps.web.core.exceptions import ServiceException
  11. from apps.web.core.networking import MessageSender
  12. from apps.web.device.models import Device
  13. from apps.web.eventer.base import FaultEvent, WorkEvent
  14. from apps.web.eventer import EventBuilder
  15. from apps.web.eventer.policy_common import PolicyComNetPayAckEvent, PolicyOnlineCardStartAckEvent, \
  16. StartAckEventPreProcessor
  17. from apps.web.user.models import CardRechargeOrder, Card
  18. if TYPE_CHECKING:
  19. pass
  20. logger = logging.getLogger(__name__)
  21. class builder(EventBuilder):
  22. def __getEvent__(self, device_event):
  23. # 订单机制事件
  24. if 'order_id' in device_event:
  25. if device_event['order_type'] == 'com_start':
  26. return MyComNetPayAckEvent(self.deviceAdapter, device_event)
  27. if device_event['order_type'] == 'ic_recharge':
  28. pass
  29. if device_event['order_type'] == 'id_start':
  30. return OnlineCardStartAckEvent(self.deviceAdapter, device_event)
  31. if device_event['order_type'] == 'card_refund':
  32. pass
  33. else:
  34. if 'event_type' in device_event:
  35. if device_event['event_type'] == 'card':
  36. return CardEvent(self.deviceAdapter, device_event)
  37. event_data = self.deviceAdapter.analyze_event_data(device_event)
  38. if event_data is None or 'cmdCode' not in event_data:
  39. return None
  40. if event_data['cmdCode'] in [
  41. # todo 0A的告警不要了,先注释掉,后续删除
  42. # CMD_CODE.DEVICE_SUBMIT_DEVICE_FAULT_0A,
  43. CMD_CODE.DEVICE_FAULT_TEMPERATURE,
  44. CMD_CODE.DEVICE_FAULT_POWER,
  45. CMD_CODE.DEVICE_FAULT_SMOKE,
  46. CMD_CODE.DEVICE_ELEC,
  47. CMD_CODE.DEVICE_FAULT_ALTER
  48. ]:
  49. return JNDZEventerFailure(self.deviceAdapter, event_data)
  50. class CardEvent(WorkEvent):
  51. def do(self):
  52. if self.event_data['funCode'] == '10':
  53. self._do_get_balance()
  54. if self.event_data['funCode'] == '22':
  55. self._do_get_balance_22()
  56. def _do_get_balance(self):
  57. if self.device.devType.get('features', {}).get('cardNoReverse'):
  58. card_id = self.deviceAdapter.reverse_hex(self.event_data['card_id'])
  59. else:
  60. card_id = self.event_data['card_id']
  61. cardNo = str(int(card_id, 16))
  62. money = self.device.policyTemp.get('forIdcard', {}).get('money', 1)
  63. logger.info('[_do_get_balance] receive cardNo = {}'.format(cardNo))
  64. card = self.update_card_dealer_and_type(cardNo) # type: Card
  65. data = {'funCode': '10', 'card_id': self.event_data['card_id'], 'balance': 0, } # 十六进制卡号
  66. dealer = self.device.owner
  67. # 无主卡或者是卡被冻结
  68. if not card or not card.openId or card.frozen or not dealer:
  69. logger.info('[_do_get_balance] receive cardNo = {}, card invalid!'.format(cardNo))
  70. data.update({'result': 1})
  71. return self.send_mqtt(data=data)
  72. # 是否存在没有到账的余额 进行充值
  73. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  74. self.recharge_id_card(
  75. card=card,
  76. rechargeType='append',
  77. order=card_recharge_order
  78. )
  79. card.reload()
  80. # ongoingList = getattr(card, 'ongoingList', []) # 有冻结未结束的订单
  81. #
  82. # # 先不给做一张卡只能开启一单的限制
  83. if card.balance <= RMB(money):
  84. logger.info(
  85. '[_do_get_balance] receive cardNo = {}, card balance = {} not enough!'.format(cardNo, card.balance))
  86. data.update({'result': 1})
  87. return self.send_mqtt(data=data)
  88. data.update({
  89. 'balance': int(RMB(card.balance) * 10), # 单位: 角
  90. 'attach_paras': {'openId': card.openId},
  91. })
  92. data.update(self.generate_rule())
  93. if RMB(card.balance) > RMB(money):
  94. data.update({'result': 0})
  95. else:
  96. data.update({'result': 1})
  97. self.send_mqtt(data=data)
  98. def send_mqtt(self, data=None, cmd=DeviceCmdCode.OPERATE_DEV_SYNC):
  99. """
  100. 发送mqtt 指令默认210 返回data
  101. """
  102. result = MessageSender.send(self.device, cmd,
  103. data)
  104. if 'rst' in result and result['rst'] != 0:
  105. if result['rst'] == -1:
  106. raise ServiceException(
  107. {'result': 2, 'description': u'该设备正在玩命找网络,请您稍候再试', 'rst': -1})
  108. elif result['rst'] == 1:
  109. raise ServiceException(
  110. {'result': 2, 'description': u'该设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能', 'rst': 1})
  111. else:
  112. if cmd in [DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE]:
  113. return
  114. return result.get('data', 'ok')
  115. def generate_rule(self):
  116. forIdcard = self.device.policyTemp.get('forIdcard', {})
  117. policyType = forIdcard.get('policyType', 'time')
  118. billingMethod = forIdcard.get('billingMethod', 'prepaid')
  119. account_type = 'real' # account_type(max, real, min) 计费的规则是最大功率计费,还是实时功率计费
  120. if 'card_accumulate_max_power' in self.device.owner.features:
  121. account_type = 'max'
  122. policy = {'billing_method': billingMethod}
  123. if billingMethod == CONSUMETYPE.POSTPAID: # 后付费
  124. if policyType == 'time':
  125. policy.update({
  126. 'rule_type': 'TIME_ELEC_MONEY',
  127. 'rule': {
  128. 'account_type': account_type,
  129. 'step': self.deviceAdapter.get_uart_step(is_card=True),
  130. }})
  131. elif policyType == 'elec':
  132. elecFee = float(forIdcard.get('rule', {}).get('price', 1)) # 电费单价, 用于显示金额
  133. policy.update({
  134. 'rule_type': 'ELEC',
  135. 'rule': {
  136. 'account_type': account_type,
  137. 'elec_fee': int(elecFee * 100)
  138. }
  139. })
  140. else:
  141. raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'})
  142. else: # 预付费
  143. money = forIdcard.get('money', 1)
  144. policy.update({
  145. 'fee': int(money * 100), # 订单的真实扣费. 预付费需要扣费 这里填写fee
  146. })
  147. if policyType == 'time':
  148. policy.update({
  149. 'rule_type': 'MONEY',
  150. 'rule': {
  151. 'account_type': account_type,
  152. 'amount': int(money * 100),
  153. 'step': self.deviceAdapter.get_uart_step(is_card=True),
  154. 'need_time': int(forIdcard.get('maxTime', 720.0) * 60), # 单位: 秒
  155. },
  156. })
  157. elif policyType == 'elec':
  158. elecFee = float(forIdcard.get('rule', {}).get('price', 1)) # 电费单价, 用于显示金额
  159. policy.update({
  160. 'rule_type': 'MONEY_BY_ELEC',
  161. 'rule': {
  162. 'account_type': account_type,
  163. 'amount': int(money * 100), # 启动金额
  164. 'elec_fee': int(elecFee * 100) # 电费单价
  165. },
  166. })
  167. else:
  168. raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'})
  169. return policy
  170. class JNDZEventerFailure(FaultEvent):
  171. def do(self, **args):
  172. cmdCode = self.event_data.get("cmdCode")
  173. faultType = self.event_data.get(u"fault")
  174. desc = self.event_data.get("desc", "")
  175. # todo 0A的告警不要了,先注释掉,后续删除
  176. # 保证原有的故障处理逻辑不变
  177. # if cmdCode == CMD_CODE.DEVICE_SUBMIT_DEVICE_FAULT_0A:
  178. # return self.handler_0A_fault()
  179. # 这些都是整机告警
  180. part = "0"
  181. warningData = {
  182. "warningStatus": 2,
  183. "warningDesc": desc,
  184. "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  185. }
  186. Device.update_dev_warning_cache(self.device.devNo, {part: warningData})
  187. group = self.device.group
  188. titleList = [
  189. {u"告警名称": faultType},
  190. {u"地址名称": group["groupName"]}
  191. ]
  192. title = make_title_from_dict(titleList)
  193. # 接下来的都是整机告警,这个地方需要通知到经销商
  194. self.notify_dealer(
  195. "device_fault",
  196. title=title,
  197. device=u" 号设备".format(self.device.logicalCode),
  198. faultType=faultType,
  199. notifyTime=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  200. fault=""
  201. )
  202. # 记录错误故障
  203. fault_record = self.record(
  204. faultCode=cmdCode,
  205. description=desc,
  206. title=faultType,
  207. detail={"faultType": faultType, "errorCode": self.event_data.get("errorCode")}
  208. )
  209. self.north_to_liangxi(fault_record)
  210. # todo 0A的告警不要了,先注释掉,后续删除
  211. # def handler_0A_fault(self):
  212. # faultContent = self.event_data.get('faultContent', '')
  213. # level = self.event_data.get('level', '')
  214. # errCode = self.event_data.get('errCode')
  215. #
  216. # port = self.event_data.get('port')
  217. # if port and port != 255:
  218. # title = u'注意!您的设备{}号端口发出告警!'.format(port)
  219. # part = str(port)
  220. # else:
  221. # title = u'注意!您的设备发出告警!'
  222. # part = "0"
  223. #
  224. # if errCode in ['A3']: # 空载 无需显示在经销商后台
  225. # return
  226. #
  227. # elif errCode in ['00']: # 老设备上报继电器粘连 100206 不上报!!
  228. # return
  229. #
  230. # # 设备告警打入缓存
  231. # elif errCode in ['A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'AA']:
  232. # warningData = {
  233. # "warningStatus": 2,
  234. # "warningDesc": faultContent,
  235. # "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  236. # }
  237. # Device.update_dev_warning_cache(self.device.devNo, {part: warningData})
  238. #
  239. # # 设备告警清除
  240. # elif errCode in ['20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2A']: # 恢复信号 不操作
  241. # Device.clear_part_warning_cache(self.device.devNo, part)
  242. # # ctrInfo = Device.get_dev_control_cache(self.device.devNo)
  243. # # if 'statusInfo' in ctrInfo and 'errCode' in ctrInfo:
  244. # # if ctrInfo['errCode'][-1] == errCode[-1]:
  245. # # ctrInfo['status'] = None
  246. # # ctrInfo['errCode'] = None
  247. # # ctrInfo['faultContent'] = None
  248. # # ctrInfo['level'] = None
  249. # # ctrInfo['statusInfo'] = None
  250. # # ctrInfo['cmdCode'] = None
  251. # # Device.update_dev_control_cache(self.device['devNo'], ctrInfo)
  252. #
  253. # else:
  254. # pass
  255. # # Device.update_dev_control_cache(self.device['devNo'], self.event_data)
  256. #
  257. # # 记录错误故障
  258. # self.record(
  259. # title=title,
  260. # description=faultContent,
  261. # level=level
  262. # )
  263. #
  264. # group = Group.get_group(self.device.groupId)
  265. #
  266. # if self.is_notify_dealer:
  267. # self.notify_dealer(
  268. # templateName="device_fault",
  269. # title=title,
  270. # device=u'{}-{}'.format(self.device.devTypeName, self.device.logicalCode),
  271. # fault=faultContent,
  272. # location=u'{}-{}-{}号设备({})'.format(group["address"], group["groupName"], self.device["groupNumber"],
  273. # self.device["logicalCode"]),
  274. # notifyTime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  275. # )
  276. class AckEventPreProcessor(StartAckEventPreProcessor):
  277. def analysis_reason(self, reason, fault_code=None):
  278. FINISHED_CHARGE_REASON_MAP = {
  279. 0x00: u'购买的充电时间或者电量已经用完',
  280. 0x01: u'系统判断为异常断电(插头被拔或者松动,或者电瓶已经充满),电瓶车充电器种类繁多,可能存在误差',
  281. 0x02: u'电池已经充满',
  282. 0x03: u'警告!您的电池功率超过本机最大限制。为了公共安全,不建议您在该充电桩充电',
  283. 0x04: u'远程断电',
  284. 0x0B: u'设备或是端口出现问题,被迫停止',
  285. # 服务器定义的停止事件
  286. 0xC0: u'计费方式无效',
  287. 0xC1: u'订购套餐已用完',
  288. 0xC2: u'订购时间已用完',
  289. 0xC3: u'订购电量已用完',
  290. 0xC4: u'动态计算功率后, 时间已用完',
  291. 0xC5: u'订单异常,设备可能离线超过1小时, 平台结单',
  292. 0xC6: u'系统检测到充电已结束, 平台结单(0x21)',
  293. 0xC7: u'系统检测到充电已结束, 平台结单(0x0F)',
  294. 0xC8: u'用户远程停止订单',
  295. 0xC9: u'经销商远程停止订单',
  296. 0xCA: u'系统检测到订单已结束, 平台结单(0xCA)',
  297. 0xCB: u'充电时长已达到最大限制(0xCB)',
  298. 0xCC: u'充电电量已达到最大限制(0xCC)',
  299. 0xCD: u'设备升级中... 关闭当前订单',
  300. # 设备自定义的停止时间
  301. 0xD0: u'设备继电器粘连, 结束充电',
  302. 0xD1: u'空载(充电器未连接或充电器插头脱落)',
  303. }
  304. return FINISHED_CHARGE_REASON_MAP.get(reason, '充电结束')
  305. class MyComNetPayAckEvent(PolicyComNetPayAckEvent):
  306. def __init__(self, smartBox, event_data):
  307. super(PolicyComNetPayAckEvent, self).__init__(smartBox, event_data, AckEventPreProcessor())
  308. class OnlineCardStartAckEvent(PolicyOnlineCardStartAckEvent):
  309. def __init__(self, smartBox, event_data):
  310. super(PolicyOnlineCardStartAckEvent, self).__init__(smartBox, event_data, AckEventPreProcessor())