dc_policy.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  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.common.models import District
  8. from apps.web.constant import DeviceCmdCode, CONSUMETYPE, FAULT_CODE, FAULT_LEVEL
  9. from apps.web.core.exceptions import ServiceException
  10. from apps.web.core.networking import MessageSender
  11. from apps.web.device.models import Device, PortReport, Group, Part
  12. from apps.web.eventer.base import FaultEvent, WorkEvent, AckEventProcessorIntf
  13. from apps.web.eventer import EventBuilder
  14. from apps.web.eventer.policy_common import PolicyComNetPayAckEvent, PolicyOnlineCardStartAckEvent, \
  15. StartAckEventPreProcessor
  16. from apps.web.south_intf.liangxi_fire import LiangXiXiaoFang
  17. from apps.web.south_intf.zhejiang_fire import send_event_to_zhejiang
  18. from apps.web.user.models import CardRechargeOrder, Card
  19. if TYPE_CHECKING:
  20. from apps.web.device.models import DeviceDict
  21. logger = logging.getLogger(__name__)
  22. class builder(EventBuilder):
  23. def __getEvent__(self, device_event):
  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. if 'data' not in device_event:
  38. return None
  39. # 100228 互感器事件处理
  40. if 'type' in device_event:
  41. if device_event['type'] == 'alert':
  42. return InteroperatorAlertEvent(self.deviceAdapter, device_event)
  43. if device_event['type'] == 'report':
  44. return InteroperatorReport(self.deviceAdapter, device_event)
  45. else:
  46. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  47. if event_data is None or 'cmdCode' not in event_data:
  48. return None
  49. if 'today_coins' in device_event and 'ts' in device_event:
  50. event_data.update({'today_coins': device_event['today_coins']})
  51. event_data.update({'ts': device_event['ts']})
  52. if event_data.get('cmdCode') == '35' or event_data.get('cmdCode') == '41':
  53. return ZHIXIA2InductorEvent(self.deviceAdapter, event_data)
  54. return None
  55. class CardEvent(WorkEvent):
  56. def do(self):
  57. if self.event_data['funCode'] == '22':
  58. self._do_get_balance()
  59. def _do_get_balance(self):
  60. cardNo = str(int(self.event_data['card_id'], 16))
  61. money = self.event_data['money'] * 0.1
  62. logger.info('[_do_get_balance] receive cardNo = {}'.format(cardNo))
  63. card = self.update_card_dealer_and_type(cardNo) # type: Card
  64. data = {'funCode': '23', 'card_id': self.event_data['card_id'], 'balance': 0, 'card_type': self.event_data['card_type']} # 十六进制卡号
  65. dealer = self.device.owner
  66. # 无主卡或者是卡被冻结
  67. if not card or not card.openId or card.frozen or not dealer:
  68. logger.info('[_do_get_balance] receive cardNo = {}, card invalid!'.format(cardNo))
  69. data.update({'result': 1})
  70. return self.send_mqtt(data=data)
  71. # 是否存在没有到账的余额 进行充值
  72. card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id))
  73. self.recharge_id_card(
  74. card=card,
  75. rechargeType='append',
  76. order=card_recharge_order
  77. )
  78. card.reload()
  79. # ongoingList = getattr(card, 'ongoingList', []) # 有冻结未结束的订单
  80. #
  81. # # 先不给做一张卡只能开启一单的限制
  82. if card.balance <= RMB(money):
  83. logger.info(
  84. '[_do_get_balance] receive cardNo = {}, card balance = {} not enough!'.format(cardNo, card.balance))
  85. data.update({'result': 1})
  86. return self.send_mqtt(data=data)
  87. data.update({
  88. 'balance': int(RMB(card.balance) * 10), # 单位: 角
  89. 'attach_paras': {'openId': card.openId},
  90. })
  91. data.update(self.generate_rule())
  92. if RMB(card.balance) > RMB(money):
  93. data.update({'result': 1})
  94. else:
  95. data.update({'result': 2})
  96. self.send_mqtt(data=data)
  97. def send_mqtt(self, data=None, cmd=DeviceCmdCode.OPERATE_DEV_SYNC):
  98. """
  99. 发送mqtt 指令默认210 返回data
  100. """
  101. result = MessageSender.send(self.device, cmd,
  102. data)
  103. if 'rst' in result and result['rst'] != 0:
  104. if result['rst'] == -1:
  105. raise ServiceException(
  106. {'result': 2, 'description': u'该设备正在玩命找网络,请您稍候再试', 'rst': -1})
  107. elif result['rst'] == 1:
  108. raise ServiceException(
  109. {'result': 2, 'description': u'该设备忙,无响应,请您稍候再试。也可能是您的设备版本过低,暂时不支持此功能', 'rst': 1})
  110. else:
  111. if cmd in [DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, DeviceCmdCode.PASSTHROUGH_OPERATE_DEV_NO_RESPONSE]:
  112. return
  113. return result.get('data', 'ok')
  114. def generate_rule(self):
  115. forIdcard = self.device.policyTemp.get('forIdcard', {})
  116. policyType = forIdcard.get('policyType', 'time')
  117. billingMethod = forIdcard.get('billingMethod', 'prepaid')
  118. account_type = 'real' # account_type(max, real, min) 计费的规则是最大功率计费,还是实时功率计费
  119. if 'card_accumulate_max_power' in self.device.owner.features:
  120. account_type = 'max'
  121. policy = {'billing_method': billingMethod}
  122. if billingMethod == CONSUMETYPE.POSTPAID: # 后付费
  123. if policyType == 'time':
  124. policy.update({
  125. 'rule_type': 'TIME_ELEC_MONEY',
  126. 'rule': {
  127. 'account_type': account_type,
  128. 'step': self.deviceAdapter.get_uart_step(is_card=True)
  129. }})
  130. elif policyType == 'elec':
  131. elecFee = float(forIdcard.get('rule', {}).get('price', 1)) # 电费单价, 用于显示金额
  132. policy.update({
  133. 'rule_type': 'ELEC',
  134. 'rule': {
  135. 'account_type': account_type,
  136. 'elec_fee': int(elecFee * 100)
  137. }
  138. })
  139. else:
  140. raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'})
  141. else: # 预付费
  142. money = self.event_data['money'] * 0.1
  143. if policyType == 'time':
  144. policy.update({
  145. 'rule_type': 'MONEY',
  146. 'rule': {
  147. 'account_type': account_type,
  148. 'amount': int(money * 100),
  149. 'step': self.deviceAdapter.get_uart_step(is_card=True)
  150. },
  151. })
  152. elif policyType == 'elec':
  153. elecFee = float(forIdcard.get('rule', {}).get('price', 1)) # 电费单价, 用于显示金额
  154. policy.update({
  155. 'rule_type': 'MONEY_BY_ELEC',
  156. 'rule': {
  157. 'account_type': account_type,
  158. 'amount': int(money * 100),
  159. 'elec_fee': int(elecFee * 100)
  160. },
  161. })
  162. else:
  163. raise ServiceException({'result': 2, 'description': u'套餐单位错误,请联系经销商'})
  164. return policy
  165. class ZHIXIA2InductorEvent(FaultEvent):
  166. def do(self, **args):
  167. # 处理数据
  168. voltage = self.event_data.get('voltage', -1)
  169. temperature = self.event_data.get('temperature', -1)
  170. smokeWarning = self.event_data.get('smokeWarning', False)
  171. try:
  172. otherConf = Device.objects.get(devNo=self.device.devNo).otherConf
  173. maxVoltage = otherConf.get('voltageThre', 230)
  174. if voltage >= maxVoltage:
  175. desc = u'当前电压%s伏超过了门限值:%s伏' % (voltage, maxVoltage)
  176. self.record(faultCode=FAULT_CODE.OVER_VOLTAGE, description=desc, title=u'主机电压过高', detail=None,
  177. level=FAULT_LEVEL.CRITICAL)
  178. maxTemperature = otherConf.get('temThre', 60)
  179. if temperature > maxTemperature:
  180. desc = u'当前主机温度%s度超过了门限值:%s度' % (temperature, maxTemperature)
  181. self.record(faultCode=FAULT_CODE.OVER_TEMPERATURE, description=desc, title=u'主机温度过高',
  182. detail=None, level=FAULT_LEVEL.CRITICAL)
  183. self.send_fault_to_xf()
  184. if smokeWarning:
  185. desc = u'当前主机出现冒烟,请第一时间确定是否有起火。此告警十万火急,请迅速联系物业、消防相关部门!'
  186. self.record(faultCode=FAULT_CODE.SMOKE, description=desc, title=u'主机出现冒烟', detail=None,
  187. level=FAULT_LEVEL.FATAL)
  188. self.send_fault_to_xf()
  189. except Exception as e:
  190. logger.error('some error=%s' % e)
  191. return
  192. def send_fault_to_xf(self):
  193. try:
  194. code = self.event_data.get('cmdCode')
  195. # 无锡 梁溪区 粤万通 消防对接 推送
  196. if code == '41':
  197. faultCode = '02'
  198. faultContent = u'烟雾报警'
  199. elif code == '35':
  200. faultCode = '01'
  201. faultContent = u'温度报警'
  202. else:
  203. return
  204. from taskmanager.mediator import task_caller
  205. task_caller('send_to_xf_falut',
  206. dealerId=self.device.ownerId,
  207. devNo=self.device.devNo,
  208. faultCode=faultCode,
  209. faultContent=faultContent)
  210. except Exception:
  211. pass
  212. class InteroperatorAlertEvent(FaultEvent):
  213. def __Analyze_alert_data(self, data):
  214. alertInfo = {'cmdCode': data['cmd'], 'logicalCode': self.device['logicalCode']}
  215. address = Group.get_group(self.device['groupId'])['address']
  216. # 这里判断数据格式
  217. if 'status' not in data:
  218. logger.error('Data arrays have no keywords status')
  219. return
  220. # 这里做漏电告警处理
  221. if '5' in data['status']:
  222. electricityNum = str(int(data['values'][0:4], 16)) + 'mA'
  223. alertInfo['electricity'] = {'electricityNum': electricityNum,
  224. 'address': address,
  225. 'reasonCode': '12',
  226. 'reason': u'在{}编号为{}发生漏电,漏电量为{}'
  227. .format(address, self.device['logicalCode'], electricityNum)}
  228. # 这里做高温告警处理
  229. if '6' in data['status']:
  230. temperatureAccess = [index for index, acces in enumerate(data['status'], 1) if acces == '6']
  231. temperatureAlertList = []
  232. for i in temperatureAccess:
  233. temperatureValue = str(int(data['values'][(i - 1) * 4:(i - 1) * 4 + 4], 16))
  234. temperatureAlertList.append(
  235. {'temperatureValue': temperatureValue,
  236. 'address': address,
  237. 'reasonCode': '11',
  238. 'reason': u'在{}编号为{}的设备有高温预警,当前温度为{}摄氏度'
  239. .format(address, self.device['logicalCode'], temperatureValue)})
  240. alertInfo['temperature'] = temperatureAlertList
  241. return alertInfo
  242. def do(self, **args):
  243. # 判断不存在的设备网上报
  244. if not self.device.ownerId:
  245. logger.error('This device cant find a dealer')
  246. return
  247. # 是否存在温感和电感
  248. temperaturePart = Part.objects(logicalCode=self.device['logicalCode'], partType='3001')
  249. electricityPart = Part.objects(logicalCode=self.device['logicalCode'], partType='3002')
  250. if not temperaturePart.count() or not electricityPart.count():
  251. logger.error(
  252. 'There are no transformers in the locigalcode {} equipment'.format(self.device['logicalCode']))
  253. return
  254. # 处理数据
  255. eventInfo = self.__Analyze_alert_data(self.event_data['data'])
  256. try:
  257. # 先处理高温情况
  258. if 'temperature' in eventInfo:
  259. for InfoDetail in eventInfo['temperature']:
  260. send_event_to_zhejiang(self.dealer, self.device, InfoDetail, partId=temperaturePart[0].id)
  261. # 提示用户
  262. group = Group.get_group(self.device['groupId'])
  263. self.notify_dealer('device_fault', **{
  264. 'title': u'注意!注意!您的设备发生故障',
  265. 'device': u'组号::%s, 二维码编号:%s' % (self.device['groupNumber'], self.device['logicalCode']),
  266. 'location': u'组名称:%s, 地址:%s' % (group['groupName'], group['address']),
  267. 'fault': InfoDetail['reason'],
  268. 'notifyTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  269. })
  270. # 上报高温至消防
  271. # if self.device["ownerId"] in ("5b4ed32e8732d67bd0626528", "5b6c29388732d669f3ae6f94"):
  272. group = Group.get_group(self.device['groupId'])
  273. districtInfo = District.get_district(group["districtId"])
  274. self.device.update({"districtInfo": districtInfo, "groupAddr": group["address"]})
  275. LiangXiXiaoFang.send_to_xf_fault(self.device, "01", u"设备温度过高")
  276. # 处理漏电情况
  277. elif 'electricity' in eventInfo:
  278. # 获取漏电告警插件
  279. send_event_to_zhejiang(self.dealer, self.device, eventInfo['electricity'],
  280. partId=electricityPart[0].id)
  281. # 提示用户
  282. group = Group.get_group(self.device['groupId'])
  283. self.notify_dealer('device_fault', **{
  284. 'title': u'注意!注意!您的设备发生故障',
  285. 'device': u'组号::%s, 二维码编号:%s' % (self.device['groupNumber'], self.device['logicalCode']),
  286. 'location': u'组名称:%s, 地址:%s' % (group['groupName'], group['address']),
  287. 'fault': eventInfo['electricity']['reason'],
  288. 'notifyTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  289. })
  290. # 上报漏电至消防
  291. # if self.device["ownerId"] in ("5b4ed32e8732d67bd0626528", "5b6c29388732d669f3ae6f94"):
  292. group = Group.get_group(self.device['groupId'])
  293. districtInfo = District.get_district(group["districtId"])
  294. self.device.update({"districtInfo": districtInfo, "groupAddr": group["address"]})
  295. LiangXiXiaoFang.send_to_xf_fault(self.device, "04", u"设备发生漏电")
  296. except:
  297. logger.error('Array {} nonspecification'.format(eventInfo))
  298. return
  299. self.record(detail=eventInfo)
  300. class InteroperatorReport(WorkEvent):
  301. def do(self, **args):
  302. if 'type' not in self.event_data:
  303. logger.error('Array {} is not format,lose a key named "type"'.format(self.event_data))
  304. if self.event_data.get('type') == 'report':
  305. devReportDict = {'logicalCode': 'logicalCode', 'time': self.event_data['time_stamp'], 'portInfo': {}}
  306. temperature = ''
  307. voltage = 220
  308. try:
  309. # 拿到个数判断是不是第一次
  310. reportNum = PortReport.get_collection().find({
  311. 'logicalCode': self.device['logicalCode']
  312. }).sort('time', -1).count()
  313. if reportNum:
  314. # 获取上一次存储的信息
  315. reportLast = PortReport.get_collection().find({
  316. 'logicalCode': self.device['logicalCode']
  317. }).sort('time', -1)[0]
  318. for ii in range(10):
  319. power = self.__saveDate(1, msgDict=self.event_data, ii=ii)
  320. if power:
  321. electricity = float(power) / voltage / 10
  322. else:
  323. electricity = reportLast['portInfo'][str(ii + 1)]['electricity']
  324. temperatureR = self.__saveDate(2, msgDict=self.event_data, ii=ii, electricity=electricity,
  325. devReportDict=devReportDict)
  326. if temperatureR:
  327. temperature = temperatureR
  328. devReportDict.update({'temperature': temperature})
  329. # 查看现在的跟以前差距多少
  330. timeInterval = devReportDict['time'] - reportLast['time']
  331. if timeInterval > 2:
  332. PortReportNewList = [
  333. {"logicalCode": self.device['logicalCode'], "temperature": reportLast['temperature'],
  334. 'portInfo': reportLast['portInfo'],
  335. 'time': reportLast['time'] + (v + 1) * 2}
  336. for v in range(int(timeInterval / 2) - 1)]
  337. PortReport.get_collection().insert_many(PortReportNewList)
  338. # 首存的情况
  339. else:
  340. for ii in range(10):
  341. power = self.__saveDate(1, msgDict=self.event_data, ii=ii)
  342. electricity = float(power) / voltage / 10
  343. temperatureR = self.__saveDate(2, msgDict=self.event_data, ii=ii, electricity=electricity,
  344. devReportDict=devReportDict)
  345. if temperatureR:
  346. temperature = temperatureR
  347. devReportDict.update({'temperature': temperature})
  348. except Exception as e:
  349. logger.error('solve dev=%s device report has an error e=%s' % (self.device.devNo, e))
  350. finally:
  351. newInfo = PortReport(
  352. logicalCode=self.device['logicalCode'],
  353. temperature=devReportDict['temperature'],
  354. time=devReportDict['time'],
  355. portInfo=devReportDict['portInfo']
  356. )
  357. newInfo.save()
  358. def __saveDate(self, data, msgDict, ii, electricity=None, devReportDict=None):
  359. # 存储数据库
  360. if data == 1:
  361. powerData = msgDict['data']['power_data'][0 + 4 * ii:4 + 4 * ii]
  362. power = int(powerData, 16)
  363. return power
  364. if data == 2:
  365. temperature = ''
  366. status = 'idle' if electricity == 0 else 'busy'
  367. devReportDict['portInfo'].update(
  368. {str(ii + 1): {'electricity': round(electricity, 3), 'status': status}})
  369. if ii < 4 and msgDict['data']['temp_data'][0 + 4 * ii:4 + 4 * ii] != '0000':
  370. temperatureNum = msgDict['data']['temp_data'][0 + 4 * ii:4 + 4 * ii]
  371. temperature = int(temperatureNum, 16)
  372. return temperature
  373. class AckEventPreProcessor(StartAckEventPreProcessor):
  374. def analysis_reason(self, reason, fault_code=None):
  375. FINISHED_CHARGE_REASON_MAP = {
  376. 0: u'购买的充电时间或电量用完了。',
  377. 1: u'插头被拔或者松动,或者电瓶已经充满(系统判断为异常断电,电瓶车充电器种类繁多,可能存在误差)',
  378. 2: u'电池已经充满',
  379. 3: u'设备或者端口故障。建议您根据已经充电的时间评估是否需要到现场换到其他端口充电',
  380. 4: u'警告!您的电池功率超过本机最大限制。为了公共安全,不建议您在该充电桩充电',
  381. 5: u'刷卡退费结束',
  382. 6: u'可能是插头被拔掉或者未连接充电器。如果不是自己操作,建议您到现场检查是否有人误操作',
  383. # 7: u'远程方式停止充电。如果不是自己操作,建议到现场尽快恢复充电',
  384. # 服务器定义的停止事件
  385. 0xC0: u'计费方式无效',
  386. 0xC1: u'订购套餐已用完',
  387. 0xC2: u'订购时间已用完',
  388. 0xC3: u'订购电量已用完',
  389. 0xC4: u'动态计算功率后, 时间已用完',
  390. 0xC5: u'订单异常,设备可能离线超过1小时, 平台结单',
  391. 0xC6: u'系统检测到充电已结束, 平台结单(0X24)',
  392. 0xC7: u'系统检测到充电已结束, 平台结单(0X34)',
  393. 0xC8: u'用户远程停止订单',
  394. 0xC9: u'经销商远程停止订单',
  395. 0xCA: u'系统检测到订单已结束, 平台结单(0xCA)',
  396. 0xCB: u'充电时长已达到最大限制(0xCB)',
  397. 0xCC: u'充电电量已达到最大限制(0xCC)',
  398. 0xCD: u'设备升级中... 关闭当前订单',
  399. }
  400. return FINISHED_CHARGE_REASON_MAP.get(reason, '充电结束')
  401. class MyComNetPayAckEvent(PolicyComNetPayAckEvent):
  402. def __init__(self, smartBox, event_data):
  403. super(PolicyComNetPayAckEvent, self).__init__(smartBox, event_data, AckEventPreProcessor())
  404. class CardStartAckEventPreProcessor(AckEventProcessorIntf):
  405. def analysis_reason(self, reason, fault_code=None):
  406. pass
  407. def pre_processing(self, device, event_data):
  408. # type:(DeviceDict, dict)->dict
  409. pass
  410. class OnlineCardStartAckEvent(PolicyOnlineCardStartAckEvent):
  411. def __init__(self, smartBox, event_data):
  412. super(PolicyOnlineCardStartAckEvent, self).__init__(smartBox, event_data, AckEventPreProcessor())