# -*- coding: utf-8 -*- # !/usr/bin/env python import logging import datetime import random import time from typing import TYPE_CHECKING from apilib.monetary import RMB, VirtualCoin from apilib.utils_datetime import to_datetime from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND from apps.web.core.accounting import Accounting from apps.web.dealer.models import Dealer from apps.web.device.models import OfflineCoinStatistics, Group, Device from apps.web.south_intf.liangxi_fire import LiangXiXiaoFang from apps.web.south_intf.yuhuan_fire import YuhuanNorther from apps.web.common.models import District from apps.web.eventer.base import FaultEvent, WorkEvent from apps.web.eventer import EventBuilder from apps.web.south_intf.platform import notify_event_to_north from apps.web.south_intf.zhejiang_fire import send_event_to_zhejiang from apps.web.user.models import ServiceProgress, UserVirtualCard, VCardConsumeRecord, Card, MyUser, CardRechargeOrder, RechargeRecord from apps.web.user.transaction_deprecated import refund_money if TYPE_CHECKING: from apps.web.device.models import GroupDict logger = logging.getLogger(__name__) class builder(EventBuilder): def __getEvent__(self, device_event): event_data = self.deviceAdapter.analyze_event_data(device_event['data']) if event_data is None or 'cmdCode' not in event_data: return None if 'duration' in device_event: event_data.update({'duration': device_event['duration']}) if 'v' in device_event: event_data.update({'v': device_event['v']}) if 'elec' in device_event: event_data.update({'elec': device_event['elec']}) if event_data['cmdCode'] in ['03', '05', '11', '12', '30', '31', '32']: return ChargingZHIXIAWorkEvent(self.deviceAdapter, event_data) if event_data['cmdCode'] == '0D': return ZHIXIAFaultEvent(self.deviceAdapter, event_data) return None class ZHIXIAFaultEvent(FaultEvent): LX_FAULE_CODE_MAP = { "01": "07", "02": "03", "03": "05" } def do_norther(self): """ 上报其他平台的,都在这个地方处理 可迁移至异步任务 :return: """ # 玉环的消防对接 YuhuanNorther.send_dev_event(self.device, self.event_data['FaultCode'], self.event_data['port']) # 梁溪消防局的对接 faultContent = self.event_data["statusInfo"] faultCode = self.LX_FAULE_CODE_MAP.get(self.event_data["FaultCode"], "") districtInfo = District.get_district(self.device.group["districtId"]) self.device.update({"districtInfo": districtInfo, "groupAddr": self.device.group["address"]}) LiangXiXiaoFang.send_to_xf_fault(self.device, faultCode, faultContent, self.event_data["port"]) def do(self, **args): # 将告警的消息打入相应的缓存 port = self.event_data["port"] # 0 表示整机 if port == 0xFF: part = str(0) else: part = str(port) warningData = { "warningStatus": 1, "warningDesc": self.event_data["statusInfo"], "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "warningUart": self.event_data["uart"] } Device.update_dev_warning_cache(self.device.devNo, {part: warningData}) self.do_norther() super(ZHIXIAFaultEvent, self).do() class ChargingZHIXIAWorkEvent(WorkEvent): def __parse_device_finished_data(self, event_data): duration = event_data.get('duration', -1) if duration != -1: if 'v' in event_data: duration = (duration / 60) elec = event_data.get('elec', -1) if elec != -1: if 'v' in event_data: elec = round(elec / (10000.0 * 3600.0), 3) else: elec = round(elec / 3600.0, 3) logger.debug('device duration is {}, device elec is {}'.format(duration, elec)) return duration, elec def do(self, **args): logger.info('zhixiakeji charging event detected, devNo=%s,info=%s' % (self.device.devNo, self.event_data)) devNo = self.device.devNo if self.event_data['cmdCode'] == '03': # 电川的无法记录投币的端口数据 Accounting.recordOfflineCoin(self.device, int(time.time()), int(self.event_data['coins'])) if self.device.ownerId is not None and self.device.ownerId != '': dealer = Dealer.objects(id = self.device.ownerId).first() if dealer is not None and 'show_device_offline_coins' in dealer.features: OfflineCoinStatistics.recordCoinEvent( self.device['logicalCode'], self.device.devNo, int(self.event_data.get('coins', 1)), self.device['groupId'] ) else: logger.error('undefined dealer id=%s' % self.device.ownerId) # 如果是投币,直接把端口状态刷新下 self.deviceAdapter.get_port_status_from_dev() elif self.event_data['cmdCode'] == '05': devNo = self.device.devNo port = str(self.event_data['port']) ctrInfo = Device.get_dev_control_cache(self.device.devNo) lineInfo = ctrInfo.get(port) if not lineInfo: logger.info('aaaaaaaaaaaaaaaaa,the ctrInfo is empty,devNo=%s' % devNo) try: return self.do_time_finish(devNo, port, lineInfo, self.event_data) finally: Device.clear_port_control_cache(devNo, str(port)) notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = self.event_data['reason']) send_event_to_zhejiang(self.dealer, self.device, self.event_data) if self.event_data.get('reasonCode', '') == '04': # 功率过载导致的断电,一条告警,一条恢复告警 YuhuanNorther.send_dev_event(self.device, '97', port) YuhuanNorther.send_dev_event(self.device, '98', port) # 发送一条端口使用结束的告警 YuhuanNorther.send_dev_status(self.device, port, 2) # 结束使用 elif self.event_data['cmdCode'] == '11': cardNo = self.event_data['cardNo'] card = self.update_card_dealer_and_type(cardNo, 'IC') if not card: return lineInfo = Device.update_port_control_cache(self.device.devNo, self.event_data) lineInfo['openId'] = card.openId lineInfo = Device.update_port_control_cache(self.device.devNo, lineInfo) balance = RMB(self.event_data['balance']) consumeMoney = RMB(self.event_data['coins']) if self.event_data['status'] == '01': # 扣款成功,表示马上需要使用卡了,需要登记 desc = u'正在刷卡使用,卡号:%s,卡名称:%s,端口号:%s,扣费:%s,余额:%s' % ( card.cardNo, card.cardName, self.event_data['port'], self.event_data['coins'], balance) orderNo, cardOrderNo = \ self.record_consume_for_card(card = card, money = consumeMoney, desc = desc, servicedInfo = { 'balance': self.event_data['balance'], 'coin': self.event_data['coins'], 'port': self.event_data['port']}) # 记录当前服务的progress.没有上报端口号,所以先用-1标记,表示端口未知。端口等消费的时候会报上来 ServiceProgress.register_card_service(self.device, self.event_data['port'], card, {'orderNo': orderNo, 'coin': self.event_data['coins'], 'cardOrderNo': cardOrderNo}) self.notify_balance_has_consume_for_card(card, consumeMoney, desc) # 更新下balance,便于刷卡记录中,更新到数据库中 lineInfo['balance'] = str(balance) lineInfo['cardId'] = str(card.id) lineInfo['startTime'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') lineInfo['isStart'] = True lineInfo['status'] = Const.DEV_WORK_STATUS_WORKING Device.update_port_control_cache(self.device.devNo, lineInfo) # 如果卡挂失掉了,立马把端口关闭掉 if card.frozen: logger.debug('{} has been frozen.'.format(repr(card))) self.deviceAdapter.stop_charging_port(lineInfo['port']) elif self.event_data['status'] == '02': # 扣款失败,余额不足 pass elif self.event_data['status'] == '03': # 返现 balance = balance + consumeMoney self.update_card_balance(card, balance) elif self.event_data['cmdCode'] == '12': # 电川的板子充值是覆写方式,需要把上次的余额一起累加进来 cardNo = self.event_data['cardNo'] card = self.update_card_dealer_and_type(cardNo, 'IC') # type: Card if not card: return if card.frozen: logger.debug('{} has been frozen.'.format(repr(card))) return preBalance = RMB(self.event_data['balance']) card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id)) # type: CardRechargeOrder self.recharge_ic_card(card = card, preBalance = preBalance, rechargeType = 'overwrite', order = card_recharge_order) elif self.event_data['cmdCode'] == '20': # 启动设备后,需要把状态刷新下,便于实时的把状态查询出来 lineInfo = Device.update_port_control_cache(self.device.devNo, self.event_data) lineInfo['startTime'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') lineInfo['isStart'] = True lineInfo['status'] = Const.DEV_WORK_STATUS_WORKING Device.update_port_control_cache(self.device.devNo, lineInfo) # 发送事件给北向,表示已经开始工作 YuhuanNorther.send_dev_status(self.device, self.event_data['port'], 1) elif self.event_data['cmdCode'] == '30': try: lastValue = Device.get_dev_control_cache(devNo) lastValue.update({'elecValue': self.event_data}) Device.update_dev_control_cache(self.device["devNo"], lastValue) finally: notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = u'电流主动上报事件', dataDict = self.event_data) elif self.event_data['cmdCode'] == '31': try: lastValue = Device.get_dev_control_cache(devNo) lastValue.update({'temperatureValue': self.event_data}) Device.update_dev_control_cache(self.device["devNo"], lastValue) finally: notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = u'温度主动上报事件', dataDict = self.event_data) elif self.event_data['cmdCode'] == '32': try: lastValue = Device.get_dev_control_cache(devNo) lastValue.update(self.event_data) Device.update_dev_control_cache(self.device["devNo"], lastValue) finally: if self.event_data['isYangan']: notify_event_to_north(self.dealer, self.device, level = Const.EVENT_NORMAL, desc = u'烟感事件', dataDict = self.event_data) elif self.event_data['cmdCode'] == '17': # 余额回收 card = self.update_card_dealer_and_type(self.event_data['cardNo'], 'IC') # type: Card self.refund_money_for_card(RMB(self.event_data['backMoney']), card.id) def do_time_finish(self, devNo, port, lineInfo, event_data): try: refundProtectionTime = self.device.get_other_conf_item('refundProtectionTime', 5) deviceDuration, deviceElec = self.__parse_device_finished_data(event_data) if lineInfo is not None and 'startTime' in lineInfo: startTime = to_datetime(lineInfo['startTime']) nowTime = datetime.datetime.now() if startTime > nowTime: logger.error('start time is bigger than now time,devNo={}'.format(devNo)) serverDuration = -1 else: serverDuration = int(round((((nowTime - startTime).total_seconds() + 59) / 60.0))) else: logger.info('lineinfo has not startTime,devNo=%s' % devNo) serverDuration = -1 leftTime = self.event_data['leftTime'] logger.debug('serverDuration = {}; deviceDuration = {}; leftTime = {}; lineInfo = {}'.format( serverDuration, deviceDuration, leftTime, lineInfo)) if leftTime == 65535: # 设备返回65535说明端口没有启动 leftTimeStr = u'端口未使用' actualNeedTime = 0 usedTime = 0 leftTime = 65535 elif serverDuration < 0 and deviceDuration < 0: # duration参数错误 logger.debug('serverDuration and deviceDuration is valid.') actualNeedTime = -1 else: usedTime = max(serverDuration, deviceDuration) leftTime = self.event_data['leftTime'] leftTimeStr = leftTime actualNeedTime = usedTime + leftTime # 通过使用的时间来判断是否可以进行保险的退款 if actualNeedTime != -1 and usedTime <= 5: rechargeIds = list() payInfo = lineInfo.get('payInfo', list()) for item in payInfo: if 'rechargeRcdId' not in item: continue rechargeIds.append(item['rechargeRcdId']) group = self.device.group # type: GroupDict if actualNeedTime < 0: if 'cardNo' in self.event_data and self.event_data['cardNo']: card = Card.objects(cardNo = str(self.event_data['cardNo']), agentId = self.dealer.agentId).first() if card: self.notify_user_service_complete( service_name = u'充电', openid = card.managerialOpenId if card else '', port = port, address = group['address'], reason = self.event_data['reason'], finished_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra = [{ u'实体卡号': self.event_data['cardNo'] }] ) logger.debug('has no actual need time. ignore this event.') return consumeDict = { 'reason': self.event_data['reason'], 'leftTime': leftTimeStr, 'chargeIndex': port, 'actualNeedTime': u'动态功率计算为%s分钟' % actualNeedTime, 'duration': usedTime, 'deviceDuration': deviceDuration, 'serverDuration': serverDuration, 'uartData': self.event_data.get('uartData', '') } try: groupObj = Group.objects(id=self.device.groupId).first() if groupObj.otherConf.get('zhuxing', None) is not None: spendElec = round((float(random.randint(15, 19)) / 100) * (float(usedTime) / 60), 3) consumeDict.update({'elec': spendElec}) consumeDict.update({'elecFee': self.deviceAdapter.calc_elec_fee(spendElec)}) else: if deviceElec != -1: consumeDict.update({'elec': deviceElec}) if deviceElec == 0 and usedTime > 0: consumeDict.update({'elec': round((float(random.randint(15, 19)) / 100) * (float(usedTime) / 60), 3)}) else: consumeDict.update({'elec': 0.0}) consumeDict.update({'elecFee': RMB(0.0).mongo_amount}) except Exception as e: logger.error("device <{}> elec add error! <{}>".format(self.device.devNo, event_data)) if 'cardNo' in lineInfo and lineInfo['cardNo']: # 如果是刷卡的,直接更新消费记录,发通知消息,支持ID卡的退费。 # IC卡的退费, 如果结束原因是刷卡退费(05), 则会把退费信息返回; # 否则不会把退费信息传过来,会通过11号命令返回 card = Card.objects(cardNo = lineInfo['cardNo'], agentId = self.dealer.agentId).first() if not card: logger.error('not exist this card no.') return consumeDict.update({'balance': lineInfo['balance']}) ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId, {'open_id': lineInfo['openId'], 'port': int(port), 'device_imei': self.device.devNo, 'isFinished': False}, consumeDict) extra = [{ u'实体卡号': lineInfo['cardNo'] }] self.notify_user_service_complete( service_name = u'充电', openid = card.managerialOpenId if card else '', port = port, address = group['address'], reason = self.event_data['reason'], finished_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra = extra) else: if 'coins' not in lineInfo: logger.warning('coins not in cache of port<{}> of device'.format(port, self.device.devNo)) return # 计算退费信息 coins = VirtualCoin(lineInfo['coins']) refundCoins = VirtualCoin(0) if self.device.is_auto_refund: if leftTime != 65535: refundCoins = coins * (float(leftTime) / float(actualNeedTime)) else: refundCoins = coins # if (refundProtection == 1 and usedTime < refundProtectionTime) and backCoins != VirtualCoin(0): if usedTime < refundProtectionTime and refundCoins != VirtualCoin(0): refundCoins = coins if refundCoins > coins: refundCoins = coins logger.debug( 'lefttime = {}, usedTime = {}, need time = {}, back coins = {}'.format(leftTime, usedTime, actualNeedTime, refundCoins)) if 'consumeRcdId' in lineInfo and lineInfo['consumeRcdId']: vCardId = lineInfo['vCardId'] vCard = UserVirtualCard.objects(id = vCardId).first() # type: UserVirtualCard if not vCard: logger.info('can not find the vCard id = %s' % vCardId) return try: ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId, {'open_id': lineInfo['openId'], 'port': int(port), 'device_imei': self.device.devNo, 'isFinished': False}, consumeDict) if refundCoins > VirtualCoin(0): vCardConsumeRcd = VCardConsumeRecord.objects.get(id = lineInfo['consumeRcdId']) vCard.refund_quota(vCardConsumeRcd, usedTime, 0.0, refundCoins.mongo_amount) finally: user = MyUser.objects(openId = lineInfo['openId'], groupId = self.device.groupId).first() self.notify_user_service_complete( service_name = u'充电', openid = user.managerialOpenId if user else '', port = port, address = group['address'], reason = self.event_data['reason'], finished_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra = [ {u'虚拟卡号': vCard.cardNo} ] ) elif 'openId' in lineInfo and lineInfo['openId']: try: consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.COIN: VirtualCoin(coins).mongo_amount}) if refundCoins > VirtualCoin(0): consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: refundCoins.mongo_amount, DEALER_CONSUMPTION_AGG_KIND.COIN: (VirtualCoin(coins) - refundCoins).mongo_amount }) refund_money(self.device, refundCoins, lineInfo['openId']) ServiceProgress.update_progress_and_consume_rcd(self.device.ownerId, {'open_id': lineInfo['openId'], 'port': int(port), 'device_imei': self.device.devNo, 'isFinished': False}, consumeDict) finally: extra = [] if refundCoins > VirtualCoin(0): extra.append({u'消费金额': '{}(金币)'.format(coins - refundCoins)}) extra.append({u'退款金额': '{}(金币)'.format(refundCoins)}) else: extra.append({u'消费金额': '{}(金币)'.format(coins)}) user = MyUser.objects(openId = lineInfo['openId'], groupId = self.device.groupId).first() self.notify_user_service_complete( service_name = u'充电', openid = user.managerialOpenId if user else '', port = port, address = group['address'], reason = self.event_data['reason'], finished_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra = extra ) else: logger.error('not net pay rather user virtual card pay. something is wrong.') except Exception as e: logger.exception(e)