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.
- solax_py_library/__init__.py +1 -1
- solax_py_library/device/constant/cabinet.py +2 -0
- solax_py_library/device/core/interver/__init__.py +36 -36
- solax_py_library/device/core/interver/base.py +215 -215
- solax_py_library/device/types/alarm.py +16 -0
- solax_py_library/device/types/inverter_config.py +41 -41
- solax_py_library/device/types/modbus_point.py +30 -30
- solax_py_library/exception.py +10 -10
- solax_py_library/smart_scene/__init__.py +0 -0
- solax_py_library/smart_scene/constant/__init__.py +0 -0
- solax_py_library/smart_scene/constant/message_entry.py +179 -0
- solax_py_library/smart_scene/core/__init__.py +0 -0
- solax_py_library/smart_scene/core/action/__init__.py +0 -0
- solax_py_library/smart_scene/core/action/base.py +10 -0
- solax_py_library/smart_scene/core/action/ems_action.py +6 -0
- solax_py_library/smart_scene/core/action/system_action.py +6 -0
- solax_py_library/smart_scene/core/condition/__init__.py +17 -0
- solax_py_library/smart_scene/core/condition/base.py +17 -0
- solax_py_library/smart_scene/core/condition/cabinet_condition.py +44 -0
- solax_py_library/smart_scene/core/condition/date_condition.py +23 -0
- solax_py_library/smart_scene/core/condition/price_condition.py +110 -0
- solax_py_library/smart_scene/core/condition/system_condition.py +35 -0
- solax_py_library/smart_scene/core/condition/weather_condition.py +61 -0
- solax_py_library/smart_scene/core/service/__init__.py +3 -0
- solax_py_library/smart_scene/core/service/runner.py +156 -0
- solax_py_library/smart_scene/exceptions/__init__.py +7 -0
- solax_py_library/smart_scene/exceptions/price.py +5 -0
- solax_py_library/smart_scene/exceptions/smart_scene.py +82 -0
- solax_py_library/smart_scene/exceptions/weather.py +5 -0
- solax_py_library/smart_scene/types/__init__.py +0 -0
- solax_py_library/smart_scene/types/action.py +164 -0
- solax_py_library/smart_scene/types/condition.py +299 -0
- solax_py_library/smart_scene/types/smart_scene_content.py +173 -0
- solax_py_library/snap_shot/__init__.py +3 -3
- solax_py_library/snap_shot/constant/__init__.py +5 -5
- solax_py_library/snap_shot/constant/crc_table.py +258 -258
- solax_py_library/snap_shot/core/base_modbus.py +14 -14
- solax_py_library/snap_shot/core/parser.py +261 -225
- solax_py_library/snap_shot/core/snap_shot.py +25 -7
- solax_py_library/snap_shot/exceptions/__init__.py +3 -3
- solax_py_library/snap_shot/exceptions/snap_shot.py +9 -9
- solax_py_library/snap_shot/types/__init__.py +15 -15
- solax_py_library/snap_shot/types/address.py +39 -39
- solax_py_library/test/__init__.py +0 -0
- solax_py_library/test/test_smart_scene/__init__.py +0 -0
- solax_py_library/test/test_smart_scene/test_condition.py +11 -0
- solax_py_library/test/test_utils/__init__.py +0 -0
- solax_py_library/test/test_utils/test_cloud_client.py +14 -0
- solax_py_library/upload/__init__.py +3 -3
- solax_py_library/upload/api/service.py +24 -24
- solax_py_library/upload/core/upload_service/ftp.py +1 -0
- solax_py_library/upload/exceptions/__init__.py +8 -8
- solax_py_library/upload/exceptions/upload_error.py +21 -21
- solax_py_library/utils/cloud_client.py +210 -0
- solax_py_library/utils/struct_util.py +42 -30
- solax_py_library/utils/time_util.py +38 -0
- {solax_py_library-1.0.0.23.dist-info → solax_py_library-1.0.0.26.dist-info}/METADATA +2 -1
- solax_py_library-1.0.0.26.dist-info/RECORD +80 -0
- solax_py_library-1.0.0.23.dist-info/RECORD +0 -46
- {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,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"
|
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]
|