kh_policy.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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 apps.web.constant import DeviceCmdCode, CONSUMETYPE, FAULT_CODE, FAULT_LEVEL
  8. from apps.web.core.exceptions import ServiceException
  9. from apps.web.core.networking import MessageSender
  10. from apps.web.device.models import Device
  11. from apps.web.eventer.base import FaultEvent, WorkEvent, AckEventProcessorIntf
  12. from apps.web.eventer import EventBuilder
  13. from apps.web.eventer.policy_common import PolicyComNetPayAckEvent, PolicyOnlineCardStartAckEvent, \
  14. StartAckEventPreProcessor
  15. from apps.web.user.models import CardRechargeOrder, Card
  16. if TYPE_CHECKING:
  17. from apps.web.device.models import DeviceDict
  18. logger = logging.getLogger(__name__)
  19. class AckEventPreProcessor(StartAckEventPreProcessor):
  20. def analysis_reason(self, reason, fault_code=None):
  21. FINISHED_CHARGE_REASON_MAP = {
  22. 0: u'购买的充电时间或电量用完了。',
  23. 1: u'插头被拔或者松动,或者电瓶已经充满(系统判断为异常断电,电瓶车充电器种类繁多,可能存在误差)',
  24. 2: u'电池已经充满',
  25. 3: u'设备或者端口故障。建议您根据已经充电的时间评估是否需要到现场换到其他端口充电',
  26. 4: u'警告!您的电池功率超过本机最大限制。为了公共安全,不建议您在该充电桩充电',
  27. 5: u'刷卡退费结束',
  28. 6: u'可能是插头被拔掉或者未连接充电器。如果不是自己操作,建议您到现场检查是否有人误操作',
  29. # 7: u'远程方式停止充电。如果不是自己操作,建议到现场尽快恢复充电',
  30. # 服务器定义的停止事件
  31. 0xC0: u'计费方式无效',
  32. 0xC1: u'订购套餐金额已用完',
  33. 0xC2: u'订购时间已用完',
  34. 0xC3: u'订购电量已用完',
  35. 0xC4: u'动态计算功率后, 时间已用完',
  36. 0xC5: u'订单异常,设备可能离线超过1小时, 平台结单',
  37. 0xC6: u'系统检测到充电已结束, 平台结单(0x21)',
  38. 0xC7: u'系统检测到充电已结束, 平台结单(0x15)',
  39. 0xC8: u'用户远程停止订单',
  40. 0xC9: u'经销商远程停止订单',
  41. 0xCA: u'系统检测到充电已结束, 平台结单(0xCA)',
  42. 0xCD: u'设备升级中... 关闭当前订单',
  43. }
  44. return FINISHED_CHARGE_REASON_MAP.get(reason, '充电结束')
  45. class builder(EventBuilder):
  46. def __getEvent__(self, device_event):
  47. if 'order_id' in device_event:
  48. if device_event['order_type'] == 'com_start':
  49. return MyComNetPayAckEvent(self.deviceAdapter, device_event)
  50. if device_event['order_type'] == 'ic_recharge':
  51. pass
  52. if device_event['order_type'] == 'id_start':
  53. return OnlineCardStartAckEvent(self.deviceAdapter, device_event)
  54. if device_event['order_type'] == 'card_refund':
  55. pass
  56. else:
  57. if 'event_type' in device_event:
  58. if device_event['event_type'] == 'card':
  59. return CardEvent(self.deviceAdapter, device_event)
  60. if 'data' not in device_event:
  61. return None
  62. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  63. if event_data is None or 'cmdCode' not in event_data:
  64. return None
  65. if 'today_coins' in device_event and 'ts' in device_event:
  66. event_data.update({'today_coins': device_event['today_coins']})
  67. event_data.update({'ts': device_event['ts']})
  68. if event_data.get('cmdCode') == '35' or event_data.get('cmdCode') == '41':
  69. return KEHANGInductorEvent(self.deviceAdapter, event_data)
  70. return None
  71. class MyComNetPayAckEvent(PolicyComNetPayAckEvent):
  72. def __init__(self, smartBox, event_data):
  73. super(PolicyComNetPayAckEvent, self).__init__(smartBox, event_data, AckEventPreProcessor())
  74. class OnlineCardStartAckEvent(PolicyOnlineCardStartAckEvent):
  75. def __init__(self, smartBox, event_data):
  76. super(PolicyOnlineCardStartAckEvent, self).__init__(smartBox, event_data, AckEventPreProcessor())
  77. class AckEventPreProcessor(StartAckEventPreProcessor):
  78. def analysis_reason(self, reason, fault_code=None):
  79. FINISHED_CHARGE_REASON_MAP = {
  80. 0: u'购买的充电时间或电量用完了。',
  81. 1: u'插头被拔或者松动,或者电瓶已经充满(系统判断为异常断电,电瓶车充电器种类繁多,可能存在误差)',
  82. 2: u'电池已经充满',
  83. 3: u'设备或者端口故障。建议您根据已经充电的时间评估是否需要到现场换到其他端口充电',
  84. 4: u'警告!您的电池功率超过本机最大限制。为了公共安全,不建议您在该充电桩充电',
  85. 5: u'刷卡退费结束',
  86. 6: u'可能是插头被拔掉或者未连接充电器。如果不是自己操作,建议您到现场检查是否有人误操作',
  87. # 7: u'远程方式停止充电。如果不是自己操作,建议到现场尽快恢复充电',
  88. # 服务器定义的停止事件
  89. 0xC1: u'订购套餐金额已用完',
  90. 0xC2: u'订购时间已用完',
  91. 0xC3: u'订购电量已用完',
  92. 0xC4: u'动态计算功率后, 时间已用完',
  93. 0xC5: u'订单异常,设备可能离线超过1小时, 平台结单',
  94. 0xC6: u'系统检测到充电已结束, 平台结单(0x21)',
  95. 0xC7: u'系统检测到充电已结束, 平台结单(0x15)',
  96. 0xC8: u'用户远程停止订单',
  97. 0xC9: u'经销商远程停止订单',
  98. 0xCA: u'系统检测到充电已结束, 平台结单(0xCA)',
  99. }
  100. return FINISHED_CHARGE_REASON_MAP.get(reason, '充电结束')
  101. class CardEvent(WorkEvent):
  102. def do(self):
  103. if self.event_data['funCode'] == '22':
  104. self._do_get_balance()
  105. def _do_get_balance(self):
  106. cardNo = str(int(self.event_data['card_id'], 16))
  107. money = self.event_data['money'] * 0.1
  108. logger.info('[_do_get_balance] receive cardNo = {}'.format(cardNo))
  109. card = self.update_card_dealer_and_type(cardNo) # type: Card
  110. data = {'funCode': '23', 'card_id': self.event_data['card_id'], 'balance': 0, 'card_type': self.event_data['card_type']} # 十六进制卡号
  111. dealer = self.device.owner
  112. # 无主卡或者是卡被冻结
  113. if not card or not card.openId or card.frozen or not dealer:
  114. logger.info('[_do_get_balance] receive cardNo = {}, card invalid!'.format(cardNo))
  115. data.update({'result': 1})
  116. return self.send_mqtt(data=data)
  117. # 是否存在没有到账的余额 进行充值
  118. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  119. self.recharge_id_card(
  120. card=card,
  121. rechargeType='append',
  122. order=card_recharge_order
  123. )
  124. card.reload()
  125. # # 先不给做一张卡只能开启一单的限制
  126. if card.balance <= RMB(money):
  127. logger.info(
  128. '[_do_get_balance] receive cardNo = {}, card balance = {} not enough!'.format(cardNo, card.balance))
  129. data.update({'result': 1})
  130. return self.send_mqtt(data=data)
  131. data.update({
  132. 'balance': int(RMB(card.balance) * 10), # 单位: 角
  133. 'attach_paras': {'openId': card.openId},
  134. })
  135. data.update(self.generate_rule())
  136. if RMB(card.balance) > RMB(money):
  137. data.update({'result': 1})
  138. else:
  139. data.update({'result': 2})
  140. self.send_mqtt(data=data)
  141. def send_mqtt(self, data=None, cmd=DeviceCmdCode.OPERATE_DEV_SYNC):
  142. """
  143. 发送mqtt 指令默认210 返回data
  144. """
  145. result = MessageSender.send(self.device, cmd,
  146. data)
  147. if 'rst' in result and result['rst'] != 0:
  148. if result['rst'] == -1:
  149. raise ServiceException(
  150. {'result': 2, 'description': u'该设备正在玩命找网络,请您稍候再试', 'rst': -1})
  151. elif result['rst'] == 1:
  152. raise ServiceException(
  153. {'result': 2, 'description': u'该设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能', 'rst': 1})
  154. else:
  155. if cmd in [DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE]:
  156. return
  157. return result.get('data', 'ok')
  158. def generate_rule(self):
  159. forIdcard = self.device.policyTemp.get('forIdcard', {})
  160. policyType = forIdcard.get('policyType', 'time')
  161. billingMethod = forIdcard.get('billingMethod', 'prepaid')
  162. account_type = 'real' # account_type(max, real, min) 计费的规则是最大功率计费,还是实时功率计费
  163. if 'card_accumulate_max_power' in self.device.owner.features:
  164. account_type = 'max'
  165. policy = {'billing_method': billingMethod}
  166. if billingMethod == CONSUMETYPE.POSTPAID: # 后付费
  167. if policyType == 'time':
  168. policy.update({
  169. 'rule_type': 'TIME_ELEC_MONEY',
  170. 'rule': {
  171. 'account_type': account_type,
  172. 'step': self.deviceAdapter.get_uart_step(is_card=True)
  173. }})
  174. elif policyType == 'elec':
  175. elecFee = float(forIdcard.get('rule', {}).get('price', 1)) # 电费单价, 用于显示金额
  176. policy.update({
  177. 'rule_type': 'ELEC',
  178. 'rule': {
  179. 'account_type': account_type,
  180. 'elec_fee': int(elecFee * 100)
  181. }
  182. })
  183. else:
  184. raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'})
  185. else: # 预付费
  186. money = self.event_data['money'] * 0.1
  187. if policyType == 'time':
  188. policy.update({
  189. 'rule_type': 'MONEY',
  190. 'rule': {
  191. 'account_type': account_type,
  192. 'amount': int(money * 100),
  193. 'step': self.deviceAdapter.get_uart_step(is_card=True)
  194. },
  195. })
  196. elif policyType == 'elec':
  197. elecFee = float(forIdcard.get('rule', {}).get('price', 1)) # 电费单价, 用于显示金额
  198. policy.update({
  199. 'rule_type': 'MONEY_BY_ELEC',
  200. 'rule': {
  201. 'account_type': account_type,
  202. 'amount': int(money * 100),
  203. 'elec_fee': int(elecFee * 100)
  204. },
  205. })
  206. else:
  207. raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'})
  208. return policy
  209. class CardStartAckEventPreProcessor(AckEventProcessorIntf):
  210. def analysis_reason(self, reason, fault_code=None):
  211. pass
  212. def pre_processing(self, device, event_data):
  213. # type:(DeviceDict, dict)->dict
  214. pass
  215. class KEHANGInductorEvent(FaultEvent):
  216. def do(self, **args):
  217. # 处理数据
  218. voltage = self.event_data.get('voltage', -1)
  219. temperature = self.event_data.get('temperature', -1)
  220. smokeWarning = self.event_data.get('smokeWarning', False)
  221. try:
  222. otherConf = Device.objects.get(devNo=self.device.devNo).otherConf
  223. maxVoltage = otherConf.get('voltageThre', 230)
  224. if voltage >= maxVoltage:
  225. desc = u'当前电压%s伏超过了门限值:%s伏' % (voltage, maxVoltage)
  226. self.record(faultCode=FAULT_CODE.OVER_VOLTAGE, description=desc, title=u'主机电压过高', detail=None,
  227. level=FAULT_LEVEL.CRITICAL)
  228. maxTemperature = otherConf.get('temThre', 60)
  229. if temperature > maxTemperature:
  230. desc = u'当前主机温度%s度超过了门限值:%s度' % (temperature, maxTemperature)
  231. self.record(faultCode=FAULT_CODE.OVER_TEMPERATURE, description=desc, title=u'主机温度过高',
  232. detail=None, level=FAULT_LEVEL.CRITICAL)
  233. if smokeWarning:
  234. desc = u'当前主机出现冒烟,请第一时间确定是否有起火。此告警十万火急,请迅速联系物业、消防相关部门!'
  235. self.record(faultCode=FAULT_CODE.SMOKE, description=desc, title=u'主机出现冒烟', detail=None,
  236. level=FAULT_LEVEL.FATAL)
  237. except Exception as e:
  238. logger.error('some error=%s' % e)
  239. return