solax-py-library 1.0.0.23__py3-none-any.whl → 1.0.0.26__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 (60) hide show
  1. solax_py_library/__init__.py +1 -1
  2. solax_py_library/device/constant/cabinet.py +2 -0
  3. solax_py_library/device/core/interver/__init__.py +36 -36
  4. solax_py_library/device/core/interver/base.py +215 -215
  5. solax_py_library/device/types/alarm.py +16 -0
  6. solax_py_library/device/types/inverter_config.py +41 -41
  7. solax_py_library/device/types/modbus_point.py +30 -30
  8. solax_py_library/exception.py +10 -10
  9. solax_py_library/smart_scene/__init__.py +0 -0
  10. solax_py_library/smart_scene/constant/__init__.py +0 -0
  11. solax_py_library/smart_scene/constant/message_entry.py +179 -0
  12. solax_py_library/smart_scene/core/__init__.py +0 -0
  13. solax_py_library/smart_scene/core/action/__init__.py +0 -0
  14. solax_py_library/smart_scene/core/action/base.py +10 -0
  15. solax_py_library/smart_scene/core/action/ems_action.py +6 -0
  16. solax_py_library/smart_scene/core/action/system_action.py +6 -0
  17. solax_py_library/smart_scene/core/condition/__init__.py +17 -0
  18. solax_py_library/smart_scene/core/condition/base.py +17 -0
  19. solax_py_library/smart_scene/core/condition/cabinet_condition.py +44 -0
  20. solax_py_library/smart_scene/core/condition/date_condition.py +23 -0
  21. solax_py_library/smart_scene/core/condition/price_condition.py +110 -0
  22. solax_py_library/smart_scene/core/condition/system_condition.py +35 -0
  23. solax_py_library/smart_scene/core/condition/weather_condition.py +61 -0
  24. solax_py_library/smart_scene/core/service/__init__.py +3 -0
  25. solax_py_library/smart_scene/core/service/runner.py +156 -0
  26. solax_py_library/smart_scene/exceptions/__init__.py +7 -0
  27. solax_py_library/smart_scene/exceptions/price.py +5 -0
  28. solax_py_library/smart_scene/exceptions/smart_scene.py +82 -0
  29. solax_py_library/smart_scene/exceptions/weather.py +5 -0
  30. solax_py_library/smart_scene/types/__init__.py +0 -0
  31. solax_py_library/smart_scene/types/action.py +164 -0
  32. solax_py_library/smart_scene/types/condition.py +299 -0
  33. solax_py_library/smart_scene/types/smart_scene_content.py +173 -0
  34. solax_py_library/snap_shot/__init__.py +3 -3
  35. solax_py_library/snap_shot/constant/__init__.py +5 -5
  36. solax_py_library/snap_shot/constant/crc_table.py +258 -258
  37. solax_py_library/snap_shot/core/base_modbus.py +14 -14
  38. solax_py_library/snap_shot/core/parser.py +261 -225
  39. solax_py_library/snap_shot/core/snap_shot.py +25 -7
  40. solax_py_library/snap_shot/exceptions/__init__.py +3 -3
  41. solax_py_library/snap_shot/exceptions/snap_shot.py +9 -9
  42. solax_py_library/snap_shot/types/__init__.py +15 -15
  43. solax_py_library/snap_shot/types/address.py +39 -39
  44. solax_py_library/test/__init__.py +0 -0
  45. solax_py_library/test/test_smart_scene/__init__.py +0 -0
  46. solax_py_library/test/test_smart_scene/test_condition.py +11 -0
  47. solax_py_library/test/test_utils/__init__.py +0 -0
  48. solax_py_library/test/test_utils/test_cloud_client.py +14 -0
  49. solax_py_library/upload/__init__.py +3 -3
  50. solax_py_library/upload/api/service.py +24 -24
  51. solax_py_library/upload/core/upload_service/ftp.py +1 -0
  52. solax_py_library/upload/exceptions/__init__.py +8 -8
  53. solax_py_library/upload/exceptions/upload_error.py +21 -21
  54. solax_py_library/utils/cloud_client.py +210 -0
  55. solax_py_library/utils/struct_util.py +42 -30
  56. solax_py_library/utils/time_util.py +38 -0
  57. {solax_py_library-1.0.0.23.dist-info → solax_py_library-1.0.0.26.dist-info}/METADATA +2 -1
  58. solax_py_library-1.0.0.26.dist-info/RECORD +80 -0
  59. solax_py_library-1.0.0.23.dist-info/RECORD +0 -46
  60. {solax_py_library-1.0.0.23.dist-info → solax_py_library-1.0.0.26.dist-info}/WHEEL +0 -0
@@ -0,0 +1,299 @@
1
+ import operator
2
+ from enum import IntEnum, Enum
3
+ from typing import Optional, List, Union, Any
4
+
5
+ from pydantic import BaseModel, validator, root_validator
6
+
7
+ from solax_py_library.device.types.alarm import AlarmLevel
8
+ from solax_py_library.smart_scene.constant.message_entry import MESSAGE_ENTRY
9
+
10
+
11
+ class LogicFunc(IntEnum):
12
+ OR = 0
13
+ AND = 1
14
+
15
+
16
+ class ConditionFunc(IntEnum):
17
+ GT = 100
18
+ LT = 101
19
+ EQ = 102
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
+
28
+
29
+ class RepeatFunc(IntEnum):
30
+ ONCE = 103
31
+ EVERYDAY = 104
32
+ WEEKDAY = 105
33
+ WEEKEND = 106
34
+ CUSTOM = 107
35
+
36
+
37
+ class ConditionType(str, Enum):
38
+ date = "date"
39
+ weather = "weather"
40
+ buyingPrice = "buyingPrice"
41
+ sellingPrice = "sellingPrice"
42
+ systemCondition = "systemCondition"
43
+ cabinet = "cabinet"
44
+
45
+
46
+ class WeatherConditionType(str, Enum):
47
+ irradiance = "irradiance"
48
+ temperature = "temperature"
49
+
50
+
51
+ class PriceConditionType(str, Enum):
52
+ price = "price"
53
+ lowerPrice = "lowerPrice"
54
+ higherPrice = "higherPrice"
55
+ expensiveHours = "expensiveHours"
56
+ cheapestHours = "cheapestHours"
57
+
58
+
59
+ class DateConditionType(str, Enum):
60
+ time = "time"
61
+ duration = "duration"
62
+
63
+
64
+ class SystemConditionType(str, Enum):
65
+ systemSoc = "systemSoc"
66
+ systemImportPower = "systemImportPower" # 买电功率
67
+ systemExportPower = "systemExportPower" # 馈电功率
68
+
69
+
70
+ class CabinetConditionType(str, Enum):
71
+ cabinetAlarm = "cabinetAlarm"
72
+ cabinetSoc = "cabinetSoc"
73
+
74
+
75
+ class SmartSceneUnit(IntEnum):
76
+ PERCENT = 1
77
+ NUM = 2
78
+
79
+
80
+ class ConditionItemChildData(BaseModel):
81
+ function: Optional[ConditionFunc]
82
+ data: List[Any]
83
+
84
+
85
+ class PriceConditionItemData(BaseModel):
86
+ childType: PriceConditionType
87
+ childData: ConditionItemChildData
88
+
89
+ @validator("childData", always=True)
90
+ def _check_child_data(cls, value, values):
91
+ child_type = values.get("childType")
92
+ if child_type in {
93
+ PriceConditionType.lowerPrice,
94
+ PriceConditionType.higherPrice,
95
+ }:
96
+ assert value.data[0] > 0, ValueError
97
+ elif child_type in {
98
+ PriceConditionType.expensiveHours,
99
+ PriceConditionType.cheapestHours,
100
+ }:
101
+ assert 1 <= value.data[2] <= 24, ValueError
102
+ return value
103
+
104
+ def to_text(self, lang, unit):
105
+ data = self.childData.data
106
+ func = self.childData.function
107
+ if self.childType == PriceConditionType.price:
108
+ return MESSAGE_ENTRY[self.childType][lang].format(
109
+ MESSAGE_ENTRY[str(func.value)][lang], data[0], unit
110
+ )
111
+ elif self.childType in {
112
+ PriceConditionType.lowerPrice,
113
+ PriceConditionType.higherPrice,
114
+ }:
115
+ return MESSAGE_ENTRY[self.childType][lang].format(
116
+ data[0], "%" if data[1] == 1 else unit
117
+ )
118
+ elif self.childType in {
119
+ PriceConditionType.expensiveHours,
120
+ PriceConditionType.cheapestHours,
121
+ }:
122
+ return MESSAGE_ENTRY[self.childType][lang].format(data[0], data[1], data[2])
123
+
124
+
125
+ class SystemConditionItemData(BaseModel):
126
+ childType: SystemConditionType
127
+ childData: ConditionItemChildData
128
+
129
+ @validator("childData", always=True)
130
+ def _check_child_data(cls, value, values):
131
+ child_type = values.get("childType")
132
+ if child_type in {
133
+ SystemConditionType.systemExportPower,
134
+ SystemConditionType.systemImportPower,
135
+ }:
136
+ assert 0 <= value.data[0] <= 100000, ValueError
137
+ value.data[0] = round(value.data[0], 2) # 功率保留两位小数
138
+ elif child_type == SystemConditionType.systemSoc:
139
+ assert 5 <= value.data[0] <= 100, ValueError
140
+ return value
141
+
142
+ def to_text(self, lang, unit):
143
+ data = self.childData.data
144
+ func = self.childData.function
145
+ if self.childType == SystemConditionType.systemSoc:
146
+ return MESSAGE_ENTRY[self.childType][lang].format(
147
+ MESSAGE_ENTRY[str(func.value)][lang], data[0]
148
+ )
149
+ elif self.childType in {
150
+ SystemConditionType.systemImportPower,
151
+ SystemConditionType.systemExportPower,
152
+ }:
153
+ return MESSAGE_ENTRY[self.childType][lang].format(
154
+ MESSAGE_ENTRY[str(func.value)][lang], data[0]
155
+ )
156
+
157
+
158
+ class CabinetConditionItemData(BaseModel):
159
+ childType: CabinetConditionType
160
+ childData: ConditionItemChildData
161
+
162
+ @validator("childData", always=True)
163
+ def _check_child_data(cls, value, values):
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:
172
+ assert 0 <= value.data[0] <= 100, ValueError
173
+ return value
174
+
175
+ def to_text(self, lang, unit):
176
+ data = self.childData.data
177
+ func = self.childData.function
178
+ if self.childType == CabinetConditionType.cabinetSoc:
179
+ return MESSAGE_ENTRY[self.childType][lang].format(
180
+ MESSAGE_ENTRY[str(func.value)][lang], data[0]
181
+ )
182
+ elif self.childType == CabinetConditionType.cabinetAlarm:
183
+ return MESSAGE_ENTRY[self.childType][lang].format(
184
+ MESSAGE_ENTRY[str(AlarmLevel(data[0]))][lang], lang
185
+ )
186
+
187
+
188
+ class DateConditionItemData(BaseModel):
189
+ childType: DateConditionType
190
+ childData: ConditionItemChildData
191
+
192
+ @validator("childData", always=True)
193
+ def check_param(cls, value, values):
194
+ child_type = values.get("childType")
195
+ data = value.data
196
+ if child_type == DateConditionType.time:
197
+ assert isinstance(data[0], str), ValueError
198
+ elif child_type == DateConditionType.duration:
199
+ assert isinstance(data[0], int), ValueError
200
+ return value
201
+
202
+ def to_text(self, lang, unit):
203
+ if self.childType == DateConditionType.duration:
204
+ return MESSAGE_ENTRY[self.childType][lang].format(self.childData.data[0])
205
+ elif self.childType == DateConditionType.time:
206
+ return MESSAGE_ENTRY[self.childType][lang] + "/" + self.childData.data[0]
207
+
208
+
209
+ class WeatherConditionItemData(BaseModel):
210
+ childType: WeatherConditionType
211
+ childData: ConditionItemChildData
212
+
213
+ @validator("childData", always=True)
214
+ def _check_child_data(cls, value, values):
215
+ child_type = values.get("childType")
216
+ if child_type == WeatherConditionType.irradiance:
217
+ assert value.data[0] > 0, ValueError
218
+ assert 0 <= value.data[1] <= 24, ValueError
219
+ return value
220
+
221
+ def to_text(self, lang, unit):
222
+ func = self.childData.function
223
+ data = self.childData.data
224
+ if self.childType == WeatherConditionType.irradiance:
225
+ return MESSAGE_ENTRY[self.childType][lang].format(
226
+ MESSAGE_ENTRY[str(func.value)][lang], data[0], data[1]
227
+ )
228
+ else:
229
+ return MESSAGE_ENTRY[self.childType][lang].format(
230
+ MESSAGE_ENTRY[str(func.value)][lang], data[0]
231
+ )
232
+
233
+
234
+ class ConditionItem(BaseModel):
235
+ type: ConditionType
236
+ cabinet: Optional[List[str]]
237
+ data: List[
238
+ Union[
239
+ DateConditionItemData,
240
+ WeatherConditionItemData,
241
+ PriceConditionItemData,
242
+ SystemConditionItemData,
243
+ CabinetConditionItemData,
244
+ ]
245
+ ]
246
+
247
+ @validator("cabinet")
248
+ def _check_cabinet(cls, value, values):
249
+ condition_type = values.get("type")
250
+ if condition_type == ConditionType.cabinet:
251
+ assert value, "cabinet is None"
252
+ return value
253
+
254
+ def to_text(self, lang, unit):
255
+ if self.type != ConditionType.cabinet:
256
+ return {self.type: [d.to_text(lang, unit) for d in self.data]}
257
+ elif self.type == ConditionType.cabinet:
258
+ cabinet_sns = ",".join(self.cabinet)
259
+ return {
260
+ self.type: [d.to_text(lang, unit) for d in self.data] + [cabinet_sns]
261
+ }
262
+
263
+
264
+ class SmartSceneCondition(BaseModel):
265
+ operation: LogicFunc
266
+ value: List[ConditionItem]
267
+
268
+ @root_validator
269
+ def _root_check(cls, values):
270
+ if values.get("operation") == LogicFunc.OR:
271
+ new_value = []
272
+ for item in values.get("value"):
273
+ if item.type != ConditionType.date:
274
+ new_value.append(item)
275
+ continue
276
+ new_data = []
277
+ for date_item in item.data:
278
+ if date_item.childType != DateConditionType.duration:
279
+ new_data.append(date_item)
280
+ item.data = new_data
281
+ if item.data:
282
+ new_value.append(item)
283
+ values["value"] = new_value
284
+ return values
285
+
286
+ def to_text(self, lang, unit):
287
+ ret = {"operation": [MESSAGE_ENTRY[self.operation.name][lang]]}
288
+ for v in self.value:
289
+ ret.update(v.to_text(lang, unit))
290
+ return ret
291
+
292
+ def get_duration_info(self):
293
+ for item in self.value:
294
+ if item.type != ConditionType.date:
295
+ continue
296
+ for date_item in item.data:
297
+ if date_item.childType == DateConditionType.duration:
298
+ return (date_item.childData.data[0] // 10) + 1
299
+ return 1
@@ -0,0 +1,173 @@
1
+ import json
2
+ import uuid
3
+ from typing import Optional, List
4
+
5
+ from pydantic import Field
6
+ from pydantic.main import BaseModel
7
+
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,
12
+ ActionType,
13
+ SystemActionType,
14
+ )
15
+ from solax_py_library.smart_scene.types.condition import (
16
+ RepeatFunc,
17
+ SmartSceneCondition,
18
+ LogicFunc,
19
+ ConditionType,
20
+ PriceConditionType,
21
+ ConditionFunc,
22
+ )
23
+
24
+
25
+ class SmartSceneOtherInfo(BaseModel):
26
+ duration_times: Optional[int] = Field(
27
+ description="如果需要判断持续时间,记录当前已持续的时间", default=0
28
+ )
29
+ version: Optional[int] = Field(
30
+ description="当前版本,现用于是否做数据迁移,V009版本开始", default=9
31
+ )
32
+ once_flag: Optional[bool] = Field(
33
+ description="是否是单次执行,默认False", default=False
34
+ )
35
+
36
+ @classmethod
37
+ def new_other_info(cls):
38
+ return SmartSceneOtherInfo(
39
+ duration_times=0,
40
+ version=9,
41
+ once_flag=False,
42
+ ).dict()
43
+
44
+
45
+ class SmartSceneContent(BaseModel):
46
+ name: str = Field(description="Scene name", max_length=100)
47
+ repeatFunction: Optional[RepeatFunc] = Field(description="重复规则")
48
+ weekList: Optional[List[int]]
49
+ conditions: SmartSceneCondition = Field(alias="if")
50
+ thenActions: List[SmartSceneAction] = Field(alias="then")
51
+ elseThenActions: Optional[List[SmartSceneAction]] = Field(alias="elseThen")
52
+
53
+ def build_smart_scene(self, copied=False):
54
+ return {
55
+ "scene_id": str(uuid.uuid4()),
56
+ "switch": False,
57
+ "content": json.dumps(
58
+ {
59
+ "name": self.name if copied is False else self.name + "-copy",
60
+ "repeatFunction": self.repeatFunction,
61
+ "weekList": self.weekList,
62
+ "if": self.conditions.dict(),
63
+ "then": [item.dict() for item in self.thenActions],
64
+ "elseThen": [item.dict() for item in self.elseThenActions]
65
+ if self.elseThenActions
66
+ else None,
67
+ }
68
+ ),
69
+ "other": json.dumps(SmartSceneOtherInfo.new_other_info()),
70
+ }
71
+
72
+ @classmethod
73
+ def rec_scene(cls, lang, cabinet_type):
74
+ scene_rec_1 = {
75
+ "name": MESSAGE_ENTRY["rec_name_01"][lang],
76
+ "instruction": MESSAGE_ENTRY["instruction_01"][lang],
77
+ "repeatFunction": RepeatFunc.EVERYDAY,
78
+ "weekList": [1, 2, 3, 4, 5, 6, 7],
79
+ "if": {
80
+ "operation": LogicFunc.AND,
81
+ "value": [
82
+ {
83
+ "type": ConditionType.buyingPrice,
84
+ "data": [
85
+ {
86
+ "childType": PriceConditionType.price,
87
+ "childData": {
88
+ "function": ConditionFunc.GT,
89
+ "data": [1],
90
+ },
91
+ }
92
+ ],
93
+ }
94
+ ],
95
+ },
96
+ "then": [
97
+ {
98
+ "type": ActionType.system,
99
+ "data": [
100
+ {
101
+ "childType": SystemActionType.workMode,
102
+ "childData": {
103
+ "data": [16, 9, 0, -60, 100],
104
+ },
105
+ }
106
+ ],
107
+ }
108
+ ],
109
+ "elseThen": [],
110
+ }
111
+ scene_rec_2 = {
112
+ "name": MESSAGE_ENTRY["rec_name_02"][lang],
113
+ "instruction": MESSAGE_ENTRY["instruction_02"][lang],
114
+ "repeatFunction": RepeatFunc.EVERYDAY,
115
+ "weekList": [1, 2, 3, 4, 5, 6, 7],
116
+ "if": {
117
+ "operation": LogicFunc.AND,
118
+ "value": [
119
+ {
120
+ "type": ConditionType.sellingPrice,
121
+ "data": [
122
+ {
123
+ "childType": PriceConditionType.price,
124
+ "childData": {
125
+ "function": ConditionFunc.LT,
126
+ "data": [1],
127
+ },
128
+ }
129
+ ],
130
+ }
131
+ ],
132
+ },
133
+ "then": [
134
+ {
135
+ "type": ActionType.system,
136
+ "data": [
137
+ {
138
+ "childType": SystemActionType.exportControl,
139
+ "childData": {
140
+ "data": [1, 2, 0, 1],
141
+ },
142
+ }
143
+ ],
144
+ }
145
+ ],
146
+ "elseThen": [],
147
+ }
148
+ if cabinet_type in TRENE_CABINET_ENUM:
149
+ return [scene_rec_2]
150
+ else:
151
+ return [scene_rec_1, scene_rec_2]
152
+
153
+ def get_weekday_index(self):
154
+ if self.repeatFunction == RepeatFunc.EVERYDAY:
155
+ return [1, 2, 3, 4, 5, 6, 7]
156
+ elif self.repeatFunction == RepeatFunc.WEEKDAY:
157
+ return [1, 2, 3, 4, 5]
158
+ elif self.repeatFunction == RepeatFunc.WEEKEND:
159
+ return [6, 7]
160
+ elif self.repeatFunction == RepeatFunc.CUSTOM:
161
+ return self.weekList
162
+
163
+ def get_condition_types(self):
164
+ condition_types = set()
165
+ for v in self.conditions.value:
166
+ condition_types.add(v.type)
167
+ return condition_types
168
+
169
+ def repeat_text_info(self, lang):
170
+ if self.repeatFunction != RepeatFunc.CUSTOM:
171
+ return MESSAGE_ENTRY[str(self.repeatFunction.value)][lang]
172
+ else:
173
+ return ",".join([MESSAGE_ENTRY[str(d)][lang] for d in self.weekList])
@@ -1,3 +1,3 @@
1
- from . import types, core, exceptions
2
-
3
- __all__ = ["core", "exceptions", "types"]
1
+ from . import types, core, exceptions
2
+
3
+ __all__ = ["core", "exceptions", "types"]
@@ -1,5 +1,5 @@
1
- from .crc_table import CRC_Table
2
-
3
- __all__ = [
4
- "CRC_Table",
5
- ]
1
+ from .crc_table import CRC_Table
2
+
3
+ __all__ = [
4
+ "CRC_Table",
5
+ ]