# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import random import time from arrow import Arrow from django.conf import settings from typing import TYPE_CHECKING from apilib.monetary import sum_rmb, RMB, VirtualCoin, Ratio from apilib.utils_datetime import to_datetime from apps.web.device.timescale import FluentedEngine from apps.web.south_intf.liangxi_fire import LiangXiXiaoFang from apps.web.common.models import District from apps.web.constant import Const, FAULT_CODE, FAULT_LEVEL, DEALER_CONSUMPTION_AGG_KIND from apps.web.core.accounting import Accounting from apps.web.core.device_define.dianchuan import FINISHED_CHARGE_REASON_MAP from apps.web.dealer.models import Dealer from apps.web.device.models import PortReport, OfflineCoinStatistics, Part, Group, Device from apps.web.eventer.base import FaultEvent, WorkEvent, ComNetPayAckEvent, IcStartAckEvent, \ AckEventProcessorIntf, IcRechargeAckEvent, CardRefundAckEvent 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 VCardConsumeRecord, ServiceProgress, UserVirtualCard, CardRechargeOrder, MyUser, \ ConsumeRecord, RechargeRecord, Card if TYPE_CHECKING: from apps.web.user.models import Card from apps.web.device.models import GroupDict from apps.web.device.models import DeviceDict logger = logging.getLogger(__name__) class builder(EventBuilder): def __getEvent__(self, device_event): if 'data' not in device_event: return None if 'type' in device_event: if device_event['type'] == 'alert': return DIANCHUANAlertEvent(self.deviceAdapter, device_event) if device_event['type'] == 'report': return DIANCHUANReport(self.deviceAdapter, device_event) else: 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 'elec' in device_event: event_data.update({'elec': device_event['elec']}) if 'v' in device_event: event_data.update({'v': device_event['v']}) if 'today_coins' in device_event and 'ts' in device_event: event_data.update({'today_coins': device_event['today_coins']}) event_data.update({'ts': device_event['ts']}) if event_data.get('cmdCode') in ['03', '05', '11', '12', '17', '22', '48']: return ChargingDIANCHUANWorkEvent(self.deviceAdapter, event_data) if event_data.get('cmdCode') == '0D': return DIANCHUANFaultEvent(self.deviceAdapter, event_data) if event_data.get('cmdCode') in ['41', '47']: return DIANCHUANInductorEvent(self.deviceAdapter, event_data) class ChargingDIANCHUANWorkEvent(WorkEvent): @property def support_playback(self): if self.event_data['cmdCode'] in ['05']: return True else: return False 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('[dianchuan] charging event detected, devNo=%s,info=%s' % (self.device.devNo, self.event_data)) if self.event_data['cmdCode'] == '03': if 'today_coins' in self.event_data and 'ts' in self.event_data: Accounting.syncOfflineCoin( self.device, datetime.datetime.fromtimestamp(self.event_data['ts']).strftime('%Y-%m-%d'), self.event_data['today_coins']) if self.event_data['coins'] > 0: FluentedEngine().in_put_coins_udp(devNo=self.device.devNo, ts=int(time.time()), coins=self.event_data['coins'], mode='uart') else: # 老的流程会单条记录上报记录 Accounting.recordOfflineCoin(device=self.device, report_ts=int(time.time()), coins=int(self.event_data['coins']), mode='uart', port=self.event_data.get('port', None)) try: # 如果是投币,直接把端口状态刷新下 self.deviceAdapter.get_port_status_from_dev() except Exception, e: logger.info('some err=%s' % e) # YuhuanNorther.send_dev_status(self.device, self.event_data['port'], 1) elif self.event_data['cmdCode'] == '22': # ID在线卡刷卡查询余额 cardNo = self.event_data['cardNo'] fee = RMB(self.event_data['fee']) cardType = self.event_data['cardType'] card = self.update_card_dealer_and_type(cardNo) if not card: res = "03" elif not card.openId: res = '03' elif card.frozen: res = '04' else: card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id)) result = self.recharge_id_card(card=card, rechargeType='append', order=card_recharge_order) card.reload() logger.info('cardNo=%s,openId=%s,result=%s,fee=%s,info=%s' % ( cardNo, card.openId, result, fee, self.event_data)) res = "01" if result["balance"] >= fee else "02" balance = RMB(0) if not card else card.balance try: logger.info("card is <{}> fee is <{}> response res is <{}>".format(cardNo, fee, res)) result = self.deviceAdapter.response_card_status(cardNo, balance, res) except Exception, e: # 启动失败需要把金币还回去 logger.info('resp back error=%s' % e) return if fee <= RMB(0): logger.debug( 'query the balance of card in device'.format(card.cardNo, self.device.devNo)) return # 设备回复扣款成功,服务器就需要正式扣掉钱 if result['status'] == '01' and res == '01': virtual_card = card.bound_virtual_card if virtual_card is not None: group = Group.get_group(self.device['groupId']) VCardConsumeRecord( orderNo=VCardConsumeRecord.make_no(self.device.logicalCode), openId=card.openId, cardId=str(virtual_card.id), dealerId=card.dealerId, devNo=self.device.devNo, devTypeCode = self.device.devTypeCode, devTypeName = self.device.devTypeName, logicalCode=self.device['logicalCode'], groupId=group['groupId'], address=group['address'], groupNumber=self.device['groupNumber'], groupName=group['groupName'], ).save() else: self.consume_money_for_card(card, fee) # 记录卡消费记录以及消费记录 orderNo, cardOrderNo = self.record_consume_for_card(card, fee) # 记录当前服务的progress.没有上报端口号,所以先用-1标记,表示端口未知。端口等消费的时候会报上来 ServiceProgress.register_card_service( self.device, -1, card, { 'orderNo': orderNo, 'money': self.event_data['fee'], 'coin': self.event_data['fee'], 'needTime': 0, 'cardOrderNo': cardOrderNo } ) # 通知微信,已经扣费 self.notify_balance_has_consume_for_card(card, fee) elif self.event_data['cmdCode'] == '11' and self.event_data['status'] == '01': # 开始启动某个端口 # 按下端口,开始使用。IC卡和ID卡都会上报这条信息。IC卡只记录消费记录信息,ID卡主要是上报了具体的某一个端口.ID卡和劲能的类似,主要是表示开始某一个端口以及回收余额 cardType = self.event_data['cardType'] card = self.update_card_dealer_and_type(self.event_data['cardNo'], cardType) # 这个地方将计费方式更新到 portDict 刷卡退费的时候会将刷卡的退费金额上报过来 结束事件需要这个字段 主要的是要更新consumeDict的字段 # 0 按时间收费 # 1 按度计费 consumeModule = self.device.get("otherConf", dict()).get("consumeModule", 0) if consumeModule == 0: self.event_data["billingType"] = "time" else: self.event_data["billingType"] = "elec" if cardType == 'ID': consumeDict = {'chargeIndex': self.event_data['port']} queryDict = {'device_imei': self.device.devNo, 'port': -1, 'isFinished': False, 'cardId': str(card.id), 'start_time': {'$gte': int(time.time()) - 3600}} progressDict = {'port': self.event_data['port']} ServiceProgress.update_progress_and_consume_rcd(ownerId=self.device.ownerId, queryDict=queryDict, consumeDict=consumeDict, updateConsume=True, progressDict=progressDict) # 找出对应的卡的ID记录到端口内存数据 queryDict.update(progressDict) rcds = ServiceProgress.get_collection().find(queryDict, {'consumeOrder': 1}, sort=[('start_time', -1)]) allCoins = sum_rmb([rcd['consumeOrder']['coin'] for rcd in rcds]) self.event_data.update({'cardId': str(card.id)}) self.event_data.update({'openId': card.openId}) self.event_data.update({'coins': str(allCoins)}) self.event_data.update({'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)}) self.event_data.update({'isStart': True, 'status': Const.DEV_WORK_STATUS_WORKING}) Device.update_port_control_cache(self.device.devNo, self.event_data) # 记录该端口累计需要的时间和钱,cardId else: # IC卡,需要记录消费记录 if card is not None: fee = RMB(self.event_data['fee']) card.balance = RMB(self.event_data['balance']) try: card.save() except Exception, e: logger.exception(e) orderNo, cardOrderNo = self.record_consume_for_card(card, fee) ServiceProgress.register_card_service(self.device, self.event_data['port'], card, {'orderNo': orderNo, 'money': self.event_data['fee'], 'coin': self.event_data['fee'], 'cardOrderNo': cardOrderNo}) # 通知微信,已经扣费 self.notify_balance_has_consume_for_card(card, fee) self.event_data.update({'cardId': str(card.id)}) self.event_data.update({'openId': card.openId}) self.event_data.update({'coins': self.event_data['fee']}) self.event_data.update({'startTime': datetime.datetime.now().strftime(Const.DATETIME_FMT)}) self.event_data.update({'isStart': True, 'status': Const.DEV_WORK_STATUS_WORKING}) Device.update_port_control_cache(self.device.devNo, self.event_data) # 记录该端口累计需要的时间和钱,cardId # YuhuanNorther.send_dev_status(self.device, self.event_data['port'], 1) elif self.event_data['cmdCode'] == '17': # 余额回收 card = self.update_card_dealer_and_type(self.event_data['cardNo'], 'IC') # type: Card if not card: logger.warning('Card is not registered.'.format( str(self.dealer.id), self.event_data['cardNo'])) return self.refund_money_for_card(RMB(self.event_data['backMoney']), card.id) elif self.event_data['cmdCode'] == '12': cardNo = self.event_data['cardNo'] preBalance = RMB(self.event_data['balance']) card = self.update_card_dealer_and_type(cardNo, 'IC', balance=preBalance) # type: Card if not card: return if card.frozen: logger.debug('{} has been frozen.'.format(repr(card))) return card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id)) # type: CardRechargeOrder if not card_recharge_order: logger.debug('{} has no recharge order.'.format(repr(card))) return self.recharge_ic_card(card=card, preBalance=preBalance, rechargeType='overwrite', order=card_recharge_order) elif self.event_data['cmdCode'] == '05': # 新版本的结束事件,比就版本多刷卡ID卡的在线退费 devNo = self.device.devNo port = str(self.event_data['port']) try: # 这个lineInfo 就是缓存全部的信息 lineInfo = Device.clear_port_control_cache(devNo, port) if not lineInfo: logger.debug('get null control cache from {}'.format(repr(self.device))) return logger.debug('port<{}> cache is: {}'.format(port, str(lineInfo))) billingType = lineInfo.get("billingType", "time") if billingType == "elec": return self.do_elec_finish(devNo, port, lineInfo, self.event_data) else: return self.do_time_finish(devNo, port, lineInfo, self.event_data) finally: notify_event_to_north(self.dealer, self.device, level=Const.EVENT_NORMAL, desc=self.event_data.get('reason')) send_event_to_zhejiang(self.dealer, self.device, self.event_data) if self.event_data['reasonCode'] == '04': # 功率过载导致的断电,一条告警,一条恢复告警 pass elif self.event_data['cmdCode'] == '48': # 没有报端口 leftTime = self.event_data.get('leftTime', 0) nowPower = self.event_data.get('nowPower', 0) nowPowerRatio = self.event_data.get('nowPowerRatio', 0) ctlDict = Device.get_dev_control_cache(self.device.devNo) for port, info in ctlDict.items(): if isinstance(info, dict): doPowerLevel = info.get('doPowerLevel', '') if doPowerLevel == True or doPowerLevel == '': continue realTime = info['needTime'] * nowPowerRatio / 100 if abs(realTime - leftTime) <= 3: info['doPowerLevel'] = True info['powerLevelTime'] = leftTime info['power'] = nowPower info['port'] = port Device.update_port_control_cache(self.device.devNo, info) else: pass def do_elec_finish(self, devNo, port, lineInfo, msgDict): """ 电川的板子 按电量退费 :return: """ logger.info("[{} do_elec_finish] devNo = {}, port = {}, lineInfo = {}, msgDict = {}, event = {}".format( self.__class__.__name__, devNo, port, lineInfo, msgDict, self.event_data)) if not self.dealer: logger.error( "[{} do_elec_finish] dealer {} is not found!".format(self.__class__.__name__, self.device.ownerId)) return # 提取事件信息 leftElec = self.event_data.get("leftTime", 0) / 100.0 reasonCode = self.event_data.get("endType") reasonStr = self.event_data.get("reason") cardNo = self.event_data.get("cardNo", None) cardType = self.event_data.get("cardType", None) price = RMB(lineInfo.get('price', 0)) startTime = lineInfo.get("startTime") # 投币事件 if not startTime: return startTime = to_datetime(startTime) recvTime = to_datetime(self.recvTime) deviceDuration, deviceElec = self.__parse_device_finished_data(self.event_data) try: consumeDict = { "reason": reasonStr, "chargeIndex": port, "uartData": self.event_data.get('uartData', '') } # 计算充电的时间 if deviceDuration and deviceDuration > 0: usedTime = deviceDuration else: if startTime > recvTime: usedTime = 0 else: usedTime = int(round(((recvTime - startTime).total_seconds() / 60.0))) refundProtectionTime = self.device.get_other_conf_item('refundProtectionTime', 3) consumeDict.update({"duration": usedTime}) # 获取组信息 group = Group.get_group(self.device["groupId"]) coins = VirtualCoin(lineInfo.get("coins")) # 扫码的结束 if not cardNo: consumeDict.update({ "reason": reasonStr, "chargeIndex": port }) needElec = lineInfo.get("needElec") refundCoins = VirtualCoin(0) refundedMoney = RMB(0) if leftElec == int("FFFF", 16): refundCoins = VirtualCoin(coins) refundedMoney = RMB(price) leftElec = needElec spendElec = 0 else: if usedTime < refundProtectionTime: refundCoins = VirtualCoin(coins) refundedMoney = RMB(price) spendElec = 0 else: if leftElec > needElec: logger.error('left elec is bigger than need elec. something is wrong') leftElec = 0 spendElec = needElec - leftElec refundSwitch = self.device.is_auto_refund if refundSwitch: refundCoins = VirtualCoin(coins) * Ratio(leftElec / needElec) refundedMoney = RMB(price) * Ratio(leftElec / needElec) logger.debug('left elec = {}, duration = {}, need elec = {}, back coins = {}, refund cash = {}'.format( leftElec, usedTime, needElec, refundCoins, refundedMoney)) if 'consumeRcdId' in lineInfo and lineInfo['consumeRcdId']: # 虚拟卡的结束 consumeRcdId = lineInfo['consumeRcdId'] vCardId = lineInfo.get("vCardId", "") vCard = UserVirtualCard.objects(id=vCardId).first() 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=consumeRcdId) vCard.refund_quota(vCardConsumeRcd, needElec - spendElec, spendElec, 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=recvTime.strftime('%Y-%m-%d %H:%M:%S'), extra=[ {u'虚拟卡号': vCard.cardNo} ] ) elif 'openId' in lineInfo and lineInfo['openId']: # 扫码使用金币启动 user = MyUser.objects(openId=lineInfo['openId'], groupId=self.device.groupId).first() try: is_cash = False if 'refundRMB_device_event' in self.device.owner.features and refundedMoney > RMB(0): is_cash = True if refundedMoney > RMB(0) or refundCoins > VirtualCoin(0): self.refund_net_pay(user, lineInfo, refundedMoney, refundCoins, consumeDict, is_cash) 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 DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH in consumeDict: real_refund = RMB(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH]) if real_refund > RMB(0): extra.append({u'消费金额': '{}(元)'.format(RMB(price) - real_refund)}) extra.append({u'退款金额': '{}(元)'.format(real_refund)}) else: extra.append({u'消费金额': '{}(元)'.format(price)}) elif DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS in consumeDict: real_refund = VirtualCoin(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS]) if real_refund > VirtualCoin(0): extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins) - real_refund)}) extra.append({u'退款金额': '{}(金币)'.format(real_refund)}) else: extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins))}) consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.COIN: (VirtualCoin(coins) - real_refund).mongo_amount }) else: extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins))}) consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.COIN: VirtualCoin(coins).mongo_amount }) 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=recvTime.strftime('%Y-%m-%d %H:%M:%S'), extra=extra) else: logger.error('not net pay rather user virtual card pay. something is wrong.') else: backMoney = self.event_data.get("backMoney") # 刷卡的结束 如果没有开启刷卡退费 则结束上报过来的backMoney = 0 if cardType == "IC": # IC卡结束 只有刷卡结束的IC卡才能够刷新IC卡余额 card = self.update_card_dealer_and_type(cardNo, "IC") if reasonCode == "05" and backMoney > 0: # 原因为05表示刷卡结束充电,会把退费金额报上来. 否则只能通过11指令报上来 self.refund_money_for_card(VirtualCoin(backMoney), card.id) consumeDict.update({'balance': str(card.balance + VirtualCoin(backMoney))}) else: consumeDict.update({'balance': str(card.balance)}) # IC卡必须下次刷卡的时候回收上来 elif cardType == "ID": # ID卡结束 直接退费就行了 card = self.update_card_dealer_and_type(cardNo, "ID") if backMoney > 0: self.refund_money_for_card(RMB(backMoney), str(card.id)) consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: VirtualCoin(backMoney).mongo_amount }) consumeDict.update({'balance': str(card.balance + VirtualCoin(backMoney))}) else: logger.error("invalid card type event data is <{}>".format(self.event_data)) 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'实体卡号': cardNo}] if backMoney > 0: extra.append({ u'退款金额': u'{}(金币)'.format(backMoney) }) self.notify_user_service_complete( service_name=u'充电', openid=self.get_managerialOpenId_by_openId(lineInfo['openId']), port=port, address=group['address'], reason=self.event_data['reason'], finished_time=recvTime.strftime('%Y-%m-%d %H:%M:%S'), extra=extra ) except Exception as e: logger.exception(e) def do_time_finish(self, devNo, port, lineInfo, msgDict): logger.info("[{} do_time_finish] devNo = {}, port = {}, lineInfo = {}, msgDict = {}, event = {}".format( self.__class__.__name__, devNo, port, lineInfo, msgDict, self.event_data)) try: if not self.dealer: logger.error( "[{} do_time_finish] dealer {} is not found!".format(self.__class__.__name__, self.device.ownerId)) return price = RMB(lineInfo.get('price', 0)) refundProtectionTime = self.device.get_other_conf_item('refundProtectionTime', 5) deviceDuration, deviceElec = self.__parse_device_finished_data(self.event_data) recvTime = to_datetime(self.recvTime) if lineInfo is not None and 'startTime' in lineInfo: startTime = to_datetime(lineInfo['startTime']) if startTime > recvTime: logger.error('start time is bigger than now time,devNo={}'.format(devNo)) serverDuration = -1 else: serverDuration = int(round((((recvTime - 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 serverDuration < 0 and deviceDuration < 0: logger.warning('serverDuration and deviceDuration is valid. ignore this event.') return usedTime = 0 if leftTime == 65535: # 设备返回65535说明端口没有启动 leftTimeStr = u'端口未使用' actualNeedTime = 0 else: usedTime = max(serverDuration, deviceDuration) leftTimeStr = leftTime actualNeedTime = usedTime + leftTime if usedTime < refundProtectionTime: # 通过使用的时间来判断是否可以进行保险的退款 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 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, e)) # 涉及到卡的退费 不要以缓存为准 以设备上报的为准 cardNo = self.event_data.get("cardNo", None) if cardNo: # 如果是刷卡的,直接更新消费记录,发通知消息,支持ID卡的退费。 # IC卡的退费, 如果结束原因是刷卡退费(05), 则会把退费信息返回; # 否则不会把退费信息传过来,会通过11号命令返回 cardType = self.event_data.get("cardType") if cardType == 'ID': card = self.update_card_dealer_and_type(cardNo=cardNo, cardType="ID") consumeDict.update({'balance': str(card.balance + VirtualCoin(self.event_data['backMoney']))}) if self.event_data.has_key('backMoney') and self.event_data['backMoney'] > 0: self.refund_money_for_card(VirtualCoin(self.event_data['backMoney']), card.id) # 通知微信,已经退费 self.notify_user(card.managerialOpenId, 'refund_coins', **{ 'title': u'退币完成!您的卡号是%s,卡别名:%s' % (card.cardNo, card.nickName), 'backCount': u'金币:%s' % VirtualCoin(self.event_data['backMoney']), 'finishTime': recvTime.strftime('%Y-%m-%d %H:%M:%S') }) elif cardType == "IC": card = self.update_card_dealer_and_type(cardNo=cardNo, cardType="IC") if not card: logger.warning('Card is not registered.'.format( str(self.dealer.id), cardNo)) return # IC 并且结束code是05 表示卡贴上去了 if self.event_data['endType'] == '05': # 刷卡退费结束的时候,才能够刷新IC卡的余额 self.refund_money_for_card(VirtualCoin(self.event_data['backMoney']), card.id) consumeDict.update({'balance': str(card.balance + VirtualCoin(self.event_data['backMoney']))}) else: consumeDict.update({'balance': str(card.balance)}) # IC卡必须下次刷卡的时候回收上来 else: logger.error("invalid card type event data is <{}>".format(self.event_data)) 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'] }] if VirtualCoin(self.event_data['backMoney']) > VirtualCoin(0): extra.append({ u'退费金额': u'{}(金币)'.format(self.event_data['backMoney']) }) 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=recvTime.strftime('%Y-%m-%d %H:%M:%S'), extra=extra) else: if 'coins' not in lineInfo: logger.warning('has no coins fields in lineInfo. may be coins start.') return coins = VirtualCoin(lineInfo['coins']) refundCoins = VirtualCoin(0) refundedMoney = RMB(0) if leftTime == 65535 or (usedTime < refundProtectionTime): refundCoins = coins refundedMoney = RMB(price) elif self.device.is_auto_refund: refundCoins = coins * (float(leftTime) / float(actualNeedTime)) refundedMoney = RMB(price * (float(leftTime) / float(actualNeedTime))) if refundCoins > coins: refundCoins = coins if refundedMoney > RMB(price): refundedMoney = RMB(price) logger.debug('left time = {}, duration = {}, need time = {}, back coins = {}, refund money = {}'.format( leftTime, usedTime, actualNeedTime, refundCoins, refundedMoney)) 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=recvTime.strftime('%Y-%m-%d %H:%M:%S'), extra=[ {u'虚拟卡号': vCard.cardNo} ] ) elif 'openId' in lineInfo and lineInfo['openId']: user = MyUser.objects(openId=lineInfo['openId'], groupId=self.device.groupId).first() try: is_cash = False if 'refundRMB_device_event' in self.device.owner.features and refundedMoney > RMB(0): is_cash = True if refundedMoney > RMB(0) or refundCoins > VirtualCoin(0): self.refund_net_pay(user, lineInfo, refundedMoney, refundCoins, consumeDict, is_cash) 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 DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH in consumeDict: real_refund = RMB(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH]) if real_refund > RMB(0): extra.append({u'消费金额': '{}(元)'.format(RMB(price) - real_refund)}) extra.append({u'退款金额': '{}(元)'.format(real_refund)}) else: extra.append({u'消费金额': '{}(元)'.format(price)}) elif DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS in consumeDict: real_refund = VirtualCoin(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS]) if real_refund > VirtualCoin(0): extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins) - real_refund)}) extra.append({u'退款金额': '{}(金币)'.format(real_refund)}) else: extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins))}) else: extra.append({u'消费金额': '{}(金币)'.format(VirtualCoin(coins))}) 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=recvTime.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) class DIANCHUANFaultEvent(FaultEvent): def do(self, **args): detail = {"errorCode": self.event_data["faultCode"]} self.event_data["detail"] = detail super(DIANCHUANFaultEvent, self).do() class DIANCHUANInductorEvent(FaultEvent): def do(self, **args): # 处理数据 temperatureLast = self.event_data.get('temperatureLast', None) smokeWarning = self.event_data.get('smokeWarning', False) try: faultName = "告警" faultCode = "" desc = "" if temperatureLast: logger.info('Warning!!! Temperature exceeds upper limit') temperatureMax = self.device.get('otherconf', {}).get('temperatureMax', 60) desc = u'当前主机温度%s度超过了门限值:%s度' % (temperatureLast, temperatureMax) faultName = u"主机温度告警" faultCode = FAULT_CODE.OVER_TEMPERATURE if smokeWarning: logger.info('Warning!!! Smoke sensor alarm') desc = u'当前主机出现冒烟,请第一时间确定是否有起火。此告警十万火急,请迅速联系物业、消防相关部门!' faultName = u"主机烟雾告警" faultCode = FAULT_CODE.SMOKE except Exception, e: logger.error('some error=%s' % e) return detail = {"errorCode": self.event_data["faultCode"]} self.event_data.update({ "faultName": faultName, "faultCode": faultCode, "level": FAULT_LEVEL.CRITICAL, "desc": desc, "detail": detail }) super(DIANCHUANInductorEvent, self).do(**args) class DIANCHUANAlertEvent(FaultEvent): def __Analyze_alert_data(self, data): alertInfo = {'cmdCode': data['cmd'], 'logicalCode': self.device['logicalCode']} address = Group.get_group(self.device['groupId'])['address'] # 这里判断数据格式 if 'status' not in data: logger.error('Data arrays have no keywords status') return # 这里做漏电告警处理 if '5' in data['status']: electricityNum = str(int(data['values'][0:4], 16)) + 'mA' alertInfo['electricity'] = {'electricityNum': electricityNum, 'address': address, 'reasonCode': '12', 'reason': u'在{}编号为{}发生漏电,漏电量为{}' .format(address, self.device['logicalCode'], electricityNum)} # 这里做高温告警处理 if '6' in data['status']: temperatureAccess = [index for index, acces in enumerate(data['status'], 1) if acces == '6'] temperatureAlertList = [] for i in temperatureAccess: temperatureValue = str(int(data['values'][(i - 1) * 4:(i - 1) * 4 + 4], 16)) temperatureAlertList.append( {'temperatureValue': temperatureValue, 'address': address, 'reasonCode': '11', 'reason': u'在{}编号为{}的设备有高温预警,当前温度为{}摄氏度' .format(address, self.device['logicalCode'], temperatureValue)}) alertInfo['temperature'] = temperatureAlertList return alertInfo def do(self, **args): # 判断不存在的设备网上报 if not self.device.ownerId: logger.error('This device cant find a dealer') return # 是否存在温感和电感 temperaturePart = Part.objects(logicalCode=self.device['logicalCode'], partType='3001') electricityPart = Part.objects(logicalCode=self.device['logicalCode'], partType='3002') if not temperaturePart.count() or not electricityPart.count(): logger.error( 'There are no transformers in the locigalcode {} equipment'.format(self.device['logicalCode'])) return # 处理数据 eventInfo = self.__Analyze_alert_data(self.event_data['data']) try: # 先处理高温情况 if 'temperature' in eventInfo: for InfoDetail in eventInfo['temperature']: send_event_to_zhejiang(self.dealer, self.device, InfoDetail, partId=temperaturePart[0].id) # 提示用户 group = Group.get_group(self.device['groupId']) self.notify_dealer('device_fault', **{ 'title': u'注意!注意!您的设备发生故障', 'device': u'组号::%s, 二维码编号:%s' % (self.device['groupNumber'], self.device['logicalCode']), 'location': u'组名称:%s, 地址:%s' % (group['groupName'], group['address']), 'fault': InfoDetail['reason'], 'notifyTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) # 上报高温至消防 # if self.device["ownerId"] in ("5b4ed32e8732d67bd0626528", "5b6c29388732d669f3ae6f94"): group = Group.get_group(self.device['groupId']) districtInfo = District.get_district(group["districtId"]) self.device.update({"districtInfo": districtInfo, "groupAddr": group["address"]}) LiangXiXiaoFang.send_to_xf_fault(self.device, "01", u"设备温度过高") # 处理漏电情况 elif 'electricity' in eventInfo: # 获取漏电告警插件 send_event_to_zhejiang(self.dealer, self.device, eventInfo['electricity'], partId=electricityPart[0].id) # 提示用户 group = Group.get_group(self.device['groupId']) self.notify_dealer('device_fault', **{ 'title': u'注意!注意!您的设备发生故障', 'device': u'组号::%s, 二维码编号:%s' % (self.device['groupNumber'], self.device['logicalCode']), 'location': u'组名称:%s, 地址:%s' % (group['groupName'], group['address']), 'fault': eventInfo['electricity']['reason'], 'notifyTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) # 上报漏电至消防 # if self.device["ownerId"] in ("5b4ed32e8732d67bd0626528", "5b6c29388732d669f3ae6f94"): group = Group.get_group(self.device['groupId']) districtInfo = District.get_district(group["districtId"]) self.device.update({"districtInfo": districtInfo, "groupAddr": group["address"]}) LiangXiXiaoFang.send_to_xf_fault(self.device, "04", u"设备发生漏电") except: logger.error('Array {} nonspecification'.format(eventInfo)) return self.record(detail=eventInfo) class DIANCHUANReport(WorkEvent): def do(self, **args): if 'type' not in self.event_data: logger.error('Array {} is not format,lose a key named "type"'.format(self.event_data)) if self.event_data.get('type') == 'report': devReportDict = {'logicalCode': 'logicalCode', 'time': self.event_data['time_stamp'], 'portInfo': {}} temperature = '' voltage = 220 try: # 拿到个数判断是不是第一次 reportNum = PortReport.get_collection().find({ 'logicalCode': self.device['logicalCode'] }).sort('time', -1).count() if reportNum: # 获取上一次存储的信息 reportLast = PortReport.get_collection().find({ 'logicalCode': self.device['logicalCode'] }).sort('time', -1)[0] for ii in range(10): power = self.__saveDate(1, msgDict=self.event_data, ii=ii) if power: electricity = float(power) / voltage / 10 else: electricity = reportLast['portInfo'][str(ii + 1)]['electricity'] temperatureR = self.__saveDate(2, msgDict=self.event_data, ii=ii, electricity=electricity, devReportDict=devReportDict) if temperatureR: temperature = temperatureR devReportDict.update({'temperature': temperature}) # 查看现在的跟以前差距多少 timeInterval = devReportDict['time'] - reportLast['time'] if timeInterval > 2: PortReportNewList = [ {"logicalCode": self.device['logicalCode'], "temperature": reportLast['temperature'], 'portInfo': reportLast['portInfo'], 'time': reportLast['time'] + (v + 1) * 2} for v in range(int(timeInterval / 2) - 1)] PortReport.get_collection().insert_many(PortReportNewList) # 首存的情况 else: for ii in range(10): power = self.__saveDate(1, msgDict=self.event_data, ii=ii) electricity = float(power) / voltage / 10 temperatureR = self.__saveDate(2, msgDict=self.event_data, ii=ii, electricity=electricity, devReportDict=devReportDict) if temperatureR: temperature = temperatureR devReportDict.update({'temperature': temperature}) except Exception, e: logger.error('solve dev=%s device report has an error e=%s' % (self.device.devNo, e)) finally: newInfo = PortReport( logicalCode=self.device['logicalCode'], temperature=devReportDict['temperature'], time=devReportDict['time'], portInfo=devReportDict['portInfo'] ) newInfo.save() def __saveDate(self, data, msgDict, ii, electricity=None, devReportDict=None): # 存储数据库 if data == 1: powerData = msgDict['data']['power_data'][0 + 4 * ii:4 + 4 * ii] power = int(powerData, 16) return power if data == 2: temperature = '' status = 'idle' if electricity == 0 else 'busy' devReportDict['portInfo'].update( {str(ii + 1): {'electricity': round(electricity, 3), 'status': status}}) if ii < 4 and msgDict['data']['temp_data'][0 + 4 * ii:4 + 4 * ii] != '0000': temperatureNum = msgDict['data']['temp_data'][0 + 4 * ii:4 + 4 * ii] temperature = int(temperatureNum, 16) return temperature