swap_carcharger.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime,time
  4. import hashlib
  5. import hmac
  6. import json
  7. import logging
  8. import math
  9. import random
  10. import itsdangerous
  11. import requests
  12. from django.conf import settings
  13. from mongoengine import StringField, DateTimeField, IntField, QuerySet
  14. from bson import ObjectId
  15. from django.core.cache import cache
  16. from apps.web.agent.models import Agent
  17. from apps.web.api.utils import AES_CBC_PKCS5padding_encrypt, AES_CBC_PKCS5padding_decrypt
  18. from apps.web.common.models import District
  19. from apps.web.constant import Const
  20. from apps.web.core.db import Searchable
  21. from apps.web.dealer.models import Dealer
  22. from apps.web.device.models import Device, Group, Part, GroupDict, DeviceDict,SwapGroup,DeviceType
  23. from apps.web.user.models import ConsumeRecord,RechargeRecord
  24. from apps.web.south_intf.shangdong_platform import GB2260
  25. from apps.web.constant import DeviceOnlineStatus
  26. logger = logging.getLogger(__name__)
  27. #: 互联互通签约合同信息
  28. class SwapContract(Searchable):
  29. source = StringField(verbose_name = "签约渠道方,比如快电、新电途")
  30. swapLabel = StringField(verbose_name = "交换信息标记,比如微付乐和快电互通,就是wfl_kd",unique = True)
  31. operatorType = StringField(verbose_name = "签约运营商在我们系统中的类型, dealer/agent/manager", default = "manager")
  32. operatorInnerId = StringField(verbose_name = "运营商在我们系统中的的ID,比如manager ID")
  33. OperatorId = StringField(verbose_name = "运营商ID") #对于新营业执照(三码合一),其组织机构代码为社会信用代码去掉前八位和最后一位后的 中间的数字
  34. OperatorName = StringField(verbose_name = "机构全称")
  35. OperatorTell = StringField(verbose_name = "运营商客服电话1")
  36. OperatorTel2 = StringField(verbose_name = "运营商客服电话2")
  37. OperatorRegAddress = StringField(verbose_name = "运营商注册地址")
  38. OperatorNote = StringField(verbose_name = "备注信息")
  39. deviceChangedTime = DateTimeField(default = datetime.datetime.now, verbose_name = '设备变化的最新时间')
  40. brokerage = IntField(verbose_name = "佣金比例", default = 10)
  41. #消息通讯相关字段
  42. # 北向信息 即我们推送到平台所需要的信息时候需要用到的
  43. northToken = StringField(verbose_name=u"token", default="") # 推送信息的时候的身份验证
  44. northTokenExpiredTime = DateTimeField(verbose_name=u"token的过期时间", default=datetime.datetime.now) # 过期重新获取
  45. northPort = StringField(verbose_name=u"推送的IP地址", default="")
  46. tokenSecret = StringField(verbose_name=u"tokenSecret", default="98YNJUDJDIE838KLMNXYNXL")
  47. # 我方登录北向平台的账号信息:OperatorID + northOperatorSecret密钥
  48. secretFromUs = StringField(verbose_name=u"北向平台机构秘钥", default="") # 获取northToken时候使用,后续通讯需要token
  49. sigSecretFromUs = StringField(vebose_name=u"北向平台机构签名秘钥", default="") # 我方主动推送数据的时候, 加、解密 数据使用
  50. dataSecretFromUs = StringField(verbose_name=u"北向数据秘钥", default="") # 我方主动推送数据的时候,加、解密 数据使用
  51. dataSecretIVFromUs = StringField(verbose_name=u"北向数据秘钥向量", default="")# 我方主动推送数据的时候,加、解密 数据使用
  52. # 我们的信息 即其他平台拉取的时候需要用到的
  53. operatorId2Us = StringField(verbose_name=u"账号", default="") # 省平台登录我方服务器获取token时候使用(由我方提供)
  54. secret2Us = StringField(verbose_name=u"密码", default="") # 省平台登录我方服务器获取token时候使用(由我方提供)
  55. sigSecret2Us = StringField(vebose_name=u"平台机构秘钥", default="") # 省平台拉取我方服务器数据时候 加、解密数据使用
  56. dataSecret2Us = StringField(verbose_name=u"数据秘钥", default="") # 省平台拉取我方服务器数据时候 加、解密数据使用
  57. dataSecretIV2Us = StringField(verbose_name=u"数据秘钥", default="") # 省平台拉取我方服务器数据时候 加、解密数据使用
  58. statusMap = {
  59. str(Const.DEV_WORK_STATUS_IDLE):1,
  60. str(Const.DEV_WORK_STATUS_WORKING):3,
  61. str(Const.DEV_WORK_STATUS_FAULT):255,
  62. str(Const.DEV_WORK_STATUS_FORBIDDEN):255,
  63. str(Const.DEV_WORK_STATUS_PAUSE):2,
  64. str(Const.DEV_WORK_STATUS_CONNECTED):2,
  65. str(Const.DEV_WORK_STATUS_FINISHED):2,
  66. str(Const.DEV_WORK_STATUS_MAINTENANCE):2,
  67. str(Const.DEV_WORK_STATUS_APPOINTMENT):4,
  68. str(Const.DEV_WORK_STATUS_FAULT_OVERLOAD):255,
  69. str(Const.DEV_WORK_STATUS_OCCUPY):2,
  70. str(Const.DEV_WORK_STATUS_ESTOP):2,
  71. str(Const.DEV_WORK_STATUS_READY):2,
  72. str(Const.DEV_WORK_STATUS_FAULT_RELAY_CONNECT):255
  73. }
  74. @property
  75. def ipPort(self):
  76. return self.northPort
  77. def generate_json_token(self, data, expire=None):
  78. its = itsdangerous.TimedJSONWebSignatureSerializer(self.tokenSecret, expire)
  79. return its.dumps(data)
  80. def parse_json_token(self,s, expire=None):
  81. its = itsdangerous.TimedJSONWebSignatureSerializer(self.tokenSecret, expire)
  82. try:
  83. result = its.loads(s)
  84. except itsdangerous.BadData:
  85. return dict()
  86. return result
  87. def get_token_data(self):
  88. """
  89. 获取token的载数据 身份验证以平台为维度获取 那么token的范围也以 平台为准 即 northOperatorID
  90. """
  91. return {
  92. "id": str(self.id),
  93. }
  94. @classmethod
  95. def get_norther_by_label(cls,label):
  96. return cls.objects(swapLabel = label).first()
  97. @classmethod
  98. def get_norther(cls, **kwargs): # type:(dict) -> QuerySet
  99. """
  100. 获取norther的时候 有两个维度获取方式
  101. 第一种,我方主动推送的方式,由于每一个norther的 dealerId 唯一 所以一定能找到唯一的一个norther
  102. 第二种,我方被动回复 由于省平台拉取信息是以平台为单位,即AgentOperator 此时的norther不唯一了(有可能同一个平台下 有很多经销商都要对接)
  103. """
  104. filters = {}
  105. # 通过token的信息找 被动回复 不唯一
  106. kwargs.get("id") and filters.update({"id": ObjectId(kwargs["id"])})
  107. return cls.objects.filter(**filters)
  108. def get_sig(self, data, push=False):
  109. """
  110. 生成签名字符串 根据使用的场景,确认签名的盐
  111. :param data: 生成签名的数据 iter
  112. :param push: 推送还是拉取
  113. :return:
  114. """
  115. sigSecret = str(self.sigSecretFromUs) if push else str(self.sigSecret2Us)
  116. return hmac.new(sigSecret, data, hashlib.md5).hexdigest().upper()
  117. def send_request(self, url, **kwargs):
  118. """
  119. 主动发送HTTP请求获取数据 秘钥以及签名
  120. :param url:
  121. :param kwargs:
  122. :return:
  123. """
  124. headers = {"Content-Type": "application/json;charset=utf-8"}
  125. token = kwargs.pop("token", None)
  126. if token: headers.update({"Authorization": "Bearer {}".format(token)})
  127. timeout = kwargs.pop("timeout", 5)
  128. # 主动推送 加密以及向量为 dataSecret 和 dataSecretIV
  129. data = AES_CBC_PKCS5padding_encrypt(
  130. json.dumps(kwargs), dataSecret=self.dataSecretFromUs, dataSecretIV=self.dataSecretIVFromUs
  131. )
  132. data = {
  133. "OperatorID": self.OperatorID,
  134. "TimeStamp": datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
  135. "Seq": "{:0>4}".format(random.randint(1, 1)),
  136. "Data": data
  137. }
  138. sig = self.get_sig(data.get("OperatorID") + data.get("Data") + data.get("TimeStamp") + data.get("Seq"), push=True)
  139. data.update({"Sig": sig})
  140. try:
  141. response = requests.post(url = url, json = data, headers = headers, timeout = timeout)
  142. except requests.Timeout:
  143. return dict()
  144. except Exception as e:
  145. logger.exception(e)
  146. return dict()
  147. if response.status_code != 200:
  148. return dict()
  149. # 这个地方的解密 仅仅是为了解密数据 打印日志
  150. try:
  151. responseData = response.json().get("Data", "")
  152. if responseData:
  153. responseData = json.loads(
  154. AES_CBC_PKCS5padding_decrypt(responseData) or "{}"
  155. )
  156. logger.info("response result:{}".format(response.json()))
  157. logger.info("receive responseData:{}".format(responseData))
  158. except Exception,e:
  159. return dict()
  160. return response.json()
  161. def join_url(self, path):
  162. """
  163. 拼接url
  164. :param path:
  165. :return:
  166. """
  167. # 版本号的确定
  168. return "{ipPort}/{path}".format(
  169. ipPort=self.ipPort,
  170. path=path
  171. )
  172. def get_token(self):
  173. """
  174. 获取平台的token 更新token有效期
  175. :return:
  176. """
  177. if self.northToken and self.northTokenExpiredTime > datetime.datetime.now():
  178. return self.northToken
  179. url = self.join_url("query_token")
  180. data = {
  181. "OperatorID": self.OperatorID,
  182. "OperatorSecret": self.secretFromUs
  183. }
  184. result = self.send_request(url, **data)
  185. ret = result.get("Ret")
  186. if ret != 0:
  187. return
  188. responseJson = result.get("Data")
  189. responseData = json.loads(
  190. AES_CBC_PKCS5padding_decrypt(s=responseJson, dataSecret=self.dataSecretFromUs, dataSecretIV=self.dataSecretIVFromUs) or "{}"
  191. )
  192. # 防止解析出错
  193. tokenAvailableTime = responseData.get("TokenAvailableTime", 0)
  194. token = responseData.get("AccessToken", "")
  195. # 数据库更新
  196. self.update(
  197. northToken=token,
  198. northTokenExpiredTime=datetime.datetime.now()+datetime.timedelta(seconds = tokenAvailableTime)
  199. )
  200. return token
  201. def notification_station(self, groupId):
  202. """
  203. 充电站信息上报 GROUP
  204. :param groupId:
  205. :return:
  206. """
  207. data = self.get_station(groupId, self)
  208. url = self.join_url("notification_stationInfo")
  209. token = self.get_token()
  210. self.send_request(url = url, token = token, StationInfo = [data])
  211. def notification_order_info(self, consumeDict, stopReason = None):
  212. """
  213. 当运营商平台完成一次充电时,将订单信息推送至省级平台。
  214. :param consumeDict:
  215. :param stopReason:
  216. :return:
  217. """
  218. # 参数格式化
  219. _time = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
  220. totalPower = float("{:.2f}".format(float(consumeDict.get("totalPower"))))
  221. totalElecMoney = float("{:.2f}".format(float(consumeDict.get("totalElecMoney"))))
  222. seq = self.agentOperatorID + _time + str(random.randint(0, 9999))
  223. logger.info("Shan Dong Norther send order Info, seq is {}".format(seq))
  224. data = {
  225. "StartChargeSeq": seq,
  226. "ConnectorID": consumeDict.get("connectorId"),
  227. "StartTime": consumeDict.get("startTime"),
  228. "EndTime": consumeDict.get("endTime"),
  229. "TotalPower": totalPower,
  230. "TotalElecMoney": totalElecMoney,
  231. "TotalSeviceMoney": 0.00,
  232. "TotalMoney": totalElecMoney,
  233. "StopReason": stopReason or 1
  234. }
  235. logger.info('notification_order_info:{}'.format(data))
  236. url = self.join_url("notification_orderInfo")
  237. token = self.get_token()
  238. self.send_request(url, token = token, **data)
  239. def retry_push_notification_order_info(self, retry_push_dict):
  240. """
  241. 能源局出现问题需要手动推送时候找到日志然后执行
  242. 此函数只能手动执行
  243. """
  244. data = retry_push_dict.copy()
  245. url = self.join_url("notification_orderInfo")
  246. token = self.get_token()
  247. self.send_request(url, token = token, **data)
  248. logger.info('retry_push_notification_order_info is ok seq={}'.format(retry_push_dict['StartChargeSeq']))
  249. def alarm_report(self, devNo, code, desc = None, status = None):
  250. """
  251. 当充电接口发生异常告警或故障时,运营商企业平台主动推送信息到省级平台。
  252. :param devNo:
  253. :param code:
  254. :param desc:
  255. :param status:
  256. :return:
  257. """
  258. desc = desc or u"设备故障"
  259. status = status or 0
  260. data = {
  261. "equipmentID": devNo,
  262. "alert_time": str(datetime.datetime.now())[19:],
  263. "alert_code": code,
  264. "describe": desc,
  265. "status": status
  266. }
  267. logger.info('alarm_report:{}'.format(data))
  268. url = self.join_url("alarm_report")
  269. token = self.get_token()
  270. self.send_request(url, token = token, AlarmInfos = [data])
  271. def notification_stationStatus(self, connectorId,status):
  272. data = {
  273. "ConnectorID": str(connectorId),
  274. "Status": status,
  275. }
  276. url = self.join_url("notification_stationStatus")
  277. token = self.get_token()
  278. self.send_request(url, token = token, ConnectorStatusInfo = data)
  279. def get_station(self,swap):
  280. """
  281. 获取站点信息
  282. :param groupId:
  283. :param norther:
  284. :return:
  285. """
  286. # 获取设备信息
  287. EquipmentInfos = []
  288. devNos = Device.get_devNos_by_group([swap.groupId])
  289. for devNo in devNos:
  290. dev = Device.get_dev(devNo) # type: DeviceDict
  291. if (u'交流' not in dev.majorDeviceType) and (u'直流' not in dev.majorDeviceType) :
  292. continue
  293. devInfo = self.get_equipment(dev)
  294. if devInfo:
  295. EquipmentInfos.append(devInfo)
  296. # 充电站信息
  297. group = Group.get_group(swap.groupId)
  298. data = {
  299. "StationID": swap.StationID, # 充电站ID 20
  300. "OperatorID": self.OperatorID, # 组织机构代码 9
  301. "EquipmentOwnerID": self.OperatorID, # 设备所属方组织机构代码 9
  302. "StationName": group.get("groupName"), # 充电站名称描述 50
  303. "CountryCode": "CN", # 国家代码 固定
  304. "AreaCode": GB2260.get_code(District.get_area(group.get("districtId"))), # 地区编码 20
  305. "Address": group.get("address"), # 详细地址 50
  306. "StationTel": swap.StationTel, # 站点责任人电话,
  307. "ServiceTel": swap.ServiceTel, # 站点服务电话
  308. "StationType": swap.StationType, # 站点类型
  309. "StationStatus": swap.StationStatus, # 站点状态
  310. "ParkNums": swap.ParkNums, # 车位数量 0代表未知
  311. "StationLng": "{:.6f}".format(float(swap.gcjLng)), # 精度(6位小数)
  312. "StationLat": "{:.6f}".format(float(swap.gcjLat)), # 维度(6位小数)
  313. 'SiteGuide':swap.SiteGuide,
  314. "Construction": swap.Construction, # 建设场所
  315. 'Pictures':[pic['Url'] for pic in swap.Pictures],
  316. 'MatchCars':swap.MatchCars,
  317. 'Parkinfo':swap.ParkInfo,
  318. 'BusineHours':swap.BusineHours,
  319. 'ElectricityFee':swap.ElectricityFee,
  320. 'ServiceFee':swap.ServiceFee,
  321. "ParkFee": swap.ParkFee,
  322. "Payment": swap.Payment,
  323. "SupportOrder": 0, # 是否支持预约
  324. 'Remark':swap.Remark,
  325. "EquipmentInfos": EquipmentInfos, # 充电站信息
  326. }
  327. return data
  328. def get_equipment(self,dev): # type:(DeviceDict, SwapContract) -> dict
  329. ConnectorInfo = []
  330. devType = DeviceType.objects(id = dev['devType']['id']).first()
  331. if devType is None:
  332. return None
  333. for part in dev.parts:
  334. ConnectorInfo.append(self.get_connector(devType,part))
  335. EquipmentType = 1
  336. if u'交流' in devType.majorDeviceType:
  337. EquipmentType = 2
  338. elif u'交直流' in devType.majorDeviceType:
  339. EquipmentType = 3
  340. data = {
  341. "EquipmentID": dev.devNo,
  342. 'ManufacturerID':devType.extraInfo['ManufacturerID'],
  343. "ManufacturerName": devType.extraInfo['ManufacturerName'],
  344. "EquipmentModel": devType.extraInfo['ManufacturerName'],
  345. "EquipmentType": EquipmentType,
  346. "ConnectorInfos": ConnectorInfo,
  347. 'Power':devType.extraInfo['portPower'],
  348. "EquipmentName": dev.get("logicalCode"),
  349. }
  350. return data
  351. def get_connector(self,devType,part): # type:(Part, SwapContract) -> dict
  352. """
  353. 获取部件信息
  354. """
  355. return {
  356. "ConnectorID": str(part.id),
  357. "ConnectorName": part.partName,
  358. "ConnectorType": devType.extraInfo['ConnectorType'],
  359. "VoltageUpperLimits": devType.extraInfo['VoltageUpperLimits'],
  360. "VoltageLowerLimits": devType.extraInfo['VoltageLowerLimits'],
  361. "Current": devType.extraInfo['portCurrent'],
  362. "Power": devType.extraInfo['portPower'],
  363. "ParkNo": "-",
  364. "NationalStandard": 1,
  365. }
  366. @staticmethod
  367. def get_station_state(stationId, startTime, endTime):
  368. """
  369. 获取充电站的 一段时间内的统计信息 主要是电量
  370. :param groupId:
  371. :param startTime:
  372. :param endTime
  373. :return:
  374. """
  375. swap = SwapGroup.objects(StationID = stationId).first()
  376. if not swap:
  377. return {}
  378. EquipmentStatsInfos = []
  379. devNos = Device.get_devNos_by_group([swap.groupId])
  380. for devNo in devNos:
  381. dev = Device.get_dev(devNo) # type: DeviceDict
  382. if (u'交流' not in dev.majorDeviceType) and (u'直流' not in dev.majorDeviceType) :
  383. continue
  384. tempState = SwapContract.get_equipment_state(dev, startTime, endTime)
  385. EquipmentStatsInfos.append(tempState)
  386. return {
  387. "EquipmentStatsInfos": EquipmentStatsInfos
  388. }
  389. @staticmethod
  390. def get_equipment_state(dev, startTime, endTime):
  391. """
  392. 获取充电设备的 一段时间内的统计信息 主要是电量
  393. :param devNo:
  394. :param startTime:
  395. :param endTime:
  396. :return:
  397. """
  398. ConnectorStatsInfo = []
  399. devElec = 0
  400. filters = {
  401. "devNo": dev['devNo'],
  402. "finishedTime__gte": startTime,
  403. "finishedTime__lte": endTime,
  404. }
  405. records = ConsumeRecord.objects.filter(**filters).only("servicedInfo","attachParas")
  406. portElecDict = {}
  407. for item in records:
  408. elec = item.servicedInfo.get("elec", 0.0)
  409. devElec += elec
  410. portNo = str(item.servicedInfo['chargeIndex'])
  411. if portNo not in portElecDict:
  412. portElecDict[portNo] = 0.0
  413. else:
  414. portElecDict[portNo] += elec
  415. for part in dev.parts:
  416. elec = portElecDict.get(str(part.partNo),0.0)
  417. ConnectorStatsInfo.append({"ConnectorID": str(part.id),"ConnectorElectricity": float("{:.1f}".format(float(elec)))})
  418. return {
  419. "EquipmentID": dev['devNo'],
  420. "EquipmentElectricity": devElec,
  421. "ConnectorStatsInfos": ConnectorStatsInfo
  422. }
  423. @staticmethod
  424. def get_connector_status_infos(stationId):
  425. """
  426. 获取充电站的当前状态
  427. :param groupId:
  428. :return:
  429. """
  430. swap = SwapGroup.objects(StationID = stationId).first()
  431. if not swap:
  432. return []
  433. ConnectorStatusInfos = []
  434. devNos = Device.get_devNos_by_group([swap.groupId])
  435. for devNo in devNos:
  436. device = Device.get_dev(devNo)
  437. if (u'交流' not in device.majorDeviceType) and (u'直流' not in device.majorDeviceType) :
  438. continue
  439. parts = Part.objects.filter(logicalCode = device.logicalCode)
  440. online = device.get("online", True)
  441. for part in parts:
  442. # 判断端口当前状态
  443. if not online:
  444. status = 0
  445. else:
  446. status = SwapContract.statusMap.get(part.status,1)
  447. data = {
  448. "ConnectorID": str(part.id),
  449. "Status": status,
  450. }
  451. ConnectorStatusInfos.append(data)
  452. return ConnectorStatusInfos
  453. @staticmethod
  454. def get_policy_info(partId):
  455. """
  456. 获取 端口的计费信息
  457. :param partId:
  458. :return:
  459. """
  460. DEFAULT_ELEC_PRICE = 1.500
  461. ELEC_FUNCS = [SwapContract.get_elec_price_by_package, SwapContract.get_elec_price_by_conf,
  462. SwapContract.get_elec_price_by_consume]
  463. part = Part.objects.filter(id = partId).first()
  464. if not part:
  465. return
  466. devNo = Device.get_devNo_by_logicalCode(part.logicalCode)
  467. for func in ELEC_FUNCS:
  468. try:
  469. elecPrice = func(devNo)
  470. except Exception:
  471. elecPrice = None
  472. if elecPrice:
  473. break
  474. else:
  475. elecPrice = DEFAULT_ELEC_PRICE
  476. return {
  477. "StartTime": "000000",
  478. "ElecPrice": elecPrice,
  479. "SevicePrice": None
  480. }
  481. @staticmethod
  482. def get_elec_price_by_package(devNo):
  483. """
  484. 通过套餐获取电费
  485. :param devNo:
  486. :return:
  487. """
  488. device = Device.get_dev(devNo)
  489. package = device.get("washConfig", dict()).get("1", dict())
  490. if not package:
  491. return
  492. price = package.get("price")
  493. time = package.get("time")
  494. unit = package.get("unit")
  495. if unit != u"度":
  496. return
  497. if not all([price, time]):
  498. return
  499. try:
  500. elecPrice = float("{:.4f}".format(float(price) / float(time)))
  501. except ZeroDivisionError:
  502. return
  503. return elecPrice
  504. @staticmethod
  505. def get_elec_price_by_conf(devNo):
  506. """
  507. 通过设备设置设置电费
  508. :param devNo:
  509. :return:
  510. """
  511. device = Device.get_dev(devNo)
  512. elecPrice = device.get("otherConf", dict()).get("elecPrice")
  513. return float("{:.4f}".format(float(elecPrice)))
  514. @staticmethod
  515. def get_elec_price_by_consume(devNo):
  516. """
  517. 通过 最近一次的消费记录获取电费
  518. :param devNo:
  519. :return:
  520. """
  521. record = ConsumeRecord.objects.filter(devNo = devNo).sort("-id").first()
  522. if not record or not record.servicedInfo:
  523. return
  524. elec = record.servicedInfo.get("elec")
  525. spend = record.servicedInfo.get("spend")
  526. if not all([elec, spend]):
  527. return
  528. try:
  529. elecPrice = float("{:.4f}".format(float(spend) / float(elec)))
  530. except ZeroDivisionError:
  531. return
  532. return elecPrice
  533. @staticmethod
  534. def find_need_notify_northers(dev):
  535. ownerId = dev['ownerId']
  536. owner = Dealer.objects(id = ownerId).first()
  537. if owner is None:
  538. return []
  539. agent = Agent.objects(id = owner.agentId).first()
  540. if agent is None:
  541. return []
  542. northers = SwapContract.objects(operatorInnerId__in = [ownerId,owner.agentId,agent.managerId])
  543. return northers
  544. @staticmethod
  545. def notify_2_all_northers_port_status(dev,portNo,status):
  546. status = SwapContract.statusMap.get(status,1)
  547. northers = SwapContract.find_need_notify_northers(dev)
  548. part = Part.objects(logicalCode = dev['logicalCode'],partNo = str(portNo)).first()
  549. for norther in northers:
  550. logger.info('norther label=%s,port=%s,notification_stationStatus=%s' % (norther.swapLabel,portNo,status))
  551. norther.notification_stationStatus(str(part.id),status)
  552. @staticmethod
  553. def notify_2_all_northers_port_network_status(dev,networkStatus,workStatus=1):
  554. if networkStatus == DeviceOnlineStatus.DEV_STATUS_OFFLINE:
  555. status = 0# 离线
  556. else:
  557. status = SwapContract.statusMap.get(workStatus,1) # 如果是在线,应该以设备实际的工作状态为主
  558. northers = SwapContract.find_need_notify_northers(dev)
  559. for norther in northers:
  560. ports = Part.objects(logicalCode = dev['logicalCode'])
  561. for port in ports:
  562. logger.info('norther label=%s,port=%s,notification_stationStatus=%s' % (norther.swapLabel,port,status))
  563. norther.notification_stationStatus(port.id,status)
  564. @staticmethod
  565. def notify_2_all_northers_order_status(dev,orderStatus):
  566. northers = SwapContract.find_need_notify_northers(dev)
  567. for norther in northers:
  568. logger.info('norther label=%s,notification_stationStatus=%s' % (norther.swapLabel,orderStatus))
  569. url = norther.join_url("notification_equip_charge_status")
  570. token = norther.get_token()
  571. norther.send_request(url, token = token, **orderStatus)
  572. def notification_start_charge_result(self,data):
  573. data.update({'StartTime':datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
  574. url = self.join_url("notification_start_charge_result")
  575. token = self.get_token()
  576. self.send_request(url, token = token, **data)
  577. def notification_stop_charge_result(self,data):
  578. url = self.join_url("notification_stop_charge_result")
  579. token = self.get_token()
  580. self.send_request(url, token = token, **data)
  581. @staticmethod
  582. def notify_2_all_northers_order_info(dev,data):
  583. northers = SwapContract.find_need_notify_northers(dev)
  584. for norther in northers:
  585. url = norther.join_url("notification_charge_order_info")
  586. logger.info('norther label=%s,notification_charge_order_info=%s' % (norther.swapLabel,data))
  587. token = norther.get_token()
  588. norther.send_request(url, token = token, **data)
  589. def check_charge_orders(self,startTime,endTime):
  590. rechargeRcds = RechargeRecord.get_collection().find({'time':{'$gte':startTime,'$lte':endTime},'extraInfo.swapSource':self.label})
  591. OrderCount = 0
  592. TotalOrderPower = 0
  593. TotalOrderMoney = 0
  594. ChargeOrders = []
  595. for rcd in rechargeRcds:
  596. consumeRcd = ConsumeRecord.objects(orderNo = rcd['extraInfo']['consumeOrderNo']).first()
  597. if consumeRcd is None:
  598. continue
  599. ChargeOrders.append({
  600. 'StartChargeSeq':rcd.wxOrderNo,
  601. 'TotalPower':consumeRcd.servicedInfo.get('TotalPower',0),
  602. 'TotalMoney':consumeRcd.servicedInfo.get('TotalMoney',0)
  603. })
  604. TotalOrderPower += consumeRcd.servicedInfo.get('TotalPower',0)
  605. TotalOrderMoney += consumeRcd.servicedInfo.get('TotalMoney',0)
  606. OrderCount += 1
  607. result = {
  608. 'CheckOrderSeq':self.OperatorID + str(time.time()*1000) + str(random.randint(10000,99999)),
  609. 'StartTime':startTime,
  610. 'EndTime':endTime,
  611. 'OrderCount':OrderCount,
  612. 'TotalOrderPower':TotalOrderPower,
  613. 'TotalOrderMoney':TotalOrderMoney,
  614. 'ChargeOrders':ChargeOrders
  615. }
  616. url = self.join_url("notification_charge_order_infb")
  617. token = self.get_token()
  618. result = self.send_request(url, token = token, **result)
  619. ret = result.get("Ret")
  620. if ret != 0:
  621. return {}
  622. responseJson = result.get("Data")
  623. responseData = json.loads(
  624. AES_CBC_PKCS5padding_decrypt(s=responseJson, dataSecret=self.dataSecretFromUs, dataSecretIV=self.dataSecretIVFromUs) or "{}"
  625. )
  626. return responseData
  627. @staticmethod
  628. def update_swap_time_and_num(groupId,ownerId,agentId,managerId,devNum=0):
  629. northers = SwapContract.objects(operatorInnerId__in = [ownerId,str(agentId),str(managerId)])
  630. for norther in northers:
  631. norther.deviceChangedTime = datetime.datetime.now()
  632. try:
  633. norther.save()
  634. except Exception,e:
  635. continue