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,156 @@
1
+ import json
2
+ import traceback
3
+ from datetime import datetime
4
+ from typing import List, Set
5
+
6
+ from solax_py_library.smart_scene.core.condition import BaseCondition
7
+ from solax_py_library.smart_scene.types.action import (
8
+ SmartSceneAction,
9
+ )
10
+ from solax_py_library.smart_scene.types.condition import (
11
+ ConditionType,
12
+ RepeatFunc,
13
+ LogicFunc,
14
+ SmartSceneCondition,
15
+ DateConditionType,
16
+ ConditionItem,
17
+ )
18
+ from solax_py_library.smart_scene.types.smart_scene_content import (
19
+ SmartSceneContent,
20
+ SmartSceneOtherInfo,
21
+ )
22
+ from tornado.log import app_log
23
+
24
+
25
+ class SmartSceneRunner:
26
+ def __init__(self, condition_classes, action_classes):
27
+ self.condition_class_map = {}
28
+ self.action_class_map = {}
29
+
30
+ for condition_class in condition_classes:
31
+ if not isinstance(condition_class, BaseCondition):
32
+ raise ValueError(
33
+ f"condition({condition_class}) is not derived from BaseCondition"
34
+ )
35
+ self.condition_class_map[condition_class.condition_type] = condition_class
36
+
37
+ for action_class in action_classes:
38
+ self.action_class_map[action_class.action_type] = action_class
39
+
40
+ @staticmethod
41
+ def _handle_repeat_info(scene_content: SmartSceneContent, once_flag):
42
+ # once_flag: 是否执行过
43
+ if scene_content.repeatFunction == RepeatFunc.ONCE:
44
+ return False if once_flag else True
45
+ week_list = scene_content.get_weekday_index()
46
+ if datetime.now().weekday() + 1 in week_list:
47
+ return True
48
+ return False
49
+
50
+ def smart_scene_handle(self, scene_instance):
51
+ other_info = SmartSceneOtherInfo.parse_raw(scene_instance.other)
52
+ try:
53
+ scene_data = SmartSceneContent.parse_raw(scene_instance.content)
54
+
55
+ # repeat,是否执行
56
+ if not self._handle_repeat_info(scene_data, other_info.once_flag):
57
+ app_log.info(
58
+ f"场景名称: {scene_data.name}, 场景ID: {scene_instance.scene_id} 不符合重复条件,不执行"
59
+ )
60
+ return
61
+ scene_id = scene_instance.scene_id
62
+ app_log.info(
63
+ f"场景名称: {scene_data.name}, 场景ID: {scene_id}, 判断类型: {scene_data.conditions.operation}:"
64
+ )
65
+
66
+ # 条件判断
67
+ result = self._handle_conditions(
68
+ scene_data.conditions.operation,
69
+ scene_data.conditions,
70
+ other_info,
71
+ )
72
+
73
+ # 动作执行
74
+ new_exec_number = scene_instance.exec_number
75
+ if result:
76
+ self._handle_action(scene_id, scene_data.thenActions, log_prefix="THEN")
77
+ new_exec_number = 1
78
+ other_info.once_flag = True
79
+ elif scene_instance.exec_number == 1 and scene_data.elseThenActions:
80
+ self._handle_action(
81
+ scene_id, scene_data.elseThenActions, log_prefix="ELSE THEN"
82
+ )
83
+ new_exec_number = 0
84
+ other_info.duration_times = 0
85
+
86
+ app_log.info(f"{scene_id} 执行完毕\n")
87
+ scene_instance.exec_number = new_exec_number
88
+ scene_instance.other = json.dumps(other_info.dict())
89
+ return scene_instance
90
+ except Exception:
91
+ app_log.error(
92
+ f"{scene_instance.scene_id} 执行智能场景异常 {traceback.format_exc()}"
93
+ )
94
+
95
+ def _handle_action(
96
+ self, scene_id, actions: List[SmartSceneAction], log_prefix="THEN"
97
+ ):
98
+ if not actions:
99
+ return
100
+ for action_info in actions:
101
+ action_class = self.action_class_map[action_info.type]
102
+ for child_info in action_info.data:
103
+ ret = action_class.do_func(scene_id=scene_id, data=child_info)
104
+ app_log.info(f"{log_prefix}条件 {child_info} 执行结果: {ret}")
105
+
106
+ def _handle_conditions(
107
+ self,
108
+ operation: LogicFunc,
109
+ conditions: SmartSceneCondition,
110
+ other_info: SmartSceneOtherInfo,
111
+ ):
112
+ need_duration_times = conditions.get_duration_info()
113
+ if not self._check_conditions(operation, conditions):
114
+ other_info.duration_times = 0
115
+ return False
116
+ if other_info.duration_times < need_duration_times:
117
+ other_info.duration_times += 1
118
+ app_log.info(
119
+ f"need times: {need_duration_times}, current times: {other_info.duration_times}"
120
+ )
121
+ return other_info.duration_times >= need_duration_times
122
+
123
+ def _check_conditions(self, operation, conditions):
124
+ ret_list = []
125
+ for cond in conditions.value:
126
+ parent_type = cond.type # 父条件类型 (日期、天气、电价)
127
+ condition_class = self.condition_class_map[parent_type]
128
+ ctx = self._build_condition_ctx(cond)
129
+ for child_info in cond.data:
130
+ if child_info.childType == DateConditionType.duration:
131
+ continue
132
+ ret = condition_class.meet_func(data=child_info, ctx=ctx)
133
+ app_log.info(f"IF条件 {child_info} 判断结果: {ret}")
134
+ ret_list.append(ret)
135
+ # 如果该条件判断不满足,并且客户设置为and,则无需继续判断,返回失败
136
+ if not ret and operation == LogicFunc.AND:
137
+ return False
138
+ # 如果条件判断满足,并且客户设置为or,则无需继续判断,返回成功
139
+ if ret and operation == LogicFunc.OR:
140
+ return True
141
+ if (operation == LogicFunc.AND and all(ret_list)) or (
142
+ operation == LogicFunc.OR and any(ret_list)
143
+ ):
144
+ return True
145
+ return False
146
+
147
+ @staticmethod
148
+ def _build_condition_ctx(condition_info: ConditionItem):
149
+ ctx = {}
150
+ if condition_info.type == ConditionType.cabinet:
151
+ ctx["cabinet"] = condition_info.cabinet
152
+ return ctx
153
+
154
+ def update_all_condition_data(self, condition_types: Set[ConditionType]):
155
+ for cond_type in condition_types:
156
+ self.condition_class_map[cond_type].update_value()
@@ -0,0 +1,7 @@
1
+ from .price import ElectricityPriceFailure
2
+ from .weather import WeatherFailure
3
+
4
+ __all__ = [
5
+ "ElectricityPriceFailure",
6
+ "WeatherFailure",
7
+ ]
@@ -0,0 +1,5 @@
1
+ from solax_py_library.exception import SolaxBaseError
2
+
3
+
4
+ class ElectricityPriceFailure(SolaxBaseError):
5
+ message = "cloud__electricity_price_failure"
@@ -0,0 +1,82 @@
1
+ from solax_py_library.exception import SolaxBaseError
2
+
3
+
4
+ class MissParam(SolaxBaseError):
5
+ code = 0x2001
6
+ message = "smart_scene__miss_param"
7
+
8
+
9
+ class OnlyPositive(MissParam):
10
+ message = "smart_scene__only_positive"
11
+
12
+
13
+ class TimeRange(MissParam):
14
+ message = "smart_scene__time_range"
15
+
16
+
17
+ class IrradianceOnlyPositive(MissParam):
18
+ message = "smart_scene__irradiance_only_positive"
19
+
20
+
21
+ class ExportLimitNum(MissParam):
22
+ message = "smart_scene__export_limit_num"
23
+
24
+
25
+ class ExportLimitPercent(MissParam):
26
+ message = "smart_scene__export_limit_percent"
27
+
28
+
29
+ class ImportLimitNum(MissParam):
30
+ message = "smart_scene__import_limit_num"
31
+
32
+
33
+ class ImportOnlyPositive(MissParam):
34
+ message = "smart_scene__import_only_positive"
35
+
36
+
37
+ class SocLimit(MissParam):
38
+ message = "smart_scene__soc_limit"
39
+
40
+
41
+ class ActivePowerLimitNum(MissParam):
42
+ message = "smart_scene__active_power_limit_num"
43
+
44
+
45
+ class ReactivePowerLimitNum(MissParam):
46
+ message = "smart_scene__reactive_power_limit_num"
47
+
48
+
49
+ class EnergyLimit(MissParam):
50
+ message = "smart_scene__energy_limit"
51
+
52
+
53
+ class PowerLimitNum(MissParam):
54
+ message = "smart_scene__power_limit_num"
55
+
56
+
57
+ class BatteryPowerLimitNum(MissParam):
58
+ message = "smart_scene__battery_power_limit_num"
59
+
60
+
61
+ class PvOnlyGe0(MissParam):
62
+ message = "smart_scene__pv_only_ge_0"
63
+
64
+
65
+ class CountLimit(MissParam):
66
+ message = "smart_scene__count_limit"
67
+
68
+
69
+ class NameLengthLimit(MissParam):
70
+ message = "smart_scene__name_length_limit"
71
+
72
+
73
+ class UniqueLimit(MissParam):
74
+ message = "smart_scene__unique_limit"
75
+
76
+
77
+ class ElectricityPriceFailure(MissParam):
78
+ message = "cloud__electricity_price_failure"
79
+
80
+
81
+ class WeatherFailure(MissParam):
82
+ message = "cloud__weather_failure"
@@ -0,0 +1,5 @@
1
+ from solax_py_library.exception import SolaxBaseError
2
+
3
+
4
+ class WeatherFailure(SolaxBaseError):
5
+ message = "cloud__weather_failure"
File without changes
@@ -0,0 +1,164 @@
1
+ from enum import Enum
2
+ from typing import List, Union, Any
3
+
4
+ from pydantic import BaseModel
5
+
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
+
9
+
10
+ class ActionType(str, Enum):
11
+ EMS1000 = "EMS1000"
12
+ system = "system"
13
+
14
+
15
+ class EmsActionType(str, Enum):
16
+ DoControl = "DoControl"
17
+
18
+
19
+ class SystemActionType(str, Enum):
20
+ systemSwitch = "systemSwitch"
21
+ exportControl = "exportControl"
22
+ importControl = "importControl"
23
+ workMode = "workMode"
24
+
25
+
26
+ class DoControl(BaseModel):
27
+ DoNumber: int
28
+ DoValue: int
29
+
30
+
31
+ class ActionChildData(BaseModel):
32
+ data: List[Any]
33
+
34
+
35
+ class SystemActionChildData(ActionChildData):
36
+ ...
37
+
38
+
39
+ class SystemActionItemData(BaseModel):
40
+ childType: SystemActionType
41
+ childData: SystemActionChildData
42
+
43
+ def to_text(self, lang, cabinet_type):
44
+ if self.childType == SystemActionType.systemSwitch:
45
+ switch = "off" if self.childData.data[0] == 0 else "on"
46
+ return MESSAGE_ENTRY[self.childType][lang].format(
47
+ MESSAGE_ENTRY[switch][lang]
48
+ )
49
+ elif self.childType == SystemActionType.exportControl:
50
+ if self.childData.data[0] == 0:
51
+ return MESSAGE_ENTRY["exportControlOff"][lang]
52
+ else:
53
+ switch = "on"
54
+ mode = "total" if self.childData.data[1] == 1 else "per phase"
55
+ unit = "kW" if self.childData.data[3] == 2 else "%"
56
+ return MESSAGE_ENTRY[self.childType][lang].format(
57
+ MESSAGE_ENTRY[switch][lang],
58
+ MESSAGE_ENTRY[mode][lang],
59
+ self.childData.data[2],
60
+ unit,
61
+ )
62
+ elif self.childType == SystemActionType.importControl:
63
+ if self.childData.data[0] == 0:
64
+ return MESSAGE_ENTRY["importControlOff"][lang]
65
+ else:
66
+ if cabinet_type in TRENE_CABINET_ENUM:
67
+ msg = (
68
+ "importControl_standby"
69
+ if self.childData.data[1] == 0
70
+ else "importControl_discharge"
71
+ )
72
+ return MESSAGE_ENTRY[self.childType][lang].format(
73
+ MESSAGE_ENTRY["on"][lang],
74
+ MESSAGE_ENTRY[msg][lang],
75
+ self.childData.data[2],
76
+ )
77
+ else:
78
+ return MESSAGE_ENTRY["importControl_AELIO"][lang].format(
79
+ MESSAGE_ENTRY["on"][lang], self.childData.data[1]
80
+ )
81
+ elif self.childType == SystemActionType.workMode:
82
+ return self.work_mode_to_text(lang)
83
+
84
+ def work_mode_to_text(self, lang):
85
+ work_mode = {
86
+ 0: "Self-use",
87
+ 1: "Feedin priority",
88
+ 2: "Back up mode",
89
+ 3: "Manual mode",
90
+ 4: "Peak Shaving",
91
+ 16: "VPP",
92
+ }
93
+ # 3: 手动(3 强充,4 强放,5 停止充放电)
94
+ manual_mode = {
95
+ 3: "Forced charging",
96
+ 4: "Forced discharging",
97
+ 5: "Stop charging and discharging",
98
+ }
99
+ vpp_mode = {
100
+ 1: "Power Control Mode",
101
+ 2: "Electric Quantity Target Control Mode",
102
+ 3: "SOC Target Control Mode",
103
+ 4: "Push Power - Positive/Negative Mode",
104
+ 5: "Push Power - Zero Mode",
105
+ 6: "Self-Consume - Charge/Discharge Mode",
106
+ 7: "Self-Consume - Charge Only Mode",
107
+ 8: "PV&BAT Individual Setting – Duration Mode",
108
+ 9: "PV&BAT Individual Setting – Target SOC Mode",
109
+ }
110
+ value_data = self.childData.data
111
+ # 手动模式
112
+ if value_data[0] in [3]:
113
+ if value_data[1] in [3, 4]:
114
+ return MESSAGE_ENTRY[work_mode[value_data[0]]][lang].format(
115
+ MESSAGE_ENTRY[manual_mode[value_data[1]]][lang].format(
116
+ value_data[2], value_data[3]
117
+ )
118
+ )
119
+ else:
120
+ return MESSAGE_ENTRY[work_mode[value_data[0]]][lang].format(
121
+ MESSAGE_ENTRY[manual_mode[value_data[1]]][lang]
122
+ )
123
+ elif value_data[0] in [16]:
124
+ mode = vpp_mode[value_data[1]]
125
+ if value_data[1] in [1, 2, 3, 8]:
126
+ return MESSAGE_ENTRY[mode][lang].format(value_data[2], value_data[3])
127
+ elif value_data[1] in [4]:
128
+ return MESSAGE_ENTRY[mode][lang].format(value_data[2])
129
+ elif value_data[1] in [5, 6, 7]:
130
+ return MESSAGE_ENTRY[mode][lang]
131
+ elif value_data[1] in [9]:
132
+ return MESSAGE_ENTRY[mode][lang].format(
133
+ value_data[2], value_data[3], value_data[4]
134
+ )
135
+ else:
136
+ return MESSAGE_ENTRY[work_mode[value_data[0]]][lang]
137
+ return ""
138
+
139
+
140
+ class EmsActionChildData(ActionChildData):
141
+ data: List[DoControl]
142
+
143
+
144
+ class EmsActionItemData(BaseModel):
145
+ childType: EmsActionType
146
+ childData: EmsActionChildData
147
+
148
+ def to_text(self, lang, cabinet_type):
149
+ if self.childType == EmsActionType.DoControl:
150
+ message = ""
151
+ for do_info in self.childData.data:
152
+ message += MESSAGE_ENTRY[self.childType][lang].format(
153
+ do_info.DoNumber,
154
+ do_info.DoValue,
155
+ )
156
+ return message
157
+
158
+
159
+ class SmartSceneAction(BaseModel):
160
+ type: ActionType
161
+ data: List[Union[EmsActionItemData, SystemActionItemData]]
162
+
163
+ def to_text(self, lang, cabinet_type):
164
+ return [item.to_text(lang, cabinet_type) for item in self.data]