solax-py-library 1.0.0.28__tar.gz → 1.0.0.29__tar.gz

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 (85) hide show
  1. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/PKG-INFO +1 -1
  2. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/pyproject.toml +1 -1
  3. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/constant/message_entry.py +3 -3
  4. solax_py_library-1.0.0.29/solax_py_library/smart_scene/core/service/check.py +33 -0
  5. solax_py_library-1.0.0.29/solax_py_library/smart_scene/types/action.py +334 -0
  6. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/types/condition.py +27 -29
  7. solax_py_library-1.0.0.29/solax_py_library/test/test_smart_scene/test_check_func.py +83 -0
  8. solax_py_library-1.0.0.28/solax_py_library/smart_scene/types/action.py +0 -164
  9. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/README.md +0 -0
  10. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/__init__.py +0 -0
  11. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/__init__.py +0 -0
  12. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/constant/__init__.py +0 -0
  13. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/constant/cabinet.py +0 -0
  14. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/constant/inverter_model_info.py +0 -0
  15. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/core/__init__.py +0 -0
  16. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/core/interver/__init__.py +0 -0
  17. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/core/interver/base.py +0 -0
  18. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/types/__init__.py +0 -0
  19. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/types/alarm.py +0 -0
  20. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/types/device.py +0 -0
  21. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/types/inverter_config.py +0 -0
  22. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/device/types/modbus_point.py +0 -0
  23. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/exception.py +0 -0
  24. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/__init__.py +0 -0
  25. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/constant/__init__.py +0 -0
  26. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/__init__.py +0 -0
  27. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/action/__init__.py +0 -0
  28. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/action/base.py +0 -0
  29. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/action/ems_action.py +0 -0
  30. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/action/system_action.py +0 -0
  31. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/condition/__init__.py +0 -0
  32. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/condition/base.py +0 -0
  33. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/condition/cabinet_condition.py +0 -0
  34. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/condition/date_condition.py +0 -0
  35. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/condition/price_condition.py +0 -0
  36. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/condition/system_condition.py +0 -0
  37. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/condition/weather_condition.py +0 -0
  38. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/service/__init__.py +0 -0
  39. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/core/service/runner.py +0 -0
  40. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/exceptions/__init__.py +0 -0
  41. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/exceptions/price.py +0 -0
  42. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/exceptions/smart_scene.py +0 -0
  43. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/exceptions/weather.py +0 -0
  44. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/types/__init__.py +0 -0
  45. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/types/condition_value.py +0 -0
  46. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/smart_scene/types/smart_scene_content.py +0 -0
  47. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/__init__.py +0 -0
  48. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/constant/__init__.py +0 -0
  49. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/constant/crc_table.py +0 -0
  50. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/core/__init__.py +0 -0
  51. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/core/base_modbus.py +0 -0
  52. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/core/parser.py +0 -0
  53. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/core/snap_shot.py +0 -0
  54. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/exceptions/__init__.py +0 -0
  55. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/exceptions/snap_shot.py +0 -0
  56. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/test/__init__.py +0 -0
  57. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/types/__init__.py +0 -0
  58. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/snap_shot/types/address.py +0 -0
  59. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/test/__init__.py +0 -0
  60. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/test/test_smart_scene/__init__.py +0 -0
  61. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/test/test_smart_scene/test_condition.py +0 -0
  62. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/test/test_utils/__init__.py +0 -0
  63. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/test/test_utils/test_cloud_client.py +0 -0
  64. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/__init__.py +0 -0
  65. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/api/__init__.py +0 -0
  66. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/api/service.py +0 -0
  67. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/core/__init__.py +0 -0
  68. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/core/data_adapter/__init__.py +0 -0
  69. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/core/data_adapter/base.py +0 -0
  70. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/core/data_adapter/csv.py +0 -0
  71. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/core/upload_service/__init__.py +0 -0
  72. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/core/upload_service/base.py +0 -0
  73. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/core/upload_service/ftp.py +0 -0
  74. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/exceptions/__init__.py +0 -0
  75. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/exceptions/upload_error.py +0 -0
  76. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/test/__init__.py +0 -0
  77. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/test/test_ftp.py +0 -0
  78. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/types/__init__.py +0 -0
  79. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/types/client.py +0 -0
  80. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/upload/types/ftp.py +0 -0
  81. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/utils/__init__.py +0 -0
  82. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/utils/cloud_client.py +0 -0
  83. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/utils/common.py +0 -0
  84. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/utils/struct_util.py +0 -0
  85. {solax_py_library-1.0.0.28 → solax_py_library-1.0.0.29}/solax_py_library/utils/time_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: solax-py-library
3
- Version: 1.0.0.28
3
+ Version: 1.0.0.29
4
4
  Summary: some common tool
5
5
  Author: shenlvyu
6
6
  Author-email: 13296718439@163.com
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "solax-py-library"
3
- version = "1.0.0.28"
3
+ version = "1.0.0.29"
4
4
  description = "some common tool"
5
5
  authors = ["shenlvyu <13296718439@163.com>", "x5m8944pjl <371630856@qq.com>"]
6
6
  readme = "README.md"
@@ -156,8 +156,8 @@ MESSAGE_ENTRY = {
156
156
  "en_US": "PV power target is {}kW. Battery power target is {}kW. target SOC is {}%",
157
157
  },
158
158
  "DoControl": {"zh_CN": "DO{}: {};", "en_US": "DO{}: {};"},
159
- "duration": {"zh_CN": "持续: {}秒", "en_US": "last: {} seconds"},
160
- "systemSoc": {"zh_CN": "系统soc {} {}%", "en_US": "system soc {} {}%"},
159
+ "duration": {"zh_CN": "持续: {}秒", "en_US": "Last: {} seconds"},
160
+ "systemSoc": {"zh_CN": "系统soc {} {}%", "en_US": "System soc {} {}%"},
161
161
  "systemImportPower": {
162
162
  "zh_CN": "电网买电功率 {} {}KW",
163
163
  "en_US": "Grid import power {} {}KW",
@@ -166,7 +166,7 @@ MESSAGE_ENTRY = {
166
166
  "zh_CN": "电网馈电功率 {} {}KW",
167
167
  "en_US": "Grid export power {} {}KW",
168
168
  },
169
- "cabinetSoc": {"zh_CN": "机柜soc {} {}%", "en_US": "cabinet soc {} {}%"},
169
+ "cabinetSoc": {"zh_CN": "机柜soc {} {}%", "en_US": "Cabinet soc {} {}%"},
170
170
  "EMERGENCY": {"zh_CN": "紧急告警", "en_US": "Emergency alarm"},
171
171
  "TIPS": {"zh_CN": "状态提醒", "en_US": "State Tips"},
172
172
  "NORMAL": {"zh_CN": "普通告警", "en_US": "Normal alarm"},
@@ -0,0 +1,33 @@
1
+ from typing import List, Dict, Any
2
+
3
+ from solax_py_library.smart_scene.types.action import SmartSceneAction, ActionType
4
+ from solax_py_library.smart_scene.types.condition import (
5
+ SmartSceneCondition,
6
+ ConditionType,
7
+ )
8
+
9
+
10
+ def action_param_check(actions: List[SmartSceneAction], ctx: Dict[str, Any]):
11
+ """动作里的参数范围判定"""
12
+ for action in actions:
13
+ if action.type != ActionType.system:
14
+ continue
15
+ for action_data in action.data:
16
+ ret = action_data.check_param(ctx)
17
+ if ret is not None:
18
+ return ret
19
+ return True
20
+
21
+
22
+ def condition_param_check(condition: SmartSceneCondition, ctx: Dict[str, Any]):
23
+ for condition_data in condition.value:
24
+ if condition_data.type not in [
25
+ ConditionType.systemCondition,
26
+ ConditionType.cabinet,
27
+ ]:
28
+ continue
29
+ for data in condition_data.data:
30
+ ret = data.check_param(ctx)
31
+ if ret is not None:
32
+ return ret
33
+ return True
@@ -0,0 +1,334 @@
1
+ from enum import Enum, IntEnum
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
+ from solax_py_library.smart_scene.exceptions.smart_scene import (
9
+ ExportLimitNum,
10
+ ImportLimitNum,
11
+ ImportOnlyPositive,
12
+ PowerLimitNum,
13
+ SocLimit,
14
+ ActivePowerLimitNum,
15
+ ReactivePowerLimitNum,
16
+ EnergyLimit,
17
+ BatteryPowerLimitNum,
18
+ PvOnlyGe0,
19
+ ExportLimitPercent,
20
+ )
21
+
22
+
23
+ class SmartSceneUnit(IntEnum):
24
+ PERCENT = 1
25
+ NUM = 2
26
+
27
+
28
+ class ActionType(str, Enum):
29
+ EMS1000 = "EMS1000"
30
+ system = "system"
31
+
32
+
33
+ class EmsActionType(str, Enum):
34
+ DoControl = "DoControl"
35
+
36
+
37
+ class SystemActionType(str, Enum):
38
+ systemSwitch = "systemSwitch"
39
+ exportControl = "exportControl"
40
+ importControl = "importControl"
41
+ workMode = "workMode"
42
+
43
+
44
+ class DoControl(BaseModel):
45
+ DoNumber: int
46
+ DoValue: int
47
+
48
+
49
+ class ActionChildData(BaseModel):
50
+ data: List[Any]
51
+
52
+
53
+ class SystemActionChildData(ActionChildData):
54
+ ...
55
+
56
+
57
+ class ActionItemData(BaseModel):
58
+ def check_param(self, ctx):
59
+ ...
60
+
61
+
62
+ class SystemActionItemData(ActionItemData):
63
+ childType: SystemActionType
64
+ childData: SystemActionChildData
65
+
66
+ def to_text(self, lang, cabinet_type):
67
+ if self.childType == SystemActionType.systemSwitch:
68
+ switch = "off" if self.childData.data[0] == 0 else "on"
69
+ return MESSAGE_ENTRY[self.childType][lang].format(
70
+ MESSAGE_ENTRY[switch][lang]
71
+ )
72
+ elif self.childType == SystemActionType.exportControl:
73
+ if self.childData.data[0] == 0:
74
+ return MESSAGE_ENTRY["exportControlOff"][lang]
75
+ else:
76
+ switch = "on"
77
+ mode = "total" if self.childData.data[1] == 1 else "per phase"
78
+ unit = "kW" if self.childData.data[3] == 2 else "%"
79
+ return MESSAGE_ENTRY[self.childType][lang].format(
80
+ MESSAGE_ENTRY[switch][lang],
81
+ MESSAGE_ENTRY[mode][lang],
82
+ self.childData.data[2],
83
+ unit,
84
+ )
85
+ elif self.childType == SystemActionType.importControl:
86
+ if self.childData.data[0] == 0:
87
+ return MESSAGE_ENTRY["importControlOff"][lang]
88
+ else:
89
+ if cabinet_type in TRENE_CABINET_ENUM:
90
+ msg = (
91
+ "importControl_standby"
92
+ if self.childData.data[1] == 0
93
+ else "importControl_discharge"
94
+ )
95
+ return MESSAGE_ENTRY[self.childType][lang].format(
96
+ MESSAGE_ENTRY["on"][lang],
97
+ MESSAGE_ENTRY[msg][lang],
98
+ self.childData.data[2],
99
+ )
100
+ else:
101
+ return MESSAGE_ENTRY["importControl_AELIO"][lang].format(
102
+ MESSAGE_ENTRY["on"][lang], self.childData.data[1]
103
+ )
104
+ elif self.childType == SystemActionType.workMode:
105
+ return self.work_mode_to_text(lang)
106
+
107
+ def work_mode_to_text(self, lang):
108
+ work_mode = {
109
+ 0: "Self-use",
110
+ 1: "Feedin priority",
111
+ 2: "Back up mode",
112
+ 3: "Manual mode",
113
+ 4: "Peak Shaving",
114
+ 16: "VPP",
115
+ }
116
+ # 3: 手动(3 强充,4 强放,5 停止充放电)
117
+ manual_mode = {
118
+ 3: "Forced charging",
119
+ 4: "Forced discharging",
120
+ 5: "Stop charging and discharging",
121
+ }
122
+ vpp_mode = {
123
+ 1: "Power Control Mode",
124
+ 2: "Electric Quantity Target Control Mode",
125
+ 3: "SOC Target Control Mode",
126
+ 4: "Push Power - Positive/Negative Mode",
127
+ 5: "Push Power - Zero Mode",
128
+ 6: "Self-Consume - Charge/Discharge Mode",
129
+ 7: "Self-Consume - Charge Only Mode",
130
+ 8: "PV&BAT Individual Setting – Duration Mode",
131
+ 9: "PV&BAT Individual Setting – Target SOC Mode",
132
+ }
133
+ value_data = self.childData.data
134
+ # 手动模式
135
+ if value_data[0] in [3]:
136
+ if value_data[1] in [3, 4]:
137
+ return MESSAGE_ENTRY[work_mode[value_data[0]]][lang].format(
138
+ MESSAGE_ENTRY[manual_mode[value_data[1]]][lang].format(
139
+ value_data[2], value_data[3]
140
+ )
141
+ )
142
+ else:
143
+ return MESSAGE_ENTRY[work_mode[value_data[0]]][lang].format(
144
+ MESSAGE_ENTRY[manual_mode[value_data[1]]][lang]
145
+ )
146
+ elif value_data[0] in [16]:
147
+ mode = vpp_mode[value_data[1]]
148
+ if value_data[1] in [1, 2, 3, 8]:
149
+ return MESSAGE_ENTRY[mode][lang].format(value_data[2], value_data[3])
150
+ elif value_data[1] in [4]:
151
+ return MESSAGE_ENTRY[mode][lang].format(value_data[2])
152
+ elif value_data[1] in [5, 6, 7]:
153
+ return MESSAGE_ENTRY[mode][lang]
154
+ elif value_data[1] in [9]:
155
+ return MESSAGE_ENTRY[mode][lang].format(
156
+ value_data[2], value_data[3], value_data[4]
157
+ )
158
+ else:
159
+ return MESSAGE_ENTRY[work_mode[value_data[0]]][lang]
160
+ return ""
161
+
162
+ def check_param(self, ctx):
163
+ if self.childType == SystemActionType.exportControl:
164
+ export_power_top_limit = ctx.pop("export_power_top_limit")
165
+ switch = self.childData.data[0]
166
+ if not switch:
167
+ return
168
+ _, _, value, unit = self.childData.data
169
+ if unit == SmartSceneUnit.NUM:
170
+ if value > export_power_top_limit or value < 0:
171
+ return ExportLimitNum, {"up_limit": export_power_top_limit}
172
+ else:
173
+ if value > 0 or value < 110:
174
+ return ExportLimitPercent, {}
175
+ elif self.childType == SystemActionType.importControl:
176
+ import_power_top_limit = ctx.pop("import_power_top_limit", None)
177
+ switch = self.childData.data[0]
178
+ if not switch:
179
+ return
180
+ value = self.childData.data[-1]
181
+ if import_power_top_limit is not None:
182
+ if value > import_power_top_limit or value < 0:
183
+ return (
184
+ ImportLimitNum,
185
+ {"up_limit": import_power_top_limit},
186
+ )
187
+ else:
188
+ if value < 0:
189
+ return ImportOnlyPositive, {}
190
+ elif self.childType == SystemActionType.workMode:
191
+ work_mode = self.childData.data[0]
192
+ total_power_top_limit = ctx.pop("total_power_top_limit")
193
+ total_energy_top_limit = ctx.pop("total_energy_top_limit")
194
+ soc_low_limit = ctx.pop("soc_low_limit")
195
+ if work_mode == 3: # 手动模式
196
+ if self.childData.data[1] in [3, 4]: # 强充或强放
197
+ _, _, power, soc = self.childData.data
198
+ if power <= 0 or power > total_power_top_limit:
199
+ return PowerLimitNum, {
200
+ "low_limit": 0,
201
+ "up_limit": total_power_top_limit,
202
+ }
203
+ if soc > 100 or soc < soc_low_limit:
204
+ return SocLimit, {"low_limit": soc_low_limit}
205
+ elif work_mode == 16: # VPP模式
206
+ vpp_mode = self.childData.data[1]
207
+ if vpp_mode == 1:
208
+ (
209
+ _,
210
+ _,
211
+ active_power,
212
+ reactive_power,
213
+ ) = self.childData.data
214
+ if (
215
+ active_power < -total_power_top_limit
216
+ or active_power > total_power_top_limit
217
+ ):
218
+ return (
219
+ ActivePowerLimitNum,
220
+ {
221
+ "low_limit": -total_power_top_limit,
222
+ "up_limit": total_power_top_limit,
223
+ },
224
+ )
225
+ if (
226
+ reactive_power < -total_power_top_limit
227
+ or reactive_power > total_power_top_limit
228
+ ):
229
+ return (
230
+ ReactivePowerLimitNum,
231
+ {
232
+ "low_limit": -total_power_top_limit,
233
+ "up_limit": total_power_top_limit,
234
+ },
235
+ )
236
+ elif vpp_mode == 2:
237
+ _, _, energy, power = self.childData.data
238
+ if energy < 0 or energy > total_energy_top_limit:
239
+ return (
240
+ EnergyLimit,
241
+ {"up_limit": total_energy_top_limit},
242
+ )
243
+ if power < -total_power_top_limit or power > total_power_top_limit:
244
+ return (
245
+ PowerLimitNum,
246
+ {
247
+ "low_limit": -total_power_top_limit,
248
+ "up_limit": total_power_top_limit,
249
+ },
250
+ )
251
+ elif vpp_mode == 3:
252
+ _, _, soc, power = self.childData.data
253
+ if soc < soc_low_limit or soc > 100:
254
+ return (
255
+ SocLimit,
256
+ {"low_limit": soc_low_limit},
257
+ )
258
+ if power < -total_power_top_limit or power > total_power_top_limit:
259
+ return (
260
+ PowerLimitNum,
261
+ {
262
+ "low_limit": -total_power_top_limit,
263
+ "up_limit": total_power_top_limit,
264
+ },
265
+ )
266
+ elif vpp_mode == 4:
267
+ _, _, power = self.childData.data
268
+ if power < -total_power_top_limit or power > total_power_top_limit:
269
+ return (
270
+ BatteryPowerLimitNum,
271
+ {
272
+ "low_limit": -total_power_top_limit,
273
+ "up_limit": total_power_top_limit,
274
+ },
275
+ )
276
+ elif vpp_mode == 8:
277
+ _, _, pv_power, bms_power = self.childData.data
278
+ if pv_power < 0:
279
+ return PvOnlyGe0, {}
280
+ elif (
281
+ bms_power < -total_power_top_limit
282
+ or bms_power > total_power_top_limit
283
+ ):
284
+ return (
285
+ BatteryPowerLimitNum,
286
+ {
287
+ "low_limit": -total_power_top_limit,
288
+ "up_limit": total_power_top_limit,
289
+ },
290
+ )
291
+ elif vpp_mode == 9:
292
+ _, _, pv_power, bms_power, soc = self.childData.data
293
+ if pv_power < 0:
294
+ return PvOnlyGe0, {}
295
+ if (
296
+ bms_power < -total_power_top_limit
297
+ or bms_power > total_power_top_limit
298
+ ):
299
+ return (
300
+ BatteryPowerLimitNum,
301
+ {
302
+ "low_limit": -total_power_top_limit,
303
+ "up_limit": total_power_top_limit,
304
+ },
305
+ )
306
+ if soc < soc_low_limit or soc > 100:
307
+ return SocLimit, {"low_limit": soc_low_limit}
308
+
309
+
310
+ class EmsActionChildData(ActionChildData):
311
+ data: List[DoControl]
312
+
313
+
314
+ class EmsActionItemData(ActionItemData):
315
+ childType: EmsActionType
316
+ childData: EmsActionChildData
317
+
318
+ def to_text(self, lang, cabinet_type):
319
+ if self.childType == EmsActionType.DoControl:
320
+ message = ""
321
+ for do_info in self.childData.data:
322
+ message += MESSAGE_ENTRY[self.childType][lang].format(
323
+ do_info.DoNumber,
324
+ do_info.DoValue,
325
+ )
326
+ return message
327
+
328
+
329
+ class SmartSceneAction(BaseModel):
330
+ type: ActionType
331
+ data: List[Union[EmsActionItemData, SystemActionItemData]]
332
+
333
+ def to_text(self, lang, cabinet_type):
334
+ return [item.to_text(lang, cabinet_type) for item in self.data]
@@ -2,11 +2,12 @@ import operator
2
2
  from enum import IntEnum, Enum
3
3
  from typing import Optional, List, Union, Any
4
4
 
5
- from pydantic import BaseModel, validator, root_validator
5
+ from pydantic import BaseModel, validator
6
6
 
7
7
  from solax_py_library.device.types.alarm import AlarmLevel
8
8
  from solax_py_library.device.types.device import DeviceType
9
9
  from solax_py_library.smart_scene.constant.message_entry import MESSAGE_ENTRY
10
+ from solax_py_library.smart_scene.exceptions.smart_scene import SocLimit
10
11
 
11
12
 
12
13
  class LogicFunc(IntEnum):
@@ -83,7 +84,12 @@ class ConditionItemChildData(BaseModel):
83
84
  data: List[Any]
84
85
 
85
86
 
86
- class PriceConditionItemData(BaseModel):
87
+ class ConditionItemData(BaseModel):
88
+ def check_param(self, ctx):
89
+ ...
90
+
91
+
92
+ class PriceConditionItemData(ConditionItemData):
87
93
  childType: PriceConditionType
88
94
  childData: ConditionItemChildData
89
95
 
@@ -123,7 +129,7 @@ class PriceConditionItemData(BaseModel):
123
129
  return MESSAGE_ENTRY[self.childType][lang].format(data[0], data[1], data[2])
124
130
 
125
131
 
126
- class SystemConditionItemData(BaseModel):
132
+ class SystemConditionItemData(ConditionItemData):
127
133
  childType: SystemConditionType
128
134
  childData: ConditionItemChildData
129
135
 
@@ -136,8 +142,6 @@ class SystemConditionItemData(BaseModel):
136
142
  }:
137
143
  assert 0 <= value.data[0] <= 100000, ValueError
138
144
  value.data[0] = round(value.data[0], 2) # 功率保留两位小数
139
- elif child_type == SystemConditionType.systemSoc:
140
- assert 5 <= value.data[0] <= 100, ValueError
141
145
  return value
142
146
 
143
147
  def to_text(self, lang, unit):
@@ -155,8 +159,15 @@ class SystemConditionItemData(BaseModel):
155
159
  MESSAGE_ENTRY[str(func.value)][lang], data[0]
156
160
  )
157
161
 
162
+ def check_param(self, ctx):
163
+ soc_low_limit = ctx.pop("soc_low_limit", 5)
164
+ if self.childType == SystemConditionType.systemSoc:
165
+ soc = self.childData.data[0]
166
+ if soc < soc_low_limit or soc > 100:
167
+ return SocLimit.message, {"low_limit": soc_low_limit}
168
+
158
169
 
159
- class CabinetConditionItemData(BaseModel):
170
+ class CabinetConditionItemData(ConditionItemData):
160
171
  childType: CabinetConditionType
161
172
  childData: ConditionItemChildData
162
173
 
@@ -169,8 +180,6 @@ class CabinetConditionItemData(BaseModel):
169
180
  AlarmLevel.NORMAL,
170
181
  AlarmLevel.EMERGENCY,
171
182
  }, ValueError
172
- if child_type == CabinetConditionType.cabinetSoc:
173
- assert 0 <= value.data[0] <= 100, ValueError
174
183
  return value
175
184
 
176
185
  def to_text(self, lang, unit):
@@ -191,13 +200,20 @@ class CabinetConditionItemData(BaseModel):
191
200
  MESSAGE_ENTRY[str(AlarmLevel(data[-1]))][lang],
192
201
  )
193
202
 
203
+ def check_param(self, ctx):
204
+ soc_low_limit = ctx.pop("soc_low_limit", 5)
205
+ if self.childType == CabinetConditionType.cabinetSoc:
206
+ soc = self.childData.data[0]
207
+ if soc < soc_low_limit or soc > 100:
208
+ return SocLimit.message, {"low_limit": soc_low_limit}
209
+
194
210
 
195
- class DateConditionItemData(BaseModel):
211
+ class DateConditionItemData(ConditionItemData):
196
212
  childType: DateConditionType
197
213
  childData: ConditionItemChildData
198
214
 
199
215
  @validator("childData", always=True)
200
- def check_param(cls, value, values):
216
+ def _check_child_data(cls, value, values):
201
217
  child_type = values.get("childType")
202
218
  data = value.data
203
219
  if child_type == DateConditionType.time:
@@ -213,7 +229,7 @@ class DateConditionItemData(BaseModel):
213
229
  return MESSAGE_ENTRY[self.childType][lang] + "/" + self.childData.data[0]
214
230
 
215
231
 
216
- class WeatherConditionItemData(BaseModel):
232
+ class WeatherConditionItemData(ConditionItemData):
217
233
  childType: WeatherConditionType
218
234
  childData: ConditionItemChildData
219
235
 
@@ -272,24 +288,6 @@ class SmartSceneCondition(BaseModel):
272
288
  operation: LogicFunc
273
289
  value: List[ConditionItem]
274
290
 
275
- @root_validator
276
- def _root_check(cls, values):
277
- if values.get("operation") == LogicFunc.OR:
278
- new_value = []
279
- for item in values.get("value"):
280
- if item.type != ConditionType.date:
281
- new_value.append(item)
282
- continue
283
- new_data = []
284
- for date_item in item.data:
285
- if date_item.childType != DateConditionType.duration:
286
- new_data.append(date_item)
287
- item.data = new_data
288
- if item.data:
289
- new_value.append(item)
290
- values["value"] = new_value
291
- return values
292
-
293
291
  def to_text(self, lang, unit):
294
292
  ret = {"operation": [MESSAGE_ENTRY[self.operation.name][lang]]}
295
293
  for v in self.value:
@@ -0,0 +1,83 @@
1
+ from unittest import TestCase
2
+
3
+ from solax_py_library.smart_scene.core.service.check import (
4
+ condition_param_check,
5
+ action_param_check,
6
+ )
7
+ from solax_py_library.smart_scene.types.action import (
8
+ SmartSceneAction,
9
+ ActionType,
10
+ SystemActionType,
11
+ SystemActionItemData,
12
+ SystemActionChildData,
13
+ )
14
+ from solax_py_library.smart_scene.types.condition import (
15
+ SmartSceneCondition,
16
+ ConditionItem,
17
+ ConditionType,
18
+ SystemConditionItemData,
19
+ LogicFunc,
20
+ SystemConditionType,
21
+ ConditionItemChildData,
22
+ CabinetConditionItemData,
23
+ CabinetConditionType,
24
+ ConditionFunc,
25
+ )
26
+
27
+
28
+ class TestCheckFunc(TestCase):
29
+ def test_check_condition_func(self):
30
+ con = SmartSceneCondition(
31
+ operation=LogicFunc.AND,
32
+ value=[
33
+ ConditionItem(
34
+ type=ConditionType.systemCondition,
35
+ data=[
36
+ SystemConditionItemData(
37
+ childType=SystemConditionType.systemSoc,
38
+ childData=ConditionItemChildData(
39
+ data=[101], function=ConditionFunc.EQ
40
+ ),
41
+ ),
42
+ ],
43
+ cabinet=None,
44
+ ),
45
+ ConditionItem(
46
+ type=ConditionType.cabinet,
47
+ data=[
48
+ CabinetConditionItemData(
49
+ childType=CabinetConditionType.cabinetSoc,
50
+ childData=ConditionItemChildData(
51
+ data=[4], function=ConditionFunc.EQ
52
+ ),
53
+ )
54
+ ],
55
+ cabinet=["1", "2"],
56
+ ),
57
+ ],
58
+ )
59
+ ret = condition_param_check(con, ctx={"soc_low_limit": 10})
60
+ assert ret is not None
61
+
62
+ def test_check_action_func(self):
63
+ action = [
64
+ SmartSceneAction(
65
+ type=ActionType.system,
66
+ data=[
67
+ SystemActionItemData(
68
+ childType=SystemActionType.importControl,
69
+ childData=SystemActionChildData(data=[1, -10]),
70
+ )
71
+ ],
72
+ )
73
+ ]
74
+ ret = action_param_check(
75
+ actions=action,
76
+ ctx={
77
+ "cabinet_type": 3,
78
+ "total_power": 1,
79
+ "total_power_except_solar_inv": 1,
80
+ "total_energy": 1,
81
+ },
82
+ )
83
+ assert ret is not None
@@ -1,164 +0,0 @@
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]