solax-py-library 1.0.0.2502__py3-none-any.whl → 1.0.0.2504__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. solax_py_library/device/constant/cabinet.py +2 -0
  2. solax_py_library/device/types/alarm.py +16 -0
  3. solax_py_library/smart_scene/constant/message_entry.py +174 -336
  4. solax_py_library/smart_scene/core/action/base.py +9 -0
  5. solax_py_library/smart_scene/core/action/ems_action.py +19 -0
  6. solax_py_library/smart_scene/core/action/system_action.py +241 -0
  7. solax_py_library/smart_scene/core/condition/base.py +8 -7
  8. solax_py_library/smart_scene/core/condition/cabinet_condition.py +11 -77
  9. solax_py_library/smart_scene/core/condition/date_condition.py +9 -9
  10. solax_py_library/smart_scene/core/condition/price_condition.py +27 -33
  11. solax_py_library/smart_scene/core/condition/system_condition.py +14 -21
  12. solax_py_library/smart_scene/core/condition/weather_condition.py +13 -22
  13. solax_py_library/smart_scene/core/service/__init__.py +0 -0
  14. solax_py_library/smart_scene/core/service/smart_scene_service.py +411 -0
  15. solax_py_library/smart_scene/types/action.py +3 -3
  16. solax_py_library/smart_scene/types/condition.py +32 -26
  17. solax_py_library/smart_scene/types/smart_scene_content.py +9 -9
  18. solax_py_library/test/test_utils/test_cloud_client.py +1 -1
  19. solax_py_library/utils/cloud_client.py +2 -2
  20. solax_py_library/utils/time_util.py +33 -0
  21. {solax_py_library-1.0.0.2502.dist-info → solax_py_library-1.0.0.2504.dist-info}/METADATA +1 -1
  22. {solax_py_library-1.0.0.2502.dist-info → solax_py_library-1.0.0.2504.dist-info}/RECORD +23 -16
  23. {solax_py_library-1.0.0.2502.dist-info → solax_py_library-1.0.0.2504.dist-info}/WHEEL +0 -0
@@ -0,0 +1,411 @@
1
+ import json
2
+ import os.path
3
+ import struct
4
+ import traceback
5
+ import threading
6
+ import subprocess as sp
7
+
8
+ import requests
9
+ from datetime import datetime, timedelta
10
+
11
+ from tornado.log import app_log
12
+
13
+ from domain.ems_enum.smart_scene.condition import ConditionFunc
14
+ from settings.const import (
15
+ RedisKey,
16
+ Role,
17
+ DeviceInfo,
18
+ PCSOperateFunctionName,
19
+ Enumeration,
20
+ FilePath,
21
+ )
22
+ from web.web_ext import config_json
23
+ from models.tables import Device
24
+ from utils.common import options, get_logger_sn, md5_encrypt, read_write_json_file
25
+ from services.redis_service import RedisService
26
+ from models.sqlite_db import session_maker
27
+ from utils.redis_utils import RedisProducer
28
+ from services.strategy_control_service import StrategyControlService
29
+ from solax_py_library.utils.cloud_client import CloudClient
30
+
31
+ redis_host = options.REDIS_HOST
32
+ redis_port = options.REDIS_PORT
33
+ db = options.REDIS_DB
34
+ LOCAL_URL = "http://127.0.0.1:8000/ems/cloud/"
35
+
36
+
37
+ class SmartSceneService(object):
38
+ def __init__(self):
39
+ self.ems_sn = get_logger_sn()
40
+ self.redis_service = RedisService()
41
+ self.redis_producer = RedisProducer().producer
42
+ self.strategy_control_service = StrategyControlService()
43
+
44
+ def request_local_redis(self, redis_topic, payload):
45
+ """本地redis分发任务"""
46
+ try:
47
+ self.redis_producer.publish(redis_topic, payload)
48
+ except Exception:
49
+ app_log.info(traceback.format_exc())
50
+
51
+ def request_local_server(self, apiName, apiPostData, scene_id=None):
52
+ """
53
+ 访问本地接口, 调用server8000
54
+ 需要使用admin权限
55
+ :param apiName: 接口名称
56
+ :param apiPostData: 请求参数
57
+ :param scene_id: 请求标识
58
+ :return:
59
+ """
60
+ username = Role.SUPER_ADMIN
61
+ user_info = {"username": username, "password": md5_encrypt(self.ems_sn)}
62
+ token = self.redis_service.make_token(username, user_info)
63
+ url = LOCAL_URL + apiName
64
+ headers = {"token": token}
65
+ try:
66
+ response = requests.post(url, json=apiPostData, headers=headers, timeout=5)
67
+ app_log.info(
68
+ f"scene_id: {scene_id}, request apiName: {apiName}, response: {response.text}"
69
+ )
70
+ return json.loads(response.text)
71
+ except Exception:
72
+ app_log.info(traceback.format_exc())
73
+ return False
74
+
75
+ def get_token(self):
76
+ token = self.redis_service.get_token()
77
+ if not token:
78
+ client = CloudClient(base_url=config_json.get("CLOUD_URL", ""))
79
+ token = client.get_token(
80
+ ems_sn=self.ems_sn, sn_secret=config_json.get("MQTT_PASSWORD")
81
+ )
82
+ if not token:
83
+ app_log.info("访问token接口失败")
84
+ return False
85
+ self.redis_service.set_token(token)
86
+ return token
87
+ else:
88
+ return token
89
+
90
+ def get_weather_data_from_redis(self):
91
+ try:
92
+ weather_info = self.redis_service.get_ele_price_info() or {}
93
+ return weather_info
94
+ except Exception:
95
+ app_log.error(traceback.format_exc())
96
+ return {}
97
+
98
+ def get_weather_data_from_cloud(self):
99
+ """获取未来24小时天气数据"""
100
+ try:
101
+ token = self.get_token()
102
+ client = CloudClient(base_url=config_json.get("CLOUD_URL", ""))
103
+ weather_info = client.get_weather_data_from_cloud(
104
+ ems_sn=self.ems_sn,
105
+ token=token,
106
+ )
107
+ if not weather_info:
108
+ app_log.error(f"获取天气数据失败 异常 {traceback.format_exc()}")
109
+ return False
110
+ app_log.info(f"获取天气数据成功: {weather_info}")
111
+ self.redis_service.set_weather_info(weather_info)
112
+ except Exception:
113
+ app_log.error(f"获取天气数据失败 异常 {traceback.format_exc()}")
114
+ return False
115
+
116
+ def trans_str_time_to_index(self, now_time, minute=15):
117
+ """将时间按照minute切换为索引,时间格式为 %H-%M"""
118
+ time_list = [int(i) for i in now_time.split(":")]
119
+ time_int = time_list[0] * 4 + time_list[1] // minute
120
+ return time_int
121
+
122
+ def get_electrovalence_data_from_cloud(self):
123
+ try:
124
+ token = self.get_token()
125
+ client = CloudClient(base_url=config_json.get("CLOUD_URL", ""))
126
+ ele_price_info = client.get_electrovalence_data_from_cloud(
127
+ ems_sn=self.ems_sn, token=token
128
+ )
129
+ app_log.info(f"获取电价数据成功: {ele_price_info}")
130
+ self.redis_service.set_ele_price_info(ele_price_info)
131
+ except Exception:
132
+ app_log.error(f"获取电价数据失败 异常 {traceback.format_exc()}")
133
+ return False
134
+
135
+ def get_electrovalence_data_from_redis(self):
136
+ """从自定义电价的缓存中获取"""
137
+ month_temp = self.redis_service.hget(
138
+ RedisKey.MONTH_TEMPLATE, RedisKey.MONTH_TEMPLATE
139
+ )
140
+ month_temp = json.loads(month_temp) if month_temp else {}
141
+ if month_temp == {}:
142
+ return {}
143
+ month = datetime.strftime(datetime.now(), "%m")
144
+ month_map = {
145
+ "01": "Jan",
146
+ "02": "Feb",
147
+ "03": "Mar",
148
+ "04": "Apr",
149
+ "05": "May",
150
+ "06": "Jun",
151
+ "07": "Jul",
152
+ "08": "Aug",
153
+ "09": "Sep",
154
+ "10": "Oct",
155
+ "11": "Nov",
156
+ "12": "Dec",
157
+ }
158
+ month = month_map.get(month, None)
159
+ if month is None:
160
+ return {}
161
+ template_id = str(month_temp.get(month, 0))
162
+ price_info = self.redis_service.hget(
163
+ RedisKey.ELECTRICITY_PRICE_TEMPLATE, template_id
164
+ )
165
+ price_info = json.loads(price_info) if price_info else {}
166
+ if price_info == {}:
167
+ return {}
168
+
169
+ stationInfo = self.redis_service.get(RedisKey.STATION_INFO)
170
+ stationInfo = json.loads(stationInfo) if stationInfo else {}
171
+ currencyCode = stationInfo.get("currencyCode")
172
+ currency_file = read_write_json_file(FilePath.CURRENCY_PATH)
173
+ ele_unit = False
174
+ try:
175
+ for j in currency_file["list"]:
176
+ if currencyCode == j["code"]:
177
+ ele_unit = j["unit"].split(":")[-1] + "/kWh"
178
+ break
179
+ except:
180
+ app_log.error(f"获取本地电价单位出错 {traceback.format_exc()}")
181
+ ele_price_info = {
182
+ "buy": [None] * 192,
183
+ "sell": [None] * 192,
184
+ "date": datetime.strftime(datetime.now(), "%Y-%m-%d"),
185
+ "ele_unit": ele_unit if ele_unit else "/kWh",
186
+ }
187
+ for period in price_info["periodConfiguration"]:
188
+ start_index = self.trans_str_time_to_index(period["startTime"])
189
+ end_index = self.trans_str_time_to_index(period["endTime"])
190
+ slotName = period["slotName"]
191
+ for price in price_info["priceAllocation"]:
192
+ if price["slotName"] == slotName:
193
+ ele_price_info["buy"][start_index:end_index] = [
194
+ price["buyPrice"]
195
+ ] * (end_index - start_index)
196
+ ele_price_info["sell"][start_index:end_index] = [
197
+ price["salePrice"]
198
+ ] * (end_index - start_index)
199
+ break
200
+ return ele_price_info
201
+
202
+ def get_electrovalence_data(self):
203
+ try:
204
+ online_status = self.redis_service.hget(RedisKey.LED_STATUS_KEY, "blue")
205
+ online_status = (
206
+ json.loads(online_status).get("status") if online_status else 0
207
+ )
208
+ if online_status:
209
+ ele_price_info = self.redis_service.get_ele_price_info() or {}
210
+ today = datetime.strftime(datetime.now(), "%Y-%m-%d")
211
+ date_now = ele_price_info.get("date", "")
212
+ if today not in date_now:
213
+ ele_price_info = {}
214
+ else:
215
+ app_log.info("设备离线,获取本地电价")
216
+ ele_price_info = self.get_electrovalence_data_from_redis()
217
+ return ele_price_info
218
+ except Exception:
219
+ app_log.error(traceback.format_exc())
220
+ return {}
221
+
222
+ def get_highest_or_lowest_price(
223
+ self, start_time, end_time, hours, price_list, func="expensive_hours"
224
+ ):
225
+ """获取一段时间内,电价最高或最低的几个小时"""
226
+ start_index = self.trans_str_time_to_index(start_time)
227
+ end_index = self.trans_str_time_to_index(end_time)
228
+ arr = price_list[start_index:end_index]
229
+ if None in arr:
230
+ return False
231
+ indices = list(range(end_index - start_index))
232
+ if func == "expensive_hours":
233
+ reverse = True
234
+ else:
235
+ reverse = False
236
+ sorted_indices = sorted(indices, key=lambda i: arr[i], reverse=reverse)
237
+ return sorted_indices[: int(hours * 4)], start_index
238
+
239
+ def get_rounded_times(self):
240
+ """
241
+ 返回距离当前时间最近的15min的整点时间以及后一整点5min时间(天气是预测未来15min的,也就是在00:00时,只能拿到00:15的数据)
242
+ """
243
+ now = datetime.now()
244
+ # 确定当前时间所属的15分钟区间
245
+ index_1 = now.minute // 15
246
+ index_2 = now.minute % 15
247
+ left_time = now.replace(minute=15 * index_1, second=0, microsecond=0)
248
+ right_time = left_time + timedelta(minutes=15)
249
+ if index_2 < 8:
250
+ nearest_time = left_time
251
+ else:
252
+ nearest_time = right_time
253
+ return datetime.strftime(nearest_time, "%Y-%m-%d %H:%M:%S"), datetime.strftime(
254
+ right_time, "%Y-%m-%d %H:%M:%S"
255
+ )
256
+
257
+ def compare_the_magnitudes(self, function, compare_value, base_value):
258
+ """比较两个值"""
259
+ if function == ConditionFunc.GT:
260
+ return compare_value > base_value
261
+ elif function == ConditionFunc.EQ:
262
+ return compare_value == base_value
263
+ elif function == ConditionFunc.LT:
264
+ return compare_value < base_value
265
+ return False
266
+
267
+ def get_cabinet_type(self):
268
+ # 获取机柜类型
269
+ cabinet_type = 1
270
+ with session_maker(change=False) as session:
271
+ result = (
272
+ session.query(Device.deviceModel)
273
+ .filter(Device.deviceType == DeviceInfo.ESS_TYPE, Device.isDelete == 0)
274
+ .first()
275
+ )
276
+
277
+ if result:
278
+ cabinet_type = result[0]
279
+ if cabinet_type in [3, 4, 7, 8]:
280
+ key_name = "AELIO"
281
+ else:
282
+ key_name = "TRENE"
283
+ return key_name
284
+
285
+ def set_manual_mode(self, work_mode, power, soc, cabinet_type):
286
+ """设置手动模式"""
287
+ # 充电
288
+ if work_mode == 3:
289
+ strategy_info = {
290
+ "chargePower": power,
291
+ "chargeTargetSoc": soc,
292
+ "runState": 1,
293
+ }
294
+ self.redis_service.hset(
295
+ RedisKey.MANUAL_STRATEGY_INFO,
296
+ RedisKey.CHARGE_MODE,
297
+ json.dumps(strategy_info),
298
+ )
299
+ self.strategy_control_service.apply_or_stop_strategy(
300
+ 0, RedisKey.MANUAL_STRATEGY_INFO, RedisKey.DISCHARGE_MODE
301
+ )
302
+ # 将运行模式修改为手动
303
+ self.redis_service.set(RedisKey.RUN_STRATEGY_TYPE, 1)
304
+ elif work_mode == 4:
305
+ strategy_info = {
306
+ "dischargePower": power,
307
+ "dischargeTargetSoc": soc,
308
+ "runState": 1,
309
+ }
310
+ self.redis_service.hset(
311
+ RedisKey.MANUAL_STRATEGY_INFO,
312
+ RedisKey.DISCHARGE_MODE,
313
+ json.dumps(strategy_info),
314
+ )
315
+ # 将充电状态修改
316
+ self.strategy_control_service.apply_or_stop_strategy(
317
+ 0, RedisKey.MANUAL_STRATEGY_INFO, RedisKey.CHARGE_MODE
318
+ )
319
+ # 将运行模式修改为手动
320
+ self.redis_service.set(RedisKey.RUN_STRATEGY_TYPE, 1)
321
+ # 停止
322
+ else:
323
+ self.strategy_control_service.apply_or_stop_strategy(
324
+ 0, RedisKey.MANUAL_STRATEGY_INFO, RedisKey.DISCHARGE_MODE
325
+ )
326
+ self.strategy_control_service.apply_or_stop_strategy(
327
+ 0, RedisKey.MANUAL_STRATEGY_INFO, RedisKey.CHARGE_MODE
328
+ )
329
+ self.redis_service.set(RedisKey.RUN_STRATEGY_TYPE, 1)
330
+ if work_mode == 3:
331
+ manualType = 1
332
+ elif work_mode == 4:
333
+ manualType = 2
334
+ else:
335
+ manualType = 3
336
+ data = {
337
+ "power": power,
338
+ "soc": soc,
339
+ "workMode": work_mode,
340
+ "manualType": manualType,
341
+ "useMode": 3,
342
+ }
343
+ if cabinet_type in ["TRENE"]:
344
+ t = threading.Thread(
345
+ target=self.request_local_redis, args=(options.REDIS_POWER_CONTROL, "")
346
+ )
347
+ else:
348
+ func_name = PCSOperateFunctionName.SET_AELIO_USE_MODE
349
+ channel = options.REDIS_WRITE_SERIAL_DEVICE
350
+ future_data = {}
351
+ future_data["func_name"] = func_name
352
+ future_data["operationMode"] = Enumeration.SINGLE_DEVICE_MODE
353
+ future_data["data"] = data
354
+ t = threading.Thread(
355
+ target=self.request_local_redis,
356
+ args=(channel, json.dumps(future_data)),
357
+ daemon=True,
358
+ )
359
+ t.start()
360
+ return True
361
+
362
+ def struct_transform(self, value, fmt, order="big"):
363
+ """将10进制的原始值转换为modbus协议需要的精度与类型的值"""
364
+ value = int(value)
365
+ if order == "little":
366
+ opt = "<"
367
+ else:
368
+ opt = ">"
369
+ try:
370
+ if fmt == "int16":
371
+ ret = struct.pack(f"{opt}h", value)
372
+ ret_list = struct.unpack(f"{opt}H", ret)
373
+ elif fmt == "uint16":
374
+ ret = struct.pack(f"{opt}H", value)
375
+ ret_list = struct.unpack(f"{opt}H", ret)
376
+ elif fmt == "int32":
377
+ ret = struct.pack(f"{opt}i", value)
378
+ ret_list = struct.unpack(f"{opt}HH", ret)
379
+ elif fmt == "uint32":
380
+ ret = struct.pack(f"{opt}I", value)
381
+ ret_list = struct.unpack(f"{opt}HH", ret)
382
+ elif fmt == "int64":
383
+ ret = struct.pack(f"{opt}q", value)
384
+ ret_list = struct.unpack(f"{opt}HHHH", ret)
385
+ elif fmt == "uint64":
386
+ ret = struct.pack(f"{opt}Q", value)
387
+ ret_list = struct.unpack(f"{opt}HHHH", ret)
388
+ else:
389
+ ret_list = [0]
390
+ except Exception:
391
+ if "16" in fmt:
392
+ ret_list = [0]
393
+ elif "32" in fmt:
394
+ ret_list = [0, 0]
395
+ else:
396
+ ret_list = [0, 0, 0, 0]
397
+ return list(ret_list)
398
+
399
+ def ems_do_control(self, data):
400
+ for do_info in data:
401
+ do_number = do_info.DoNumber
402
+ do_value = do_info.DoValue
403
+ if 1 <= do_number <= 8 and do_value in [0, 1]:
404
+ path = DeviceInfo.DI_DO_GPIO_MAPPING[f"DO{do_number}"] + "/value"
405
+ if not os.path.exists(path):
406
+ app_log.info(f"DO path not exists {do_info}")
407
+ else:
408
+ cmd = f"echo {do_value} > {path}"
409
+ ret = sp.getstatusoutput(cmd)
410
+ ret = True if ret[0] == 0 else False
411
+ app_log.info(f"DO {do_info} 控制结果: {ret}")
@@ -3,8 +3,8 @@ from typing import List, Union
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
6
- from domain.ems_enum.smart_scene.base import MESSAGE_ENTRY
7
- from settings.const import DeviceInfo
6
+ from solax_py_library.device.constant.cabinet import TRENE_CABINET_ENUM
7
+ from solax_py_library.smart_scene.constant.message_entry import MESSAGE_ENTRY
8
8
 
9
9
 
10
10
  class ActionType(str, Enum):
@@ -59,7 +59,7 @@ class SystemActionItemData(BaseModel):
59
59
  if self.childData.data[0] == 0:
60
60
  return MESSAGE_ENTRY["importControlOff"][lang]
61
61
  else:
62
- if cabinet_type in DeviceInfo.TRENE_CABINET_ENUM:
62
+ if cabinet_type in TRENE_CABINET_ENUM:
63
63
  msg = (
64
64
  "importControl_standby"
65
65
  if self.childData.data[1] == 0
@@ -1,8 +1,10 @@
1
+ import operator
1
2
  from enum import IntEnum, Enum
2
3
  from typing import Optional, List, Union, Any
3
4
 
4
5
  from pydantic import BaseModel, validator, root_validator
5
6
 
7
+ from solax_py_library.device.types.alarm import AlarmLevel
6
8
  from solax_py_library.smart_scene.constant.message_entry import MESSAGE_ENTRY
7
9
 
8
10
 
@@ -16,6 +18,13 @@ class ConditionFunc(IntEnum):
16
18
  LT = 101
17
19
  EQ = 102
18
20
 
21
+ def function(self):
22
+ return {
23
+ ConditionFunc.GT: operator.gt,
24
+ ConditionFunc.LT: operator.lt,
25
+ ConditionFunc.EQ: operator.eq,
26
+ }.get(self)
27
+
19
28
 
20
29
  class RepeatFunc(IntEnum):
21
30
  ONCE = 103
@@ -79,13 +88,13 @@ class PriceConditionItemData(BaseModel):
79
88
 
80
89
  @validator("childData", always=True)
81
90
  def _check_child_data(cls, value, values):
82
- childType = values.get("childType")
83
- if childType in {
91
+ child_type = values.get("childType")
92
+ if child_type in {
84
93
  PriceConditionType.lowerPrice,
85
94
  PriceConditionType.higherPrice,
86
95
  }:
87
96
  assert value.data[0] > 0, ValueError
88
- elif childType in {
97
+ elif child_type in {
89
98
  PriceConditionType.expensiveHours,
90
99
  PriceConditionType.cheapestHours,
91
100
  }:
@@ -119,14 +128,14 @@ class SystemConditionItemData(BaseModel):
119
128
 
120
129
  @validator("childData", always=True)
121
130
  def _check_child_data(cls, value, values):
122
- childType = values.get("childType")
123
- if childType in {
131
+ child_type = values.get("childType")
132
+ if child_type in {
124
133
  SystemConditionType.systemExportPower,
125
134
  SystemConditionType.systemImportPower,
126
135
  }:
127
136
  assert 0 <= value.data[0] <= 100000, ValueError
128
137
  value.data[0] = round(value.data[0], 2) # 功率保留两位小数
129
- elif childType == SystemConditionType.systemSoc:
138
+ elif child_type == SystemConditionType.systemSoc:
130
139
  assert 5 <= value.data[0] <= 100, ValueError
131
140
  return value
132
141
 
@@ -152,14 +161,14 @@ class CabinetConditionItemData(BaseModel):
152
161
 
153
162
  @validator("childData", always=True)
154
163
  def _check_child_data(cls, value, values):
155
- childType = values.get("childType")
156
- # if childType == CabinetConditionType.cabinetAlarm:
157
- # assert value.data[0] in {
158
- # AlarmLevel.TIPS,
159
- # AlarmLevel.NORMAL,
160
- # AlarmLevel.EMERGENCY,
161
- # }, ValueError
162
- if childType == CabinetConditionType.cabinetSoc:
164
+ child_type = values.get("childType")
165
+ if child_type == CabinetConditionType.cabinetAlarm:
166
+ assert value.data[0] in {
167
+ AlarmLevel.TIPS,
168
+ AlarmLevel.NORMAL,
169
+ AlarmLevel.EMERGENCY,
170
+ }, ValueError
171
+ if child_type == CabinetConditionType.cabinetSoc:
163
172
  assert 0 <= value.data[0] <= 100, ValueError
164
173
  return value
165
174
 
@@ -182,11 +191,11 @@ class DateConditionItemData(BaseModel):
182
191
 
183
192
  @validator("childData", always=True)
184
193
  def check_param(cls, value, values):
185
- childType = values.get("childType")
194
+ child_type = values.get("childType")
186
195
  data = value.data
187
- if childType == DateConditionType.time:
196
+ if child_type == DateConditionType.time:
188
197
  assert isinstance(data[0], str), ValueError
189
- elif childType == DateConditionType.duration:
198
+ elif child_type == DateConditionType.duration:
190
199
  assert isinstance(data[0], int), ValueError
191
200
  return value
192
201
 
@@ -203,8 +212,8 @@ class WeatherConditionItemData(BaseModel):
203
212
 
204
213
  @validator("childData", always=True)
205
214
  def _check_child_data(cls, value, values):
206
- childType = values.get("childType")
207
- if childType == WeatherConditionType.irradiance:
215
+ child_type = values.get("childType")
216
+ if child_type == WeatherConditionType.irradiance:
208
217
  assert value.data[0] > 0, ValueError
209
218
  assert 0 <= value.data[1] <= 24, ValueError
210
219
  return value
@@ -247,7 +256,9 @@ class ConditionItem(BaseModel):
247
256
  return {self.type: [d.to_text(lang, unit) for d in self.data]}
248
257
  elif self.type == ConditionType.cabinet:
249
258
  cabinet_sns = ",".join(self.cabinet)
250
- return {self.type: [d.to_text(lang, unit) for d in self.data] + [cabinet_sns]}
259
+ return {
260
+ self.type: [d.to_text(lang, unit) for d in self.data] + [cabinet_sns]
261
+ }
251
262
 
252
263
 
253
264
  class SmartSceneCondition(BaseModel):
@@ -273,13 +284,8 @@ class SmartSceneCondition(BaseModel):
273
284
  return values
274
285
 
275
286
  def to_text(self, lang, unit):
276
- # ret = {
277
- # "operation": MESSAGE_ENTRY[self.operation.name][lang],
278
- # "value": {}
279
- # }
280
- ret = {}
287
+ ret = {"operation": [MESSAGE_ENTRY[self.operation.name][lang]]}
281
288
  for v in self.value:
282
- # ret["value"].update(v.to_text(lang, unit))
283
289
  ret.update(v.to_text(lang, unit))
284
290
  return ret
285
291
 
@@ -5,21 +5,21 @@ from typing import Optional, List
5
5
  from pydantic import Field
6
6
  from pydantic.main import BaseModel
7
7
 
8
- from domain.ems_enum.smart_scene.action import (
8
+ from solax_py_library.device.constant.cabinet import TRENE_CABINET_ENUM
9
+ from solax_py_library.smart_scene.constant.message_entry import MESSAGE_ENTRY
10
+ from solax_py_library.smart_scene.types.action import (
11
+ SmartSceneAction,
9
12
  ActionType,
10
13
  SystemActionType,
11
- SmartSceneAction,
12
14
  )
13
- from domain.ems_enum.smart_scene.base import MESSAGE_ENTRY
14
- from domain.ems_enum.smart_scene.condition import (
15
- LogicFunc,
16
- ConditionFunc,
15
+ from solax_py_library.smart_scene.types.condition import (
17
16
  RepeatFunc,
17
+ SmartSceneCondition,
18
+ LogicFunc,
18
19
  ConditionType,
19
20
  PriceConditionType,
20
- SmartSceneCondition,
21
+ ConditionFunc,
21
22
  )
22
- from settings.const import DeviceInfo
23
23
 
24
24
 
25
25
  class SmartSceneOtherInfo(BaseModel):
@@ -145,7 +145,7 @@ class SmartSceneContent(BaseModel):
145
145
  ],
146
146
  "elseThen": [],
147
147
  }
148
- if cabinet_type in DeviceInfo.TRENE_CABINET_ENUM:
148
+ if cabinet_type in TRENE_CABINET_ENUM:
149
149
  return [scene_rec_2]
150
150
  else:
151
151
  return [scene_rec_1, scene_rec_2]
@@ -9,6 +9,6 @@ class TestCloudClient(TestCase):
9
9
  ret = client.get_weather_data_from_cloud(
10
10
  "https://aliyun-sit.solaxtech.net:5050",
11
11
  "XMG11A011L",
12
- "b080a22827484db6bd509d496f9df90b"
12
+ "b080a22827484db6bd509d496f9df90b",
13
13
  )
14
14
  print(ret)
@@ -20,7 +20,7 @@ class CloudClient:
20
20
  "registrationSn": ems_sn,
21
21
  "snSecret": sn_secret,
22
22
  },
23
- timeout=5
23
+ timeout=5,
24
24
  )
25
25
  if response.content:
26
26
  response_data = json.loads(response.content)
@@ -144,7 +144,7 @@ class CloudClient:
144
144
  url=price_url,
145
145
  headers={"token": token, "Content-Type": "application/json"},
146
146
  json={"registerNo": ems_sn},
147
- timeout=5
147
+ timeout=5,
148
148
  )
149
149
  # 访问失败或获取数据失败,则重复插入最后一条数据
150
150
  if response.status_code != 200:
@@ -1,5 +1,38 @@
1
+ from datetime import datetime, timedelta
2
+
3
+
1
4
  def trans_str_time_to_index(now_time, minute=15):
2
5
  """将时间按照minute切换为索引,时间格式为 %H-%M"""
3
6
  time_list = [int(i) for i in now_time.split(":")]
4
7
  time_int = time_list[0] * 4 + time_list[1] // minute
5
8
  return time_int
9
+
10
+
11
+ def get_highest_or_lowest_value(start_time, end_time, hours, price_list, reverse=False):
12
+ start_index = trans_str_time_to_index(start_time)
13
+ end_index = trans_str_time_to_index(end_time)
14
+ arr = price_list[start_index:end_index]
15
+ if None in arr:
16
+ return False
17
+ indices = list(range(end_index - start_index))
18
+ sorted_indices = sorted(indices, key=lambda i: arr[i], reverse=reverse)
19
+ return sorted_indices[: int(hours * 4)], start_index
20
+
21
+
22
+ def get_rounded_times():
23
+ """
24
+ 返回距离当前时间最近的15min的整点时间以及后一整点5min时间(天气是预测未来15min的,也就是在00:00时,只能拿到00:15的数据)
25
+ """
26
+ now = datetime.now()
27
+ # 确定当前时间所属的15分钟区间
28
+ index_1 = now.minute // 15
29
+ index_2 = now.minute % 15
30
+ left_time = now.replace(minute=15 * index_1, second=0, microsecond=0)
31
+ right_time = left_time + timedelta(minutes=15)
32
+ if index_2 < 8:
33
+ nearest_time = left_time
34
+ else:
35
+ nearest_time = right_time
36
+ return datetime.strftime(nearest_time, "%Y-%m-%d %H:%M:%S"), datetime.strftime(
37
+ right_time, "%Y-%m-%d %H:%M:%S"
38
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: solax-py-library
3
- Version: 1.0.0.2502
3
+ Version: 1.0.0.2504
4
4
  Summary: some common tool
5
5
  Author: shenlvyu
6
6
  Author-email: 13296718439@163.com