123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904 |
- # -*- 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
|