# -*- coding: utf-8 -*- # !/usr/bin/env python import logging import sys import time import datetime from apilib.monetary import RMB, VirtualCoin from apps.web.device.models import DevicePortReport, Device, Group from apps.web.south_intf.platform import notify_event_to_north_v2 from apps.web.user.models import ServiceProgress, MonthlyPackage, CardRechargeOrder, ConsumeRecord, CardConsumeRecord, \ MyUser from apps.common.utils import int_to_hex from apps.web.eventer import EventBuilder from apps.web.core.device_define.ywt_chongdiangui import DefaultParams, Calculater from apps.web.constant import DeviceCmdCode, Const, DEALER_CONSUMPTION_AGG_KIND from apps.web.eventer.base import FaultEvent, WorkEvent 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 # 粤万通的版本比较复杂 没有一个统一的版本 这次力争升级到一个统一的版本 # TODO 补丁 if "ack_id" in device_event and self.device.driverVersion not in [ "v1.0.0", "v1.0.1", "v1.0.2", "v3.0.1", "v3.0.2", "v3.0.3", "v4.0.0", "v4.0.1" ]: self.deviceAdapter._ack(device_event["ack_id"]) cmdCode = event_data.get("cmdCode", "") className = "ChargeCabinetEvent{}".format(cmdCode.capitalize()) myClass = getattr(sys.modules[__name__], className, None) if myClass: return myClass(self.deviceAdapter, event_data) # 工具类 class ChargeCabinetEventBase(object): @property def device_adapter(self): return getattr(self, 'deviceAdapter') @staticmethod def reverse_hex(s): if len(s) == 0: return "" return s[-2:] + ChargeCabinetEventBase.reverse_hex(s[:-2]) def is_start_by_card(self, port): devCache = Device.get_dev_control_cache(self.device_adapter.device.devNo) return "cardNo" in devCache.get(port, dict()) def response_card_start(self, _type, portStrHex, cardNoHex, balance = 0, orderNo = "0", chargeTime = 0, pw = 0, cardType = 0, leftDays = 0): balance = ChargeCabinetEventBase.reverse_hex("{:0>8X}".format(int(balance * 100) & 0xFFFFFFFF)) cardType = ChargeCabinetEventBase.reverse_hex("{:0>2X}".format(int(cardType))) leftDays = ChargeCabinetEventBase.reverse_hex("{:0>4X}".format(int(leftDays))) orderNo = ChargeCabinetEventBase.reverse_hex("{:0>14X}".format(int(orderNo))) chargeTime = ChargeCabinetEventBase.reverse_hex("{:0>4X}".format(int(chargeTime))) pw = ChargeCabinetEventBase.reverse_hex("{:0>4X}".format(int(pw))) sendData = cardNoHex + portStrHex + _type + balance + cardType + leftDays + orderNo + chargeTime + pw self.device_adapter._send_data(funCode="B0", sendData=sendData, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) def response_finish(self, orderNoHex, portStr): portStrHex = int_to_hex(portStr, lens=2) sendData = orderNoHex + portStrHex + "01" self.device_adapter._send_data("C1", sendData, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) def start_device(self, port, orderNo, pw, operate): return self.device_adapter._start(port, orderNo, pw, operate) def response_fault(self, port): portStrHex = "{:02X}".format(port) sendData = portStrHex + "01" self.device_adapter._send_data("E0", sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) def response_card_finish(self, cardNoHex, portStrHex, balance, consumeMoney, cardType = 1, leftDays = 0, cardStatus = "01"): balanceHex = ChargeCabinetEventBase.reverse_hex("{:0>8X}".format(int(balance * 100))) consumeMoneyHex = ChargeCabinetEventBase.reverse_hex("{:0>8X}".format(int(consumeMoney * 100))) cardTypeHex = ChargeCabinetEventBase.reverse_hex("{:0>2X}".format(int(cardType))) leftDaysHex = ChargeCabinetEventBase.reverse_hex("{:0>4X}".format(int(leftDays))) sendData = cardNoHex + portStrHex + cardStatus + balanceHex + cardTypeHex + leftDaysHex + consumeMoneyHex self.device_adapter._send_data("B1", sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) def deduct_for_card(self, portStr, card, money, consumeOrder, cardConsumeOrder): devCache = Device.get_dev_control_cache(self.device_adapter.device.devNo) portCache = devCache.get(portStr) monthlyPackageId = portCache.get("monthlyPackageId") if monthlyPackageId: monthlyPackage = MonthlyPackage.objects.get(id = monthlyPackageId) monthlyPackage.deduct(consumeOrder) else: cardBalance = card.balance - RMB(money) card.balance = cardBalance card.save() # 更新 consumeOrder.update(money = RMB(money).mongo_amount, coin = VirtualCoin(money).mongo_amount, status = "finished") cardConsumeOrder.update(money = RMB(money).mongo_amount, coin = VirtualCoin(money).mongo_amount) # 故障 class ChargeCabinetEventE0(FaultEvent, ChargeCabinetEventBase): def do(self, **args): group = Group.get_group(self.device["groupId"]) if self.is_notify_dealer(): self.notify_dealer( "device_fault", title=u"注意注意您的设备发生故障", device=u"组号:{},二维码编号:{}".format(self.device["groupNumber"], self.device["logicalCode"]), location=u"组名称:{},地址:{}".format(group["groupName"], group["address"]), fault=self.event_data["statusInfo"], notifyTime=str(datetime.datetime.now())[:19] ) warningData = { "warningStatus": 2, "warningDesc": self.event_data["statusInfo"], "warningTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") } # 整机告警 part = self.event_data.get("portStr", "0") Device.update_dev_warning_cache(self.device.devNo, {part: warningData}) try: faultRecord = self.record(faultCode=self.event_data.get("faultCode", ""), detail={"errorCode": self.event_data["faultCode"]}) self.north_to_liangxi(faultRecord) except Exception as e: logger.error(e) finally: sendData = self.event_data.get("portHex") + "01" self.deviceAdapter._send_data("E0", sendData = sendData, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) # 时间同步 class ChargeCabinetEventA1(WorkEvent, ChargeCabinetEventBase): def do(self, **args): logger.info("charge cabinet <{}> request to synchronization time".format(self.device.devNo)) self.deviceAdapter._sync_device_time() # 参数设置同步 class ChargeCabinetEventA2(WorkEvent, ChargeCabinetEventBase): def do(self, **args): logger.info("charge cabinet <{}> request to device params".format(self.device.devNo)) self.deviceAdapter._sync_device_settings() # 刷卡上报 class ChargeCabinetEventB0(WorkEvent, ChargeCabinetEventBase): def do(self, **args): """ 两个问题 刷卡的时候主板会判断当前端口是否已经被占用 :param args: :return: """ logger.info("charge cabinet <{}> send a card <{}> start signal ".format(self.device.devNo,self.event_data.get("cardNo"))) cardNo = self.event_data.get("cardNo") portStr = self.event_data.get("portStr") portStrHex = self.event_data['portHex'] cardNoHex = self.event_data['cardNoHex'] card = self.update_card_dealer_and_type(cardNo) if not card or not card.openId or card.frozen: self.response_card_start(_type="03", portStrHex=portStrHex, cardNoHex=cardNoHex) logger.info("bad card, cardNo is %s, cannot start port!" % cardNo) return # 充值未到账的卡的处理 有些卡充值在没有上报类型之前 充值结果不会到账的 这个地方需要处理一下 card_recharge_order = CardRechargeOrder.get_last_to_do_one(str(card.id)) self.recharge_id_card(card=card, rechargeType='append', order=card_recharge_order) card.reload() # 能够刷卡的最低的金额 monthlyPackage = card.monthlyPackage if monthlyPackage: cardType = 2 leftDays = (monthlyPackage.expireDateTime - datetime.datetime.now()).days else: # 能够刷卡的最低的金额 minCardMoney = self.device.get("otherConf", dict()).get("minAfterStartCoins", DefaultParams.DEFAULT_MIN_START_COINS) if float(card.balance) < float(minCardMoney): self.response_card_start(_type = "02", portStrHex = portStrHex, cardNoHex = cardNoHex, balance = card.balance) logger.info("negative card, card No is %s, card balance is %s" % (cardNo, card.balance)) return cardType = 1 leftDays = 0 # 制造启动前的各种参数 密码 时间等等 chargeTime = 0xFFFF pw = self.deviceAdapter.password otherConf = self.device.get("otherConf") servicedInfo = {"pw": pw, "chargeType": otherConf.get("chargeType", DefaultParams.DEFAULT_CHARGE_TYPE)} orderNo, cardConsumeOrderNo = self.record_consume_for_card( card=card, money=RMB(0), servicedInfo=servicedInfo, attachParas={"pw": pw} ) # 返回刷卡 OK 的信息 self.response_card_start( "01", portStrHex, cardNoHex=cardNoHex, balance=card.balance, orderNo=orderNo, chargeTime=chargeTime, pw=pw, cardType=cardType, leftDays=leftDays ) logger.info("card start ok! event has been report!") # 主动下发指令B2-02 ,开启柜门开启充电启动设备 self.start_device(portStr, orderNo, pw, "02") # 启动设备之后,注册服务,并更新端口缓存值 ServiceProgress.register_card_service( self.device, int(portStr), card, { "orderNo": orderNo, "cardOrderNo": cardConsumeOrderNo }, finishedTime = int(time.time()) + 7 * 24 * 60 * 60 ) portDict = { "status": Const.DEV_WORK_STATUS_WORKING, "isStart": True, "orderNo": orderNo, "cardOrderNo": cardConsumeOrderNo, "cardNo": cardNo, "openId": card.openId, "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "pw": pw, "monthlyPackageId": None if cardType == 1 else str(monthlyPackage.id) } Device.update_dev_control_cache(self.device["devNo"], {portStr: portDict}) # 通知用户已经启动了 self.notify_user( managerialOpenId = card.managerialOpenId, templateName = "consume_notify", title = u"您正在刷卡启动充电柜业务", money = u"使用结束后计算费用", serviceType = u"刷卡消费, 当前启动为: {}柜门".format(portStr), finishTime = datetime.datetime.now().strftime(Const.DATETIME_FMT) ) # 刷卡结束上报 class ChargeCabinetEventB1(WorkEvent, ChargeCabinetEventBase): def do(self): # zjl 去掉参数 """ 刷卡结束的时候, 仅仅会接受到刷卡上报的信息 和主板商沟通过不会再有充电结束的上报 :return: """ portStrHex = self.event_data['portHex'] # zjl cardNoHex = self.event_data['cardNoHex'] # zjl cardNo = self.event_data.get("cardNo") portStr = self.event_data.get("portStr") orderNo = self.event_data.get("orderNo") devCache = Device.get_dev_control_cache(self.device.devNo) portCache = devCache.get(portStr) or dict() if not portCache: logger.error("no port cache, error card finished!, card is <{}>".format(cardNo)) self.response_card_finish(cardNoHex, portStrHex, cardStatus="03") return if cardNo != portCache.get("cardNo"): logger.error("card no is not equal , card is <{}>".format(cardNo)) self.response_card_finish(cardNoHex, portStrHex, cardStatus="03") return pw = portCache.get("pw", "") openId = portCache.get("openId", "") cardOrderNo = portCache.get("cardOrderNo", "") try: card = self.update_card_dealer_and_type(cardNo) consumeOrder = ConsumeRecord.objects.get(orderNo=orderNo) cardConsumeOrder = CardConsumeRecord.objects.get(orderNo=cardOrderNo) except Exception as e: logger.exception(e) return # 请求结束充电 发送 result = self.start_device(portStr, orderNo, pw, "12") data = result.get("data") curInfo = self.deviceAdapter._parse_result_B2(data) DevicePortReport.create( devNo=self.device.devNo, port=curInfo.get("portStr"), orderNo=curInfo.get("orderNo"), openId=openId, voltage=curInfo.get("voltage"), power=curInfo.get("power"), elec=curInfo.get("elec"), chargeTime=curInfo.get("chargeTime"), stayTime=curInfo.get("stayTime"), isBilling=portCache.get("status", Const.DEV_WORK_STATUS_IDLE) == Const.DEV_WORK_STATUS_WORKING ) # 刷卡计费 consumeMoney = Calculater(self.device, consumeOrder).result self.deduct_for_card(portStr, card, consumeMoney, consumeOrder, cardConsumeOrder) # 结束服务 consumeDict = { "chargeIndex": portStr, 'actualNeedTime': curInfo.get("chargeTime"), 'elec': curInfo.get("elec"), 'stayTime': curInfo.get("stayTime"), 'chargeTime': curInfo.get("chargeTime"), } ServiceProgress.update_progress_and_consume_rcd( self.device["ownerId"], { "cardId": str(card.id), "open_id": consumeOrder.openId, "device_imei": self.device["devNo"], "port": int(portStr), "isFinished": False }, consumeDict ) # 最后回复B1 指令 monthlyPackageId = portCache.get("monthlyPackageId") if monthlyPackageId: monthlyPackage = MonthlyPackage.objects.get(id = monthlyPackageId) self.response_card_finish( cardNoHex = cardNoHex, portStrHex = portStrHex, balance = RMB(0), consumeMoney = RMB(consumeMoney), cardType = 2, leftDays = max((monthlyPackage.expireDateTime - datetime.datetime.now()).days, 0) ) extra = [({"消费金额": "{}元".format(consumeMoney), "余额": "{}元".format(0)})] else: self.response_card_finish( cardNoHex = cardNoHex, portStrHex = portStrHex, balance = RMB(card.balance), consumeMoney = RMB(consumeMoney) ) extra=[({"消费金额": "{}元".format(consumeMoney), "余额": "{}元".format(card.balance)})] self.notify_user_service_complete( service_name='充电', openid=card.managerialOpenId, port=portStr, address=self.device.group['address'], reason=self.event_data.get('reasonDesc'), finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), extra=extra) Device.clear_port_control_cache(self.device.devNo, portStr) # 状态上报 class ChargeCabinetEventC0(WorkEvent, ChargeCabinetEventBase): def do(self, **args): orderNo = self.event_data.get("orderNo") orderNoHex = self.event_data.get("orderNoHex") portStr = self.event_data.get("portStr") voltage = self.event_data.get("voltage") power = self.event_data.get("power") elec = self.event_data.get("elec") chargeTime = self.event_data.get("chargeTime") temperature = self.event_data.get("temperature") status = self.event_data.get("status") stayTime = self.event_data.get("stayTime") devCache = Device.get_dev_control_cache(self.device["devNo"]) portCache = devCache.get(portStr, dict()) # 首先回复设备表示该状态已经收到 if self.device.driverVersion in ['v1.0.0', 'v1.0.1']: sendData = orderNoHex + "{:0>2X}".format(int(portStr)) + "01" self.deviceAdapter._send_data(funCode="C0", sendData=sendData, cmd=DeviceCmdCode.OPERATE_DEV_NO_RESPONSE) # 空闲状态的端口过滤 if status == Const.DEV_WORK_STATUS_IDLE: logger.info("device {} port {} report event, useless info!".format(self.device["devNo"], portStr)) return openId = portCache.get("openId") if not openId: logger.info("port report event, has not openId, useless info!") return if orderNo != portCache.get("orderNo"): logger.info("port report event, not equal orderNo, useless info!{}".format(portStr)) return # 保存数据库, 更新端口缓存 data = { "devNo": self.device["devNo"], "port": portStr, "orderNo": orderNo, "openId": openId, "voltage": voltage, "power": power, "elec": elec, "chargeTime": chargeTime, "stayTime": stayTime } if status == Const.DEV_WORK_STATUS_OCCUPY: data.update({"isBilling": False}) reportRecord = DevicePortReport.create(**data) if not reportRecord: logger.info("port report event, save db error!") return portCache.update({ "voltage": voltage, "elec": elec, "temperature": temperature }) Device.update_dev_control_cache(self.device["devNo"], {portStr: portCache}) # 充电结束上报 class ChargeCabinetEventC1(WorkEvent, ChargeCabinetEventBase): def do(self, **args): orderNo = self.event_data.get("orderNo") portStr = self.event_data.get("portStr") orderNoHex = self.event_data.get("orderNoHex") chargeTime = int(self.event_data.get("chargeTime")) elec = self.event_data.get("elec") voltage = self.event_data.get("voltage") power = self.event_data.get("power") stayTime = self.event_data.get("stayTime") devCache = Device.get_dev_control_cache(self.device["devNo"]) portCache = devCache.get(portStr, dict()) openId = portCache.get("openId") self.response_finish(orderNoHex, portStr) if portCache.get("orderNo") != orderNo: logger.warning("charge finish, but the orderNo not equal {}".format(portCache)) # TODO 这种情况怎么去处理 # 更新充电的时间 结束充电的时候 is_billing 一定是有效的 也就是说这一笔记录是需要算充电钱的 DevicePortReport.create( devNo=self.device.devNo, port=portStr, orderNo=orderNo, openId=openId, voltage=voltage, power=power, elec=elec, chargeTime=chargeTime, stayTime=stayTime ) # 更新新的portCache 这一轮的计费结束 就是 下一轮 占位费的开始 portCache.update({ "status": Const.DEV_WORK_STATUS_OCCUPY, # 状态更新进入占位状态 "occupyStartTime": int(time.time()) # 占位的起始时间 }) Device.update_dev_control_cache(self.device.devNo, {portStr: portCache}) if self.event_data.get("reasonCode") == "06": # 密码开锁的,需要结单, 这个可能是离线的信息上报 需要直接结单 consumeOrder = ConsumeRecord.objects.get(orderNo = orderNo) # type: ConsumeRecord consumeMoney = Calculater(self.device, consumeOrder).result consumeDict = { "chargeIndex": portStr, 'actualNeedTime': self.event_data.get("chargeTime"), 'elec': self.event_data.get("elec"), 'stayTime': self.event_data.get("stayTime"), 'chargeTime': self.event_data.get("chargeTime"), } if self.is_start_by_card(portStr): cardNo = portCache.get("cardNo") card = self.update_card_dealer_and_type(cardNo) cardOrderNo = portCache.get("cardOrderNo", "") cardConsumeOrder = CardConsumeRecord.objects.get(orderNo = cardOrderNo) self.deduct_for_card(portStr, card, consumeMoney, consumeOrder, cardConsumeOrder) consumeDict = { "chargeIndex": portStr, 'actualNeedTime': self.event_data.get("chargeTime"), 'elec': self.event_data.get("elec"), 'stayTime': self.event_data.get("stayTime"), 'chargeTime': self.event_data.get("chargeTime"), DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: RMB(consumeMoney).mongo_amount } else: # 扫码启动的设备的情况 # 切换到运行结束的状态 consumeOrder.update(money=RMB(consumeMoney).mongo_amount, coin=VirtualCoin(consumeMoney).mongo_amount) consumeOrder.reload() consumeOrder.s_to_e() ServiceProgress.update_progress_and_consume_rcd( self.device["ownerId"], { "open_id": consumeOrder.openId, "device_imei": self.device["devNo"], "port": int(portStr), "isFinished": False }, consumeDict ) Device.clear_port_control_cache(self.device.devNo, portStr) # 开始通知用户充电结束 进入占位状态 else: user = MyUser.objects.filter(openId=portCache.get("openId"), groupId=self.device["groupId"]).first() group = Group.get_group(self.device["groupId"]) self.notify_user( managerialOpenId=user.managerialOpenId if user else "", templateName="service_complete", title=u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}-{port}\\n\\n服务地址:\\t\\t{group}\\n\\n充电时间:\\t\\t{chargeTime}分钟".format( reason=self.event_data["reason"], logicalCode=self.device["logicalCode"], port=self.event_data["portStr"], group=group["address"], chargeTime=chargeTime, ), service=u"充电柜充电服务", finishTime=str(datetime.datetime.now())[: 19], remark=u'充电已经结束,请及时扫码,结束本次订单,取走您的电池!' ) # 北向接口, 传递给API调用方 notify_event_to_north_v2(self.device["devNo"], self.event_data) class ChargeCabinetEventCa(WorkEvent, ChargeCabinetEventBase): def do(self, **args): logger.info("device <{}> receive CA, event data = {}".format(self.device.devNo, self.event_data)) subChargeList = self.event_data["subChargeList"] for _sub in subChargeList: ChargeCabinetEventC0(self.deviceAdapter, _sub).do()