jndz_policy.py 14 KB

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