solax-py-library 1.0.0.2602__tar.gz → 1.0.0.2901__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.2602 → solax_py_library-1.0.0.2901}/PKG-INFO +1 -1
  2. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/pyproject.toml +1 -1
  3. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/constant/message_entry.py +4 -4
  4. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/action/base.py +1 -2
  5. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/weather_condition.py +8 -4
  6. solax_py_library-1.0.0.2901/solax_py_library/smart_scene/core/service/check.py +33 -0
  7. solax_py_library-1.0.0.2901/solax_py_library/smart_scene/types/action.py +338 -0
  8. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/types/condition.py +27 -29
  9. solax_py_library-1.0.0.2901/solax_py_library/test/test_smart_scene/test_check_func.py +83 -0
  10. solax_py_library-1.0.0.2602/solax_py_library/smart_scene/types/action.py +0 -164
  11. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/README.md +0 -0
  12. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/__init__.py +0 -0
  13. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/__init__.py +0 -0
  14. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/constant/__init__.py +0 -0
  15. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/constant/cabinet.py +0 -0
  16. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/constant/inverter_model_info.py +0 -0
  17. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/core/__init__.py +0 -0
  18. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/core/interver/__init__.py +0 -0
  19. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/core/interver/base.py +0 -0
  20. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/types/__init__.py +0 -0
  21. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/types/alarm.py +0 -0
  22. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/types/device.py +0 -0
  23. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/types/inverter_config.py +0 -0
  24. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/device/types/modbus_point.py +0 -0
  25. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/exception.py +0 -0
  26. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/__init__.py +0 -0
  27. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/constant/__init__.py +0 -0
  28. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/__init__.py +0 -0
  29. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/action/__init__.py +0 -0
  30. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/action/ems_action.py +0 -0
  31. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/action/system_action.py +0 -0
  32. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/__init__.py +0 -0
  33. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/base.py +0 -0
  34. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/cabinet_condition.py +0 -0
  35. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/date_condition.py +0 -0
  36. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/price_condition.py +0 -0
  37. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/system_condition.py +0 -0
  38. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/service/__init__.py +0 -0
  39. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/service/runner.py +0 -0
  40. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/exceptions/__init__.py +0 -0
  41. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/exceptions/price.py +0 -0
  42. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/exceptions/smart_scene.py +0 -0
  43. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/exceptions/weather.py +0 -0
  44. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/types/__init__.py +0 -0
  45. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/types/condition_value.py +0 -0
  46. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/types/smart_scene_content.py +0 -0
  47. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/__init__.py +0 -0
  48. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/constant/__init__.py +0 -0
  49. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/constant/crc_table.py +0 -0
  50. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/core/__init__.py +0 -0
  51. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/core/base_modbus.py +0 -0
  52. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/core/parser.py +0 -0
  53. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/core/snap_shot.py +0 -0
  54. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/exceptions/__init__.py +0 -0
  55. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/exceptions/snap_shot.py +0 -0
  56. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/test/__init__.py +0 -0
  57. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/types/__init__.py +0 -0
  58. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/types/address.py +0 -0
  59. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/test/__init__.py +0 -0
  60. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/test/test_smart_scene/__init__.py +0 -0
  61. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/test/test_smart_scene/test_condition.py +0 -0
  62. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/test/test_utils/__init__.py +0 -0
  63. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/test/test_utils/test_cloud_client.py +0 -0
  64. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/__init__.py +0 -0
  65. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/api/__init__.py +0 -0
  66. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/api/service.py +0 -0
  67. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/__init__.py +0 -0
  68. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/data_adapter/__init__.py +0 -0
  69. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/data_adapter/base.py +0 -0
  70. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/data_adapter/csv.py +0 -0
  71. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/upload_service/__init__.py +0 -0
  72. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/upload_service/base.py +0 -0
  73. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/upload_service/ftp.py +0 -0
  74. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/exceptions/__init__.py +0 -0
  75. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/exceptions/upload_error.py +0 -0
  76. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/test/__init__.py +0 -0
  77. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/test/test_ftp.py +0 -0
  78. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/types/__init__.py +0 -0
  79. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/types/client.py +0 -0
  80. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/upload/types/ftp.py +0 -0
  81. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/utils/__init__.py +0 -0
  82. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/utils/cloud_client.py +0 -0
  83. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/utils/common.py +0 -0
  84. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/solax_py_library/utils/struct_util.py +0 -0
  85. {solax_py_library-1.0.0.2602 → solax_py_library-1.0.0.2901}/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.2602
3
+ Version: 1.0.0.2901
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.2602"
3
+ version = "1.0.0.2901"
4
4
  description = "some common tool"
5
5
  authors = ["shenlvyu <13296718439@163.com>", "x5m8944pjl <371630856@qq.com>"]
6
6
  readme = "README.md"
@@ -159,12 +159,12 @@ MESSAGE_ENTRY = {
159
159
  "duration": {"zh_CN": "持续: {}秒", "en_US": "last: {} seconds"},
160
160
  "systemSoc": {"zh_CN": "系统soc {} {}%", "en_US": "system soc {} {}%"},
161
161
  "systemImportPower": {
162
- "zh_CN": "电价买电功率 {} {}KW",
163
- "en_US": "system soc {} {}KW",
162
+ "zh_CN": "电网买电功率 {} {}KW",
163
+ "en_US": "Grid import power {} {}KW",
164
164
  },
165
165
  "systemExportPower": {
166
- "zh_CN": "电价馈电功率 {} {}KW",
167
- "en_US": "system soc {} {}KW",
166
+ "zh_CN": "电网馈电功率 {} {}KW",
167
+ "en_US": "Grid export power {} {}KW",
168
168
  },
169
169
  "cabinetSoc": {"zh_CN": "机柜soc {} {}%", "en_US": "cabinet soc {} {}%"},
170
170
  "EMERGENCY": {"zh_CN": "紧急告警", "en_US": "Emergency alarm"},
@@ -1,6 +1,5 @@
1
1
  class BaseAction(object):
2
- def __init__(self, smart_scene_service, do_funcs, **kwargs):
3
- self.smart_scene_service = smart_scene_service
2
+ def __init__(self, do_funcs, **kwargs):
4
3
  self.do_func_map = do_funcs
5
4
 
6
5
  def do_func(self, scene_id, data):
@@ -21,10 +21,14 @@ class WeatherCondition(BaseCondition):
21
21
  function_value = child_data.function
22
22
  data_value = child_data.data
23
23
  nearest_time, right_time = get_rounded_times()
24
- if nearest_time not in self.value["timeList"]:
25
- time_now = right_time
26
- else:
27
- time_now = nearest_time
24
+ if (
25
+ nearest_time not in self.value["timeList"]
26
+ and right_time not in self.value["timeList"]
27
+ ):
28
+ return False
29
+ time_now = (
30
+ right_time if nearest_time not in self.value["timeList"] else nearest_time
31
+ )
28
32
  index = self.value["timeList"].index(time_now)
29
33
  if child_type == WeatherConditionType.irradiance:
30
34
  return self.meet_func_irradiance(function_value, data_value, index)
@@ -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,338 @@
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
+ total_power_top_limit = ctx.pop("total_power_top_limit", None)
178
+ switch = self.childData.data[0]
179
+ if not switch:
180
+ return
181
+ value = self.childData.data[-1]
182
+ if import_power_top_limit is not None:
183
+ if value > total_power_top_limit or value < 0:
184
+ return (
185
+ ImportLimitNum.message,
186
+ {"up_limit": import_power_top_limit},
187
+ )
188
+ else:
189
+ if value < 0:
190
+ return ImportOnlyPositive.message, {}
191
+ elif self.childType == SystemActionType.workMode:
192
+ work_mode = self.childData.data[0]
193
+ total_power_top_limit = ctx.pop("total_power_top_limit")
194
+ total_energy_top_limit = ctx.pop("total_energy_top_limit")
195
+ soc_low_limit = ctx.pop("soc_low_limit")
196
+ if work_mode == 3: # 手动模式
197
+ if self.childData.data[1] in [3, 4]: # 强充或强放
198
+ _, _, power, soc = self.childData.data
199
+ if power <= 0 or power > total_power_top_limit:
200
+ return (
201
+ PowerLimitNum.message,
202
+ {"low_limit": 0, "up_limit": total_power_top_limit},
203
+ )
204
+ if soc > 100 or soc < soc_low_limit:
205
+ return (
206
+ SocLimit.message,
207
+ {"low_limit": soc_low_limit},
208
+ )
209
+ elif work_mode == 16: # VPP模式
210
+ vpp_mode = self.childData.data[1]
211
+ if vpp_mode == 1:
212
+ (
213
+ _,
214
+ _,
215
+ active_power,
216
+ reactive_power,
217
+ ) = self.childData.data
218
+ if (
219
+ active_power < -total_power_top_limit
220
+ or active_power > total_power_top_limit
221
+ ):
222
+ return (
223
+ ActivePowerLimitNum.message,
224
+ {
225
+ "low_limit": -total_power_top_limit,
226
+ "up_limit": total_power_top_limit,
227
+ },
228
+ )
229
+ if (
230
+ reactive_power < -total_power_top_limit
231
+ or reactive_power > total_power_top_limit
232
+ ):
233
+ return (
234
+ ReactivePowerLimitNum.message,
235
+ {
236
+ "low_limit": -total_power_top_limit,
237
+ "up_limit": total_power_top_limit,
238
+ },
239
+ )
240
+ elif vpp_mode == 2:
241
+ _, _, energy, power = self.childData.data
242
+ if energy < 0 or energy > total_energy_top_limit:
243
+ return (
244
+ EnergyLimit.message,
245
+ {"up_limit": total_energy_top_limit},
246
+ )
247
+ if power < -total_power_top_limit or power > total_power_top_limit:
248
+ return (
249
+ PowerLimitNum.message,
250
+ {
251
+ "low_limit": -total_power_top_limit,
252
+ "up_limit": total_power_top_limit,
253
+ },
254
+ )
255
+ elif vpp_mode == 3:
256
+ _, _, soc, power = self.childData.data
257
+ if soc < soc_low_limit or soc > 100:
258
+ return (
259
+ SocLimit.message,
260
+ {"low_limit": soc_low_limit},
261
+ )
262
+ if power < -total_power_top_limit or power > total_power_top_limit:
263
+ return (
264
+ PowerLimitNum.message,
265
+ {
266
+ "low_limit": -total_power_top_limit,
267
+ "up_limit": total_power_top_limit,
268
+ },
269
+ )
270
+ elif vpp_mode == 4:
271
+ _, _, power = self.childData.data
272
+ if power < -total_power_top_limit or power > total_power_top_limit:
273
+ return (
274
+ BatteryPowerLimitNum.message,
275
+ {
276
+ "low_limit": -total_power_top_limit,
277
+ "up_limit": total_power_top_limit,
278
+ },
279
+ )
280
+ elif vpp_mode == 8:
281
+ _, _, pv_power, bms_power = self.childData.data
282
+ if pv_power < 0:
283
+ return (PvOnlyGe0.message, {})
284
+ elif (
285
+ bms_power < -total_power_top_limit
286
+ or bms_power > total_power_top_limit
287
+ ):
288
+ return (
289
+ BatteryPowerLimitNum.message,
290
+ {
291
+ "low_limit": -total_power_top_limit,
292
+ "up_limit": total_power_top_limit,
293
+ },
294
+ )
295
+ elif vpp_mode == 9:
296
+ _, _, pv_power, bms_power, soc = self.childData.data
297
+ if pv_power < 0:
298
+ return (PvOnlyGe0.message, {})
299
+ if (
300
+ bms_power < -total_power_top_limit
301
+ or bms_power > total_power_top_limit
302
+ ):
303
+ return (
304
+ BatteryPowerLimitNum.message,
305
+ {
306
+ "low_limit": -total_power_top_limit,
307
+ "up_limit": total_power_top_limit,
308
+ },
309
+ )
310
+ if soc < soc_low_limit or soc > 100:
311
+ return SocLimit.message, {"low_limit": soc_low_limit}
312
+
313
+
314
+ class EmsActionChildData(ActionChildData):
315
+ data: List[DoControl]
316
+
317
+
318
+ class EmsActionItemData(ActionItemData):
319
+ childType: EmsActionType
320
+ childData: EmsActionChildData
321
+
322
+ def to_text(self, lang, cabinet_type):
323
+ if self.childType == EmsActionType.DoControl:
324
+ message = ""
325
+ for do_info in self.childData.data:
326
+ message += MESSAGE_ENTRY[self.childType][lang].format(
327
+ do_info.DoNumber,
328
+ do_info.DoValue,
329
+ )
330
+ return message
331
+
332
+
333
+ class SmartSceneAction(BaseModel):
334
+ type: ActionType
335
+ data: List[Union[EmsActionItemData, SystemActionItemData]]
336
+
337
+ def to_text(self, lang, cabinet_type):
338
+ 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]