# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import hashlib import hmac import json import logging import math import random import requests from django.conf import settings from mongoengine import StringField, DateTimeField, BooleanField, IntField, QuerySet from apps.web.api.utils import bd09_to_gcj02, get_coordinates_and_nums from apps.web.agent.models import Agent from apps.web.api.utils import AES_CBC_PKCS5padding_encrypt, AES_CBC_PKCS5padding_decrypt from apps.web.common.models import District from apps.web.constant import Const from apps.web.core.db import Searchable from apps.web.dealer.models import Dealer from apps.web.device.models import Device, Group, Part, GroupDict, DeviceDict from apps.web.user.models import ConsumeRecord logger = logging.getLogger(__name__) class PLATFORM_TYPE(object): SD = 0 # 山东 JN = 1 # 济南 class GroupIdMap(Searchable): groupId = StringField(verbose_name = u"组ID", unique = True) stationId = StringField(verbose_name = u"充电站ID", unique = True) @classmethod def add(cls, groupId): if cls._is_added(groupId): return h = hashlib.md5() h.update(groupId.encode('utf-8')) stationId = h.hexdigest()[8:24] return cls(groupId = groupId, stationId = stationId).save() @classmethod def get_groupId(cls, stationId): m = cls.objects.filter(stationId = stationId).first() if not m: return "" else: return m.groupId @classmethod def get_stationId(cls, groupId): m = cls.objects.filter(groupId = groupId).first() if not m: return "" else: return m.stationId @classmethod def _is_added(cls, groupId): return bool(cls.objects.filter(groupId = groupId).count()) class GB2260(Searchable): # GB2260 国家行政区编码对应表 code = StringField(verbose_name = "编码", unique = True) name = StringField(verbose_name = "名称") codeDict = {} @classmethod def _load(cls): for item in cls.objects.all(): cls.codeDict.update({item.name: item.code}) @classmethod def get_code(cls, name): if not cls.codeDict: cls._load() return cls.codeDict.get(name, "000000") class ShanDongNorther(Searchable): """ 山东省能源局/济南市静态交通 的账号记录 兼容双方 备注:本次修改为兼容修改 字段以及函数均通过兼容测试 2022-04-24 zjl """ # 北向信息 即我们推送到平台所需要的信息时候需要用到的 northToken = StringField(verbose_name=u"token", default="") # 推送信息的时候的身份验证 northTokenExpiredTime = DateTimeField(verbose_name=u"token的过期时间", default=datetime.datetime.now) # 过期重新获取 northPort = StringField(verbose_name=u"推送的IP地址(山东)", default="") northPortJn = StringField(verbose_name=u"推送的IP地址(济南)", default="") # 省平台/济南静态交通的账号信息 agentOperatorID = StringField(verbose_name=u"平台机构代码", default="") # 获取northToken时候使用 可以理解为代理商的营业执照 agentOperatorSecret = StringField(verbose_name=u"平台机构秘钥", default="") # 获取northToken时候使用 agentSigSecret = StringField(vebose_name=u"平台机构签名秘钥", default="") # 我方主动推送数据的时候, 加、解密 数据使用 dataSecret = StringField(verbose_name=u"数据秘钥", default="") # 我方主动推送数据的时候,加、解密 数据使用 dataSecretIV = StringField(verbose_name=u"数据秘钥向量", default="") # 我方主动推送数据的时候,加、解密 数据使用 # 我们的信息 即省平台拉取的时候需要用到的 northOperatorID = StringField(verbose_name=u"账号", default="") # 省平台登录我方服务器获取token时候使用(由我方提供) northOperatorSecret = StringField(verbose_name=u"密码", default="") # 省平台登录我方服务器获取token时候使用(由我方提供) sigSecret = StringField(vebose_name=u"平台机构秘钥", default="") # 省平台拉取我方服务器数据时候 加、解密数据使用 pullDataSecret = StringField(verbose_name=u"数据秘钥", default="") # 省平台拉取我方服务器数据时候 加、解密数据使用 pullDataSecretIV = StringField(verbose_name=u"数据秘钥", default="") # 省平台拉取我方服务器数据时候 加、解密数据使用 # 辅助信息 对于同一个代理商不同经销商来说 上面的信息都是一样的 只有下面的信息不一样 dealerId = StringField(verbose_name=u"经销商的ID") # 放置在token里面的信息 我方服务器用于从设备找经销商 equipOperatorID = StringField(verbose_name=u"设备所属方") # 其实就是经销商的营业执照(可以理解为省平台的经销商ID) platform = IntField(verbose_name="平台种类", default=PLATFORM_TYPE.SD) # 用于鉴别是山东的还是济南的 后续接口可能会归为一个 needReport = BooleanField(verbose_name = "是否需要向省平台报告", default = True) @property def ipPort(self): """ 区分到底是推送到省平台还是 济南静态交通 """ return self.northPort if self.isSdPlatform else self.northPortJn @property def isSdPlatform(self): return self.platform == PLATFORM_TYPE.SD def get_token_data(self): """ 获取token的载数据 身份验证以平台为维度获取 那么token的范围也以 平台为准 即 northOperatorID """ return { "northOperatorID": self.northOperatorID, "platform": self.platform } @classmethod def get_norther(cls, **kwargs): # type:(dict) -> QuerySet """ 获取norther的时候 有两个维度获取方式 第一种,我方主动推送的方式,由于每一个norther的 dealerId 唯一 所以一定能找到唯一的一个norther 第二种,我方被动回复 由于省平台拉取信息是以平台为单位,即AgentOperator 此时的norther不唯一了(有可能同一个平台下 有很多经销商都要对接) """ filters = { "platform": kwargs["platform"] } # 通过设备找norther 主动推送 唯一 kwargs.get("dealerId") and filters.update({"dealerId": kwargs["dealerId"]}) # 通过token的信息找 被动回复 不唯一 kwargs.get("northOperatorID") and filters.update({"northOperatorID": kwargs["northOperatorID"]}) return cls.objects.filter(**filters) @staticmethod def bd09_to_gcj02(lng, lat): """ 百度坐标系到google坐标系的经纬度转换 :param lng: 百度经度 :param lat: 百度维度 :return: """ if lng == 0.0 or lat == 0.0: return lng, lat x_pi = 3.14159265358979324 * 3000.0 / 180.0 x = lng - 0.0065 y = lat - 0.006 z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi) theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi) gg_lng = z * math.cos(theta) gg_lat = z * math.sin(theta) return [gg_lng, gg_lat] def get_sig(self, data, push=False): """ 生成签名字符串 根据使用的场景,确认签名的盐 :param data: 生成签名的数据 iter :param push: 推送还是拉取 :return: """ sigSecret = str(self.agentSigSecret) if push else str(self.sigSecret) return hmac.new(sigSecret, data, hashlib.md5).hexdigest().upper() def send_request(self, url, **kwargs): """ 主动发送HTTP请求获取数据 秘钥以及签名 :param url: :param kwargs: :return: """ headers = {"Content-Type": "application/json;charset=utf-8"} token = kwargs.pop("token", None) if token: headers.update({"Authorization": "Bearer {}".format(token)}) timeout = kwargs.pop("timeout", 5) # 主动推送 加密以及向量为 dataSecret 和 dataSecretIV data = AES_CBC_PKCS5padding_encrypt( json.dumps(kwargs), dataSecret=self.dataSecret, dataSecretIV=self.dataSecretIV ) data = { "OperatorID": self.agentOperatorID, "TimeStamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S"), "Seq": "{:0>4}".format(random.randint(1, 1)), "Data": data } sig = self.get_sig(data.get("OperatorID") + data.get("Data") + data.get("TimeStamp") + data.get("Seq"), push=True) data.update({"Sig": sig}) try: response = requests.post(url = url, json = data, headers = headers, timeout = timeout) except requests.Timeout: return dict() except Exception as e: logger.exception(e) return dict() if response.status_code != 200: return dict() # 这个地方的解密 仅仅是为了解密数据 打印日志 调试使用 没有实际意义 if settings.DEBUG: responseData = response.json().get("Data", "") from pprint import pprint pprint(response.json()) responseData = json.loads( AES_CBC_PKCS5padding_decrypt(responseData) or "{}" ) logger.info("response result:{}".format(response.json())) logger.info("receive responseData:{}".format(responseData)) return response.json() def join_url(self, path): """ 拼接url :param path: :return: """ # 版本号的确定 return "{ipPort}/{path}".format( ipPort=self.ipPort, path=path ) def get_token(self): """ 获取省平台的token 更新token有效期 :return: """ if self.northToken and self.northTokenExpiredTime > datetime.datetime.now(): return self.northToken url = self.join_url("query_token") data = { "OperatorID": self.agentOperatorID, "OperatorSecret": self.agentOperatorSecret } result = self.send_request(url, **data) ret = result.get("Ret") if ret != 0: return responseJson = result.get("Data") responseData = json.loads( AES_CBC_PKCS5padding_decrypt(s=responseJson, dataSecret=self.dataSecret, dataSecretIV=self.dataSecretIV) or "{}" ) # 防止解析出错 tokenAvailableTime = responseData.get("TokenAvailableTime", 0) token = responseData.get("AccessToken", "") # 数据库更新 self.update( northToken=token, northTokenExpiredTime=datetime.datetime.now()+datetime.timedelta(seconds = tokenAvailableTime) ) return token def notification_station(self, groupId): """ 充电站信息上报 GROUP :param groupId: :return: """ data = self.get_station(groupId, self) url = self.join_url("notification_stationInfo") token = self.get_token() self.send_request(url = url, token = token, StationInfo = [data]) def notification_order_info(self, consumeDict, stopReason = None): """ 当运营商平台完成一次充电时,将订单信息推送至省级平台。 :param consumeDict: :param stopReason: :return: """ # 参数格式化 _time = datetime.datetime.now().strftime("%Y%m%d%H%M%S") totalPower = float("{:.2f}".format(float(consumeDict.get("totalPower")))) totalElecMoney = float("{:.2f}".format(float(consumeDict.get("totalElecMoney")))) seq = self.agentOperatorID + _time + str(random.randint(0, 9999)) logger.info("Shan Dong Norther send order Info, seq is {}".format(seq)) data = { "StartChargeSeq": seq, "ConnectorID": consumeDict.get("connectorId"), "StartTime": consumeDict.get("startTime"), "EndTime": consumeDict.get("endTime"), "TotalPower": totalPower, "TotalElecMoney": totalElecMoney, "TotalSeviceMoney": 0.00, "TotalMoney": totalElecMoney, "StopReason": stopReason or 1 } logger.info('notification_order_info:{}'.format(data)) url = self.join_url("notification_orderInfo") token = self.get_token() self.send_request(url, token = token, **data) def retry_push_notification_order_info(self, retry_push_dict): """ 能源局出现问题需要手动推送时候找到日志然后执行 此函数只能手动执行 """ data = retry_push_dict.copy() url = self.join_url("notification_orderInfo") token = self.get_token() self.send_request(url, token = token, **data) logger.info('retry_push_notification_order_info is ok seq={}'.format(retry_push_dict['StartChargeSeq'])) def alarm_report(self, devNo, code, desc = None, status = None): """ 当充电接口发生异常告警或故障时,运营商企业平台主动推送信息到省级平台。 :param devNo: :param code: :param desc: :param status: :return: """ desc = desc or u"设备故障" status = status or 0 data = { "equipmentID": devNo, "alert_time": str(datetime.datetime.now())[19:], "alert_code": code, "describe": desc, "status": status } url = self.join_url("alarm_report") token = self.get_token() self.send_request(url, token = token, AlarmInfos = [data]) def notification_station_status(self, statusDict): data = { "ConnectorID": statusDict.get("connectorID"), "Status": statusDict.get("status"), "CurrentA": statusDict.get("elec"), "VoltageA": statusDict.get("voltage"), "SOC": None, "begin_time": statusDict.get("startTime"), "current_kwh": statusDict.get("usedElec"), "current_meter": None, "bms_req_voltage": None, "bms_req_current": None } url = self.join_url("notification_stationStatus") token = self.get_token() self.send_request(url, token = token, ConnectorStatusInfos = data) @staticmethod def get_coordinates_and_nums(groupId): devices = Device.objects.filter(groupId = groupId) _count = devices.count() if _count == 0: return 0.0, 0.0, _count for dev in devices: if dev.location is not None and dev.location.has_key('coordinates'): coord = dev.location['coordinates'] return coord[0], coord[1], _count return 0.0, 0.0, _count def notification_order_info_jn(self, consumeDict, stopReason = None): """ 当运营商平台完成一次充电时,将订单信息推送至省级平台。 :param consumeDict: :param stopReason: :return: """ # 参数格式化 _time = datetime.datetime.now().strftime("%Y%m%d%H%M%S") totalPower = float("{:.2f}".format(float(consumeDict.get("totalPower")))) totalElecMoney = float("{:.2f}".format(float(consumeDict.get("totalElecMoney")))) seq = self.agentOperatorID + _time + str(random.randint(0, 9999)) logger.info("Ji Nan Norther send order Info, seq is {}".format(seq)) data = { "StartChargeSeq": seq, "ConnectorID": consumeDict.get("connectorId"), "StartTime": consumeDict.get("startTime"), "EndTime": consumeDict.get("endTime"), "TotalPower": totalPower, "PreDetails": "-", "PreElecMoney":0.00, "PreServiceMoney":0.00, "TotalElecMoney": totalElecMoney, "TotalSeviceMoney": 0.00, "TotalMoney": totalElecMoney, "StopReason": stopReason or 1 } logger.info('notification_order_info:{}'.format(data)) url = self.join_url("notification_orderInfo") token = self.get_token() self.send_request(url, token = token, OrderInfo=data) def notification_station_jn(self, groupId): """ 充电站信息上报 GROUP :param groupId: :return: """ group = Group.get_group(groupId) data = self.get_stations_info_jn(group, self) url = self.join_url("notification_stationInfo") token = self.get_token() self.send_request(url=url, token=token, StationInfo=[data]) @staticmethod def get_stations_info_jn(group, norther): # type:(GroupDict, ShanDongNorther) -> dict """ 获取站点的信息 """ # 获取经纬度 获取设备数量 经纬度使用火星坐标系转换 lng, lat, count = get_coordinates_and_nums(group.groupId) lng, lat = bd09_to_gcj02(lng, lat) # 获取设备信息 EquipmentInfos = list() devNos = Device.get_devNos_by_group([group.groupId]) for devNo in devNos: dev = Device.get_dev(devNo) # type: DeviceDict if dev.majorDeviceType != u"汽车充电桩": continue EquipmentInfos.append(norther.get_equipment(dev, norther)) # 充电站信息 stationId = GroupIdMap.get_stationId(group.groupId) dealer = group.owner data = { "StationID": stationId, # 充电站ID 20 "OperatorID": norther.agentOperatorID, # 组织机构代码 9 "EquipmentOwnerID": norther.equipOperatorID, # 设备所属方组织机构代码 9 "StationName": group.groupName, # 充电站名称描述 50 "CountryCode": "CN", # 国家代码 固定 "AreaCode": GB2260.get_code(District.get_area(group.get("districtId"))), # 地区编码 20 "Address": group.address, # 详细地址 50 "StationTel": "-", # 站点责任人电话, "ServiceTel": "-", # 站点服务电话 "StationType": 1, # 站点类型 "StationStatus": 50, # 站点状态 "ParkNums": 0, # 车位数量 0代表未知 "StationLng": "{:.6f}".format(float(lng)), # 精度(6位小数) "StationLat": "{:.6f}".format(float(lat)), # 维度(6位小数) "Construction": 255, # 建设场所 "ParkInfo": "-", "ParkName": "-", "OpenAllDay": 1, # 是否全天开放 "BusineHours": u"24小时全天服务", # 营业时间描述 "MinElectricityPric": 0.0, # 最低充电费率 浮点型 "ElectricityFee": "-", # 充电电费描述, "ServiceFee": "-", # 服务费率描述 "ParkFree": 0, # 是否停车免费 "ParkFee": "-", # 停车费率描述 "SupportOrder": 0, # 是否支持预约 "EquipmentInfos": EquipmentInfos, # 充电站信息, "ElectricityTax": 0.0, "ServiceTax": 0.0 } return data def notification_station_status_jn(self, devNo): data = self.get_connector_status_jn(devNo) url = self.join_url("notification_stationStatus") token = self.get_token() self.send_request(url, token = token, ConnectorStatusInfos = data) @staticmethod def get_connector_status_jn(devNo): """ 获取充电充电设备接口状态 """ ConnectorStatusInfos = list() device = Device.get_dev(devNo) parts = Part.objects.filter(logicalCode=device.logicalCode) devCache = Device.get_dev_control_cache(devNo) online = device.get("online", True) for part in parts: if device.get("devType", dict()).get("code") not in [ Const.DEVICE_TYPE_CODE_CAR_CHARGING_CY, Const.DEVICE_TYPE_CODE_CAR_CHARGING_CY_V2 ]: partNo = part.partNo if partNo in ['allPorts', 'usedPorts', 'usePorts']: continue portCache = devCache.get(part.partNo) or dict() else: portCache = devCache or dict() # 判断端口当前状态 if not online: status = 0 else: status = 3 if portCache.get("isStart") else 1 data = { "ConnectorID": str(part.id), "Status": status, "CurrentA": 0, "VoltageA": 0, "BeginTime": portCache.get("startTime", "-"), # 开始时间 "SOC": 0.0, # 剩余电量 "CurrentKwh": 0.0, # 已充电量 "CurrentMeter": 0.0, # 当前电表读数 "BmsReqVoltage": 0.0, # BMS需求电压 "BmsReqCurrent": 0.0 # BMS需求电流 } ConnectorStatusInfos.append(data) return ConnectorStatusInfos def alarm_report_jn(self, devNo, code, desc = None, status = None): """ 当充电接口发生异常告警或故障时,运营商企业平台主动推送信息到省级平台。 :param devNo: :param code: :param desc: :param status: :return: """ desc = desc or u"设备故障" status = status or 0 data = { "EquipmentID": devNo, "AlertTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "AlertCode": code, "Describe": desc, "Status": status } url = self.join_url("alarm_report") token = self.get_token() self.send_request(url, token = token, AlarmInfos = [data]) # ------------------------- 山东省平台 拉取站点信息 -------------------------------------- @staticmethod def get_station(groupId, norther): """ 获取站点信息 :param groupId: :param norther: :return: """ # 获取经纬度 获取设备数量 lng, lat, count = ShanDongNorther.get_coordinates_and_nums(groupId) lng, lat = ShanDongNorther.bd09_to_gcj02(lng, lat) # 获取设备信息 EquipmentInfos = list() devNos = Device.get_devNos_by_group([groupId]) for devNo in devNos: dev = Device.get_dev(devNo) # type: DeviceDict if dev.devTypeCode not in [ Const.DEVICE_TYPE_CODE_CAR_CHARGING_CY_V2, Const.DEVICE_TYPE_CODE_CAR_CHARGING_CY, Const.DEVICE_TYPE_CODE_CHARGING_HONGZHUO ]: continue EquipmentInfos.append(ShanDongNorther.get_equipment(Device.get_dev(devNo), norther)) # 充电站信息 group = Group.get_group(groupId) stationId = GroupIdMap.get_stationId(groupId) dealer = Dealer.get_dealer(group.get("ownerId")) data = { "StationID": stationId, # 充电站ID 20 "OperatorID": norther.agentOperatorID, # 组织机构代码 9 "EquipmentOwnerID": norther.equipOperatorID, # 设备所属方组织机构代码 9 "StationName": group.get("groupName"), # 充电站名称描述 50 "CountryCode": "CN", # 国家代码 固定 "AreaCode": GB2260.get_code(District.get_area(group.get("districtId"))), # 地区编码 20 "Address": group.get("address"), # 详细地址 50 "StationTel": '-', # 站点责任人电话, "ServiceTel": '-', # 站点服务电话 "StationType": 1, # 站点类型 "StationStatus": 50, # 站点状态 "ParkNums": count, # 车位数量 0代表未知 "StationLng": "{:.6f}".format(float(lng)), # 精度(6位小数) "StationLat": "{:.6f}".format(float(lat)), # 维度(6位小数) "Construction": 255, # 建设场所 "OpenAllDay": 1, # 是否全天开放 "BusineHours": u"24小时全天服务", # 营业时间描述 "MinElectricityPrice": None, # 最低充电费率 浮点型 "ElectricityFee": None, # 充电电费描述, "ServiceFee": None, # 服务费率描述 "ParkFree": None, # 是否停车免费 "ParkFee": None, # 停车费率描述 "SupportOrder": 0, # 是否支持预约 "EquipmentInfos": EquipmentInfos, # 充电站信息 } return data @staticmethod def get_equipment(dev, norther): # type:(DeviceDict, ShanDongNorther) -> dict agent = Agent.objects.get(id=dev.owner.agentId) ConnectorInfo = list() for part in dev.parts: ConnectorInfo.append(norther.get_connector(part, norther)) data = { "EquipmentID": dev.devNo, "ManufacturerName": agent.productName, "EquipmentModel": dev.get("devType", dict()).get("name", ""), "EquipmentName": dev.get("logicalCode"), "EquipmentType": 2, "EquipmentStatus": 50, "EquipmentPower": 7, "NewNationalStandard": 1, "ConnectorInfos": ConnectorInfo, } return data @staticmethod def get_connector(part, norther): # type:(Part, ShanDongNorther) -> dict """ 获取部件信息 """ return { "ConnectorID": str(part.id), "ConnectorName": part.partName, "ConnectorType": 2, "VoltageUpperLimits": 220, "VoltageLowerLimits": 220, "Current": part.attachParas.get("current", 16), "Power": part.attachParas.get("power", 7), "ParkNo": "-", "NationalStandard": 3, } # ------------------------- 山东省平台 获取站点统计信息 -------------------------------------- @staticmethod def get_station_state(groupId, startTime, endTime): """ 获取充电站的 一段时间内的统计信息 主要是电量 :param groupId: :param startTime: :param endTime :return: """ EquipmentStatsInfos = list() devNos = Device.get_devNos_by_group([groupId]) elec = float(0) for devNo in devNos: tempState = ShanDongNorther.get_equipment_state(devNo, startTime, endTime) elec += tempState.get("EquipmentElectricity", 0.0) EquipmentStatsInfos.append(tempState) return { "StationElectricity": elec, "EquipmentStatsInfos": EquipmentStatsInfos } @staticmethod def get_equipment_state(devNo, startTime, endTime): """ 获取充电设备的 一段时间内的统计信息 主要是电量 :param devNo: :param startTime: :param endTime: :return: """ device = Device.get_dev(devNo) ConnectorStatsInfo = list() elec = float(0) for part in device.parts: tempState = ShanDongNorther.get_part_state(devNo, startTime, endTime, part) elec += tempState.get("ConnectorElectricity", 0.0) ConnectorStatsInfo.append(tempState) return { "EquipmentID": str(devNo), "EquipmentElectricity": elec, "ConnectorStatsInfos": ConnectorStatsInfo } @staticmethod def get_part_state(devNo, startTime, endTime, part): """ 获取充电端口的 一段时间内的统计信息 主要是电量 :param devNo: :param part: :param startTime: :param endTime: :return: """ filters = { "devNo": devNo, "finishedTime__gte": startTime, "finishedTime__lte": endTime, } device = Device.get_dev(devNo) if device.get("devType", dict()).get("code") != Const.DEVICE_TYPE_CODE_CAR_CHARGING_CY: filters.update({"attachParas__chargeIndex": part.partNo}) records = ConsumeRecord.objects.filter(**filters).only("servicedInfo") elec = float(0) for item in records: elec += item.servicedInfo.get("elec", 0.0) return { "ConnectorID": str(part.id), "ConnectorElectricity": float("{:.1f}".format(float(elec))) } # ------------------------- 山东省平台 获取站点状态信息 -------------------------------------- @staticmethod def get_station_status(groupId): """ 获取充电站的当前状态 :param groupId: :return: """ ConnectorStatusInfos = list() devNos = Device.get_devNos_by_group([groupId]) for devNo in devNos: device = Device.get_dev(devNo) parts = Part.objects.filter(logicalCode = device.logicalCode) devCache = Device.get_dev_control_cache(devNo) online = device.get("online", True) for part in parts: if device.get("devType", dict()).get("code") not in [ Const.DEVICE_TYPE_CODE_CAR_CHARGING_CY, Const.DEVICE_TYPE_CODE_CAR_CHARGING_CY_V2 ]: portCache = devCache.get(part.partNo) or dict() else: portCache = devCache or dict() # 判断端口当前状态 if not online: status = 0 else: status = 3 if portCache.get("isStart") else 1 data = { "ConnectorID": str(part.id), "Status": status, "CurrentA": None, "VoltageA": None, "begin_time": portCache.get("startTime"), # 开始时间 "SOC": None, # 剩余电量 "current_kwh": None, # 已充电量 "current_meter": None, # 当前电表读数 "bms_req_voltage": None, # BMS需求电压 "bms_req_current": None # BMS需求电流 } ConnectorStatusInfos.append(data) return ConnectorStatusInfos # ------------------------- 山东省平台 获取站点计费信息 -------------------------------------- @staticmethod def get_policy_info(partId): """ 获取 端口的计费信息 :param partId: :return: """ DEFAULT_ELEC_PRICE = 1.500 ELEC_FUNCS = [ShanDongNorther.get_elec_price_by_package, ShanDongNorther.get_elec_price_by_conf, ShanDongNorther.get_elec_price_by_consume] part = Part.objects.filter(id = partId).first() if not part: return devNo = Device.get_devNo_by_logicalCode(part.logicalCode) for func in ELEC_FUNCS: try: elecPrice = func(devNo) except Exception: elecPrice = None if elecPrice: break else: elecPrice = DEFAULT_ELEC_PRICE return { "StartTime": "000000", "ElecPrice": elecPrice, "SevicePrice": None } @staticmethod def get_elec_price_by_package(devNo): """ 通过套餐获取电费 :param devNo: :return: """ device = Device.get_dev(devNo) package = device.get("washConfig", dict()).get("1", dict()) if not package: return price = package.get("price") time = package.get("time") unit = package.get("unit") if unit != u"度": return if not all([price, time]): return try: elecPrice = float("{:.4f}".format(float(price) / float(time))) except ZeroDivisionError: return return elecPrice @staticmethod def get_elec_price_by_conf(devNo): """ 通过设备设置设置电费 :param devNo: :return: """ device = Device.get_dev(devNo) elecPrice = device.get("otherConf", dict()).get("elecPrice") return float("{:.4f}".format(float(elecPrice))) @staticmethod def get_elec_price_by_consume(devNo): """ 通过 最近一次的消费记录获取电费 :param devNo: :return: """ record = ConsumeRecord.objects.filter(devNo = devNo).sort("-id").first() if not record or not record.servicedInfo: return elec = record.servicedInfo.get("elec") spend = record.servicedInfo.get("spend") if not all([elec, spend]): return try: elecPrice = float("{:.4f}".format(float(spend) / float(elec))) except ZeroDivisionError: return return elecPrice