123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import datetime
- import logging
- import time
- from decimal import Decimal
- from apilib.utils_datetime import timestamp_to_dt
- from apps.common.utils import int_to_hex
- from apps.web.common.models import TempValues
- from apps.web.constant import Const, DeviceCmdCode, MQTT_TIMEOUT
- from apps.web.core.adapter.base import SmartBox, fill_2_hexByte
- from apps.web.core.exceptions import ServiceException
- from apps.web.core.networking import MessageSender
- from apps.web.dealer.models import Dealer
- from apps.web.device.models import Device
- logger = logging.getLogger(__name__)
- class ChangYuanCarV2(SmartBox):
- STATUS_MAP = {
- "AA": Const.DEV_WORK_STATUS_WORKING,
- "AB": Const.DEV_WORK_STATUS_APPOINTMENT,
- "AC": Const.DEV_WORK_STATUS_CONNECTED,
- "55": Const.DEV_WORK_STATUS_IDLE
- }
- FINISH_REASON_MAP = {
- "E0": "过压",
- "E1": "过流",
- "E2": "超功率",
- "E3": "限时时间到",
- "E4": "正常结束",
- "E5": "急停结束",
- "E7": "支付金额结束",
- "E8": "汽车充满电结束",
- "E9": "超温结束",
- "EA": "计量通信失败结束",
- "EB": "汽车连接失败",
- "EC": "用户刷卡停止"
- }
- DEFAULT_DISABLE_DEVICE = False
- DEFAULT_CARD_CONSUME = 100
- def _send_data(self, funCode, data, timeout = MQTT_TIMEOUT.NORMAL):
- result = MessageSender.send(device = self.device, cmd = DeviceCmdCode.OPERATE_DEV_SYNC, payload = {
- "IMEI": self._device["devNo"],
- "funCode": funCode,
- "data": data
- }, timeout = timeout)
- if result["rst"] != 0:
- if result['rst'] == -1:
- raise ServiceException({'result': 2, 'description': u'充电桩正在玩命找网络,请稍候再试'})
- elif result['rst'] == 1:
- raise ServiceException({'result': 2, 'description': u'充电桩主板连接故障'})
- else:
- raise ServiceException({'result': 2, 'description': u'系统错误'})
- return result
- def _get_device_settings(self):
- result = self._send_data("A0", "00")
- data = result.get("data", dict())
- isFree = data[8: 10]
- free = 0 if isFree == "00" else 1
- electricPrice = int(data[10: 12], 16) / 100.0
- maxConsume = int(data[12: 16], 16) / 100.0
- maxChargeTime = int(data[16: 20], 16)
- volume = int(data[20: 22], 16)
- return {
- "is_free": free,
- "electricPrice": electricPrice,
- "maxConsume": maxConsume,
- "maxChargeTime": maxChargeTime,
- "volume": volume
- }
- def _set_device_settings(self, conf):
- isFree = conf.pop("is_free", None)
- electricPrice = conf.pop("electricPrice", None)
- maxConsume = conf.pop("maxConsume", None)
- maxChargeTime = conf.pop("maxChargeTime", None)
- volume = conf.pop("volume", None)
- data = ""
- data += "AA" if int(isFree) == 1 else "00"
- data += fill_2_hexByte(hex(int(Decimal(electricPrice) * 100)), 2)
- data += fill_2_hexByte(hex(int(Decimal(maxConsume) * 100)), 4)
- data += fill_2_hexByte(hex(int(Decimal(maxChargeTime))), 4)
- data += fill_2_hexByte(hex(int(Decimal(volume))), 2)
- self._send_data("A1", data)
- # TODO zjl 数据库保存一份
- def _get_consume_count(self):
- """
- 从充电桩上获取累计 的消费金额以及消费电量
- :return:
- """
- result = self._send_data("A2", "00")
- data = result.get("data", dict())
- totalConsume = data[8: 14]
- totalElec = data[14: 20]
- return {
- "totalConsume": int(totalConsume, 16) / 100.0,
- "totalElec": int(totalElec, 16) / 100.0
- }
- def _clean_consume_count(self, consume = False, elec = False):
- """
- 清除充电桩上累计信息
- :param consume:
- :param elec:
- :return:
- """
- if consume and elec:
- sendData = "E3"
- elif not consume:
- sendData = "E2"
- elif not elec:
- sendData = "E1"
- else:
- return
- self._send_data("A3", sendData)
- def _get_not_refund_record(self):
- """
- 获取最近10条未返费的卡号以及剩余金额
- :return:
- """
- result = self._send_data("A4", "00")
- data = result.get("data", dict())
- noRefund = list()
- data = data[8: -8]
- for i in xrange(10):
- tempData = data[12 * i: 12 * (i + 1)]
- cardNo = tempData[: 8]
- amount = tempData[8: 12]
- if cardNo == "FFFFFFFF" or cardNo == "00000000": continue
- amount = int(amount, 16) / 100.0
- noRefund.append({"cardNo": cardNo, "amount": amount})
- return noRefund
- def _get_recharge_record_from_device(self, num):
- """
- 从设备上 获取最近的几条充电记录
- :param num:
- :return:
- """
- num = 30 if num > 30 else num
- numHex = fill_2_hexByte(hex(int("80", 16) + int(num)), 2)
- result = self._send_data("A5", numHex)
- data = result.get("data", dict())
- dataLen = int(data[6: 8], 16) * 2 - 2 # 数据字符数量
- recordData = data[10: 10 + dataLen]
- recordList = list()
- while recordData:
- cardNo = recordData[: 8]
- electricNum = int(recordData[8: 12], 16) / 100.0
- chargeTime = int(recordData[12: 16], 16)
- chargeBalance = int(recordData[16: 20], 16) / 100.0
- cardBalance = int(recordData[20: 26], 16) / 100.0
- recordData = recordData[26:]
- if cardNo == "FFFFFFFF":
- continue
- recordList.append(
- {
- "type": u"{} 刷卡充电".format(cardNo) if cardNo != "00000000" else u"扫码充电",
- "elec": u"{} 度".format(electricNum),
- "duration": u"{} 分钟".format(chargeTime),
- "chargeBalance": u"{} 元".format(chargeBalance),
- "cardBalance": u"{} 元".format(cardBalance) if cardBalance else u"本次无刷卡消费"
- }
- )
- return recordList
- def _get_device_status(self):
- """
- 即时获取设备状态
- :return:
- """
- result = self._send_data("A6", "00")
- data = result.get("data", dict())
- status = data[8:10]
- cardNo = data[30:38]
- if cardNo == "00000000":
- cardNo = u"在线支付"
- result = {
- "status": self.STATUS_MAP.get(status, Const.DEV_WORK_STATUS_IDLE)
- }
- if result["status"] == Const.DEV_WORK_STATUS_WORKING:
- result.update({
- "voltage": int(data[10:14], 16) / 1.0,
- "power": int(data[14:18], 16) / 1.0,
- "usedElec": int(data[18:22], 16) / 100.0,
- "usedTime": int(data[22:26], 16),
- "leftMoney": int(data[26:30], 16) / 100.0,
- "cardNo": cardNo
- })
- Device.update_dev_control_cache(self.device.devNo, result)
- return result
- def _start(self, money):
- """
- 微信支付命令
- :param money:
- :return:
- """
- sendData = "{:0<8}".format(fill_2_hexByte(hex(int(money * 100)), 4))
- result = self._send_data("A7", sendData, timeout = MQTT_TIMEOUT.START_DEVICE)
- data = result.get("data", dict())
- if data[8:12] != "4F4B":
- raise ServiceException({'result': 2, 'description': u'设备相应错误,未能成功启动设备,请上报设备故障'})
- return result
- def _set_device_disable(self, disable):
- """
- 设置 设备的可用
- :param disable:
- :return:
- """
- status = "E9" if disable else "E8"
- result = self._send_data("A9", status)
- data = result.get("data", dict())
- if data[8: 12] != "4F4B":
- raise ServiceException({'result': 2, 'description': u'设置失败,请重试'})
- otherConf = self._device.get("otherConf", {})
- otherConf["disableDevice"] = disable
- Device.objects.filter(devNo = self._device["devNo"]).update(otherConf = otherConf)
- Device.invalid_device_cache(self._device["devNo"])
- def _async_card_balance(self, cardType, cardNo, balance):
- """
- 同步卡的余额 加卡的余额一次行下发过去
- :param cardType: 00 下发单位是元 01 下发单位是 分
- :param cardNo: 卡号
- :param balance: 单位是RMB
- :return:
- """
- balance = balance if cardType == "00" else balance * 100
- # 获取随机流水号
- sidKey = "{}-{}".format(self.device.devNo, cardNo)
- sid = TempValues.get(sidKey)
- balanceHex = int_to_hex(int(balance), 6)
- sidHex = int_to_hex(int(sid))
- MessageSender.send(device = self.device, cmd = DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, payload = {
- "IMEI": self.device.devNo,
- "funCode": "F3",
- "data": cardType + balanceHex + sidHex
- })
- def _read_card_balance(self):
- result = self._send_data("AD", "00")
- data = result["data"]
- if data[6:8] == "02" and data[8:12] == "4552":
- raise ServiceException({'result': 2, 'description': u'读取失败,请将卡放置在设备指定区域'})
- cardNo = data[8:16]
- balance = int(data[16:22], 16) / 100.0
- return {
- "cardNo": cardNo,
- "balance": balance
- }
- def _stop(self, isLawFul = True):
- logger.info("ready to stop device <{}>".format(self.device.devNo))
- data = "00" if isLawFul else "01"
- self._send_data("AF", data)
- def _restart_device(self):
- self._send_data("B0", "00")
- def _response_a8id(self, a8id):
- MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, {
- "IMEI": self._device["devNo"],
- "funCode": "FA",
- "data": "",
- "a8id": a8id
- })
- @staticmethod
- def _parse_event_A8(data):
- reasonCode = data[8:10]
- desc = ChangYuanCarV2.FINISH_REASON_MAP.get(reasonCode, u"未知错误")
- cardNo = data[10:18]
- usedElec = int(data[18:22], 16) / 100.0
- chargeTime = int(data[22:26], 16)
- balance = int(data[26:30], 16) / 100.0
- cardBalance = int(data[30:36], 16) / 100.0
- payMoney = int(data[38:40] + data[36:38], 16) / 100.0
- return {
- "reasonCode": reasonCode,
- "desc": desc,
- "cardNo": cardNo,
- "usedElec": usedElec,
- "chargeTime": chargeTime,
- "balance": balance,
- "cardBalance": cardBalance,
- "payMoney": payMoney
- }
- @staticmethod
- def _parse_event_AE(data):
- statusMap = {
- "B1": Const.DEV_WORK_STATUS_CONNECTED,
- "B5": Const.DEV_WORK_STATUS_IDLE
- }
- status = statusMap.get(data[8:10], Const.DEV_WORK_STATUS_IDLE)
- return {
- "status": status
- }
- @staticmethod
- def _parse_event_B1(data):
- cardNo = data[8:16]
- return {
- "cardNo": cardNo
- }
- @staticmethod
- def _parse_event_B2(data):
- """防止他们注册错设备类型"""
- if len(data) <= 20:
- return dict()
- leftBalance = int(data[8:12], 16) / 100.0
- usedElec = int(data[12:16], 16) / 100.0
- temperature = int(data[18:20], 16)
- power = int(data[20:24], 16)
- voltage = int(data[28:32], 16)
- sid = int(data[32:36], 16)
- temperature = temperature if data[16:18] == "00" else -temperature
- return {
- "leftBalance": leftBalance,
- "usedElec": usedElec,
- "temperature": u"{}度 更新于{}".format(temperature, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
- "power": power,
- "voltage": voltage,
- "sid": sid
- }
- @staticmethod
- def _parse_event_B3(data):
- cardNo = data[8:16]
- beforeRefund = int(data[16:22], 16) / 100.0
- refund = int(data[22:26], 16) / 100.0
- afterRefund = int(data[26:32], 16) / 100.0
- return {
- "cardNo": cardNo,
- "beforeRefund": beforeRefund,
- "refund": refund,
- "afterRefund": afterRefund
- }
- @staticmethod
- def _parse_event_F4(data):
- """
- 请求卡余额同步 此指令是里显卡同步余额的发起指令
- cardType 01表示预付卡 上报单位为分 00表示次卡 上报单位是元
- :param data:
- :return:
- """
- cardType = data[8:10]
- cardNo = data[10:18]
- cardBalanceHex = data[18:24]
- if cardType == "00":
- cardBalance = int(cardBalanceHex, 16)
- else:
- cardBalance = int(cardBalanceHex, 16) / 100.0
- return {
- "cardType": cardType,
- "cardNo": cardNo,
- "cardBalance": cardBalance
- }
- @staticmethod
- def _parse_event_F3(data):
- """
- 余额同步成功 这个地方上报告知成功还是失败 如果存在失败的情况 将订单的状态重新切换或finishedPay
- :param data:
- :return:
- """
- cardType = data[8:10]
- cardNo = data[10:18]
- cardBalanceHex = data[18:24]
- asyncStatus = data[24:26]
- sidHex = data[26:30]
- if cardType == "00":
- cardBalance = int(cardBalanceHex, 16)
- else:
- cardBalance = int(cardBalanceHex, 16) / 100.0
- return {
- "cardType": cardType,
- "cardNo": cardNo,
- "cardBalance": cardBalance,
- "asyncStatus": True if asyncStatus == "AA" else False,
- "sid": int(sidHex, 16)
- }
- def test(self, coins):
- return self._start(coins)
- @property
- def isHaveStopEvent(self):
- return True
- def get_dev_setting(self):
- settings = self._get_device_settings()
- otherConf = self.device.get("otherConf", dict())
- disableDevice = otherConf.get("disableDevice", self.DEFAULT_DISABLE_DEVICE)
- needBindCard = otherConf.get("needBindCard", True)
- settings.update({"disableButton": int(disableDevice), "needBindCard": int(needBindCard)})
- otherConf.update(settings)
- Device.objects.filter(devNo = self.device.devNo).update(otherConf = otherConf)
- Device.invalid_device_cache(self.device.devNo)
- consume = self._get_consume_count()
- settings.update(consume)
- devCache = Device.get_dev_control_cache(self.device.devNo)
- temperature = devCache.get("temperature", "暂无数据")
- settings.update({"temperature": temperature})
- return settings
- def set_device_function_param(self, request, lastSetConf):
- disable = request.POST.get("disableButton")
- if disable is not None:
- dealer = Dealer.objects.filter(id = self.device.ownerId).first()
- if "dealerDisableDevice" not in dealer.features:
- raise ServiceException({"result": 2, "description": "抱歉,您无此操作权限,请联系厂家获取权限"})
- return self.set_dev_disable(bool(int(disable)))
- if "needBindCard" in request.POST:
- needBindCard = bool(int(request.POST.get('needBindCard')))
- otherConf = self.device.get("otherConf", dict())
- otherConf.update({"needBindCard": needBindCard})
- Device.objects.get(devNo=self.device.devNo).update(otherConf=otherConf)
- Device.invalid_device_cache(self.device.devNo)
- return
- newDict = {
- "is_free": request.POST.get("is_free", None),
- "electricPrice": request.POST.get("electricPrice", None),
- "maxConsume": request.POST.get("maxConsume", None),
- "maxChargeTime": request.POST.get("maxChargeTime", None),
- "volume": request.POST.get("volume", None)
- }
- otherConf = self.device.get("otherConf", dict())
- for setName, setValue in newDict.items():
- if setValue is not None:
- otherConf.update({setName: setValue})
- self._set_device_settings(otherConf)
- def set_dev_disable(self, disable):
- self._set_device_disable(disable)
- def set_device_function(self, request, lastSetConf):
- remoteStop = request.POST.get("remoteStop", None)
- clearTotalConsume = request.POST.get("clearTotalConsume", False)
- clearTotalElec = request.POST.get("clearTotalElec", False)
- reboot = request.POST.get("reboot", False)
- # 停止设备
- if remoteStop:
- self._stop()
- if reboot:
- self._restart_device()
- # 清除设备计费信息
- if any([clearTotalConsume, clearTotalElec]):
- self._clean_consume_count(clearTotalConsume, clearTotalElec)
- def get_device_function_by_key(self, keyName):
- if keyName == "noRefund":
- records = self._get_not_refund_record()
- return {"noRefund": records}
- elif keyName == "record":
- records = self._get_recharge_record_from_device(15)
- return {"record": records}
- def stop(self, port = None):
- return self._stop()
- def check_dev_status(self, attachParas = None):
- result = self._get_device_status()
- status = result.get("status")
- if status != Const.DEV_WORK_STATUS_CONNECTED:
- raise ServiceException({"result": "2", "description": u"请先将充电桩枪把插上汽车充电口"})
- def analyze_event_data(self, data):
- cmdCode = data[4:6]
- funcName = "_parse_event_{}".format(cmdCode)
- func = getattr(ChangYuanCarV2, funcName, None)
- if func and callable(func):
- eventData = func(data)
- # 解析出错的情况下 或者解析没有返回的情况下
- if not eventData:
- logger.error("receive event data error parse, data <{}>".format(data))
- return
- eventData.update({"cmdCode": cmdCode})
- return eventData
- def start_device(self, package, openId, attachParas):
- devCache = Device.get_dev_control_cache(self.device.devNo)
- if devCache.get("status") != Const.DEV_WORK_STATUS_CONNECTED:
- status = self._get_device_status().get("status")
- if status != Const.DEV_WORK_STATUS_CONNECTED:
- raise ServiceException({'result': 2, 'description': u'请先将充电桩枪把连接'})
- coins = package.get("coins")
- price = package.get("price")
- orderNo = attachParas.get("orderNo")
- result = self._start(price)
- rechargeRcdId = attachParas.get("linkedRechargeRecordId")
- startTimeStamp = int(time.time())
- devCache = {
- "openId": openId,
- "isStart": True,
- "coins": coins,
- "price": price,
- "rechargeRcdId": str(rechargeRcdId) if rechargeRcdId else None, # zjl ObjectId -> str
- "status": Const.DEV_WORK_STATUS_WORKING,
- "startTime": timestamp_to_dt(startTimeStamp).strftime("%Y-%m-%d %H:%M:%S"),
- "orderNo": orderNo,
- "finishedTime": startTimeStamp + 24 * 60 * 60 # zjl new
- }
- Device.invalid_device_control_cache(self.device.devNo)
- Device.update_dev_control_cache(self.device.devNo, devCache)
- result["finishedTime"] = startTimeStamp + 60 * 60 * 24
- if openId: # zjl new
- result["rechargeRcdId"] = str(rechargeRcdId) if rechargeRcdId else None # zjl new
- return result
- def dealer_get_port_status(self):
- devInfo = self.get_port_info(1)
- if "status" not in devInfo:
- devInfo["status"] = Const.DEV_WORK_STATUS_IDLE
- return {"1": devInfo}
- def get_port_info(self, port):
- self._get_device_status()
- devCache = Device.get_dev_control_cache(self.device.devNo) or dict()
- return devCache
- def active_deactive_port(self, port, active):
- if not active:
- self._stop()
- else:
- super(ChangYuanCarV2, self).active_deactive_port(port, active)
|