solax-py-library 1.0.0.2601__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 (87) hide show
  1. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/PKG-INFO +1 -1
  2. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/pyproject.toml +1 -1
  3. solax_py_library-1.0.0.2901/solax_py_library/device/types/device.py +26 -0
  4. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/constant/message_entry.py +17 -5
  5. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/action/base.py +1 -2
  6. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/cabinet_condition.py +2 -5
  7. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/weather_condition.py +8 -4
  8. solax_py_library-1.0.0.2901/solax_py_library/smart_scene/core/service/check.py +33 -0
  9. solax_py_library-1.0.0.2901/solax_py_library/smart_scene/types/action.py +338 -0
  10. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/types/condition.py +35 -30
  11. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/types/condition_value.py +6 -6
  12. solax_py_library-1.0.0.2901/solax_py_library/test/test_smart_scene/test_check_func.py +83 -0
  13. solax_py_library-1.0.0.2901/solax_py_library/test/test_smart_scene/test_condition.py +66 -0
  14. solax_py_library-1.0.0.2601/solax_py_library/device/types/device.py +0 -16
  15. solax_py_library-1.0.0.2601/solax_py_library/smart_scene/types/action.py +0 -164
  16. solax_py_library-1.0.0.2601/solax_py_library/test/test_smart_scene/test_condition.py +0 -45
  17. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/README.md +0 -0
  18. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/__init__.py +0 -0
  19. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/__init__.py +0 -0
  20. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/constant/__init__.py +0 -0
  21. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/constant/cabinet.py +0 -0
  22. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/constant/inverter_model_info.py +0 -0
  23. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/core/__init__.py +0 -0
  24. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/core/interver/__init__.py +0 -0
  25. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/core/interver/base.py +0 -0
  26. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/types/__init__.py +0 -0
  27. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/types/alarm.py +0 -0
  28. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/types/inverter_config.py +0 -0
  29. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/device/types/modbus_point.py +0 -0
  30. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/exception.py +0 -0
  31. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/__init__.py +0 -0
  32. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/constant/__init__.py +0 -0
  33. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/__init__.py +0 -0
  34. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/action/__init__.py +0 -0
  35. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/action/ems_action.py +0 -0
  36. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/action/system_action.py +0 -0
  37. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/__init__.py +0 -0
  38. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/base.py +0 -0
  39. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/date_condition.py +0 -0
  40. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/price_condition.py +0 -0
  41. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/condition/system_condition.py +0 -0
  42. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/service/__init__.py +0 -0
  43. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/core/service/runner.py +0 -0
  44. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/exceptions/__init__.py +0 -0
  45. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/exceptions/price.py +0 -0
  46. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/exceptions/smart_scene.py +0 -0
  47. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/exceptions/weather.py +0 -0
  48. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/types/__init__.py +0 -0
  49. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/smart_scene/types/smart_scene_content.py +0 -0
  50. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/__init__.py +0 -0
  51. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/constant/__init__.py +0 -0
  52. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/constant/crc_table.py +0 -0
  53. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/core/__init__.py +0 -0
  54. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/core/base_modbus.py +0 -0
  55. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/core/parser.py +0 -0
  56. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/core/snap_shot.py +0 -0
  57. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/exceptions/__init__.py +0 -0
  58. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/exceptions/snap_shot.py +0 -0
  59. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/test/__init__.py +0 -0
  60. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/types/__init__.py +0 -0
  61. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/snap_shot/types/address.py +0 -0
  62. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/test/__init__.py +0 -0
  63. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/test/test_smart_scene/__init__.py +0 -0
  64. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/test/test_utils/__init__.py +0 -0
  65. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/test/test_utils/test_cloud_client.py +0 -0
  66. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/__init__.py +0 -0
  67. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/api/__init__.py +0 -0
  68. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/api/service.py +0 -0
  69. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/__init__.py +0 -0
  70. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/data_adapter/__init__.py +0 -0
  71. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/data_adapter/base.py +0 -0
  72. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/data_adapter/csv.py +0 -0
  73. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/upload_service/__init__.py +0 -0
  74. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/upload_service/base.py +0 -0
  75. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/core/upload_service/ftp.py +0 -0
  76. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/exceptions/__init__.py +0 -0
  77. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/exceptions/upload_error.py +0 -0
  78. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/test/__init__.py +0 -0
  79. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/test/test_ftp.py +0 -0
  80. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/types/__init__.py +0 -0
  81. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/types/client.py +0 -0
  82. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/upload/types/ftp.py +0 -0
  83. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/utils/__init__.py +0 -0
  84. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/utils/cloud_client.py +0 -0
  85. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/utils/common.py +0 -0
  86. {solax_py_library-1.0.0.2601 → solax_py_library-1.0.0.2901}/solax_py_library/utils/struct_util.py +0 -0
  87. {solax_py_library-1.0.0.2601 → 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.2601
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.2601"
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"
@@ -0,0 +1,26 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class DeviceType(IntEnum):
5
+ EMS_TYPE = 100
6
+ PCS_TYPE = 1
7
+ BMS_TYPE = 2 # 电池簇
8
+ ELM_TYPE = 3
9
+ EVC_TYPE = 4
10
+ IO_TYPE = 5
11
+ ESS_TYPE = 6 # 机柜
12
+ CELL_TYPE = 7 # 单体
13
+ AIRCONDITIONER_TYPE = 501
14
+ FIRE_SAFETY_TYPE = 502
15
+ COLD_TYPE = 503
16
+ DEHUMIDIFY_TYPE = 504
17
+
18
+ def __str__(self):
19
+ return {
20
+ DeviceType.PCS_TYPE: "pcs",
21
+ DeviceType.BMS_TYPE: "bms",
22
+ DeviceType.ELM_TYPE: "elm",
23
+ DeviceType.IO_TYPE: "io",
24
+ DeviceType.AIRCONDITIONER_TYPE: "air_conditioner",
25
+ DeviceType.COLD_TYPE: "liquid_cooling_unit",
26
+ }.get(self)
@@ -159,21 +159,33 @@ 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"},
171
171
  "TIPS": {"zh_CN": "状态提醒", "en_US": "State Tips"},
172
172
  "NORMAL": {"zh_CN": "普通告警", "en_US": "Normal alarm"},
173
- "cabinetAlarm": {"zh_CN": "机柜发生{}", "en_US": "cabinet occurs {}"},
173
+ "cabinetAlarm": {
174
+ "zh_CN": "机柜下属({})设备发生{}",
175
+ "en_US": "The equipments({}) under the cabinet occurs {}",
176
+ },
174
177
  "OR": {"zh_CN": "满足任一条件", "en_US": "Meet any of the conditions"},
175
178
  "AND": {"zh_CN": "满足所有条件", "en_US": "Meet all conditions"},
176
179
  "tips_alarm": {"zh_CN": "状态提醒", "en_US": "Alarm tips"},
177
180
  "normal_alarm": {"zh_CN": "普通告警", "en_US": "Normal alarm"},
178
181
  "emergency_alarm": {"zh_CN": "紧急告警", "en_US": "Emergency alarm"},
182
+ "pcs": {"zh_CN": "逆变器", "en_US": "Inverter"},
183
+ "bms": {"zh_CN": "电池", "en_US": "Battery"},
184
+ "elm": {
185
+ "zh_CN": "电表",
186
+ "en_US": "Meter",
187
+ },
188
+ "io": {"zh_CN": "IO模块", "en_US": "IO Module"},
189
+ "air_conditioner": {"zh_CN": "空调", "en_US": "Air conditioner"},
190
+ "liquid_cooling_unit": {"zh_CN": "液冷机组", "en_US": "Liquid Cooling Unit"},
179
191
  }
@@ -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):
@@ -6,7 +6,6 @@ from solax_py_library.smart_scene.types.condition import (
6
6
  CabinetConditionType,
7
7
  ConditionType,
8
8
  )
9
- from solax_py_library.device.types.alarm import AlarmLevel
10
9
  from solax_py_library.smart_scene.types.condition_value import CabinetValue
11
10
 
12
11
 
@@ -15,9 +14,7 @@ class CabinetCondition(BaseCondition):
15
14
 
16
15
  def __init__(self, update_value_function, **kwargs):
17
16
  super().__init__(update_value_function, **kwargs)
18
- self.value = defaultdict(
19
- lambda: CabinetValue
20
- )
17
+ self.value = defaultdict(lambda: CabinetValue)
21
18
 
22
19
  def meet_func(self, data: CabinetConditionItemData, ctx):
23
20
  if not self.value:
@@ -42,6 +39,6 @@ class CabinetCondition(BaseCondition):
42
39
  alarm_info = cabinet_value.alarm_info(device_type)
43
40
  if not alarm_info:
44
41
  continue
45
- if alarm_info[alarm_type-1] is True:
42
+ if alarm_info[alarm_type - 1] is True:
46
43
  return True
47
44
  return False
@@ -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,10 +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
+ from solax_py_library.device.types.device import DeviceType
8
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
9
11
 
10
12
 
11
13
  class LogicFunc(IntEnum):
@@ -82,7 +84,12 @@ class ConditionItemChildData(BaseModel):
82
84
  data: List[Any]
83
85
 
84
86
 
85
- class PriceConditionItemData(BaseModel):
87
+ class ConditionItemData(BaseModel):
88
+ def check_param(self, ctx):
89
+ ...
90
+
91
+
92
+ class PriceConditionItemData(ConditionItemData):
86
93
  childType: PriceConditionType
87
94
  childData: ConditionItemChildData
88
95
 
@@ -122,7 +129,7 @@ class PriceConditionItemData(BaseModel):
122
129
  return MESSAGE_ENTRY[self.childType][lang].format(data[0], data[1], data[2])
123
130
 
124
131
 
125
- class SystemConditionItemData(BaseModel):
132
+ class SystemConditionItemData(ConditionItemData):
126
133
  childType: SystemConditionType
127
134
  childData: ConditionItemChildData
128
135
 
@@ -135,8 +142,6 @@ class SystemConditionItemData(BaseModel):
135
142
  }:
136
143
  assert 0 <= value.data[0] <= 100000, ValueError
137
144
  value.data[0] = round(value.data[0], 2) # 功率保留两位小数
138
- elif child_type == SystemConditionType.systemSoc:
139
- assert 5 <= value.data[0] <= 100, ValueError
140
145
  return value
141
146
 
142
147
  def to_text(self, lang, unit):
@@ -154,8 +159,15 @@ class SystemConditionItemData(BaseModel):
154
159
  MESSAGE_ENTRY[str(func.value)][lang], data[0]
155
160
  )
156
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
+
157
169
 
158
- class CabinetConditionItemData(BaseModel):
170
+ class CabinetConditionItemData(ConditionItemData):
159
171
  childType: CabinetConditionType
160
172
  childData: ConditionItemChildData
161
173
 
@@ -168,8 +180,6 @@ class CabinetConditionItemData(BaseModel):
168
180
  AlarmLevel.NORMAL,
169
181
  AlarmLevel.EMERGENCY,
170
182
  }, ValueError
171
- if child_type == CabinetConditionType.cabinetSoc:
172
- assert 0 <= value.data[0] <= 100, ValueError
173
183
  return value
174
184
 
175
185
  def to_text(self, lang, unit):
@@ -181,16 +191,29 @@ class CabinetConditionItemData(BaseModel):
181
191
  )
182
192
  elif self.childType == CabinetConditionType.cabinetAlarm:
183
193
  return MESSAGE_ENTRY[self.childType][lang].format(
184
- MESSAGE_ENTRY[str(AlarmLevel(data[0]))][lang], lang
194
+ ",".join(
195
+ [
196
+ MESSAGE_ENTRY[str(DeviceType(device_type))][lang]
197
+ for device_type in data[:-1]
198
+ ]
199
+ ),
200
+ MESSAGE_ENTRY[str(AlarmLevel(data[-1]))][lang],
185
201
  )
186
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
+
187
210
 
188
- class DateConditionItemData(BaseModel):
211
+ class DateConditionItemData(ConditionItemData):
189
212
  childType: DateConditionType
190
213
  childData: ConditionItemChildData
191
214
 
192
215
  @validator("childData", always=True)
193
- def check_param(cls, value, values):
216
+ def _check_child_data(cls, value, values):
194
217
  child_type = values.get("childType")
195
218
  data = value.data
196
219
  if child_type == DateConditionType.time:
@@ -206,7 +229,7 @@ class DateConditionItemData(BaseModel):
206
229
  return MESSAGE_ENTRY[self.childType][lang] + "/" + self.childData.data[0]
207
230
 
208
231
 
209
- class WeatherConditionItemData(BaseModel):
232
+ class WeatherConditionItemData(ConditionItemData):
210
233
  childType: WeatherConditionType
211
234
  childData: ConditionItemChildData
212
235
 
@@ -265,24 +288,6 @@ class SmartSceneCondition(BaseModel):
265
288
  operation: LogicFunc
266
289
  value: List[ConditionItem]
267
290
 
268
- @root_validator
269
- def _root_check(cls, values):
270
- if values.get("operation") == LogicFunc.OR:
271
- new_value = []
272
- for item in values.get("value"):
273
- if item.type != ConditionType.date:
274
- new_value.append(item)
275
- continue
276
- new_data = []
277
- for date_item in item.data:
278
- if date_item.childType != DateConditionType.duration:
279
- new_data.append(date_item)
280
- item.data = new_data
281
- if item.data:
282
- new_value.append(item)
283
- values["value"] = new_value
284
- return values
285
-
286
291
  def to_text(self, lang, unit):
287
292
  ret = {"operation": [MESSAGE_ENTRY[self.operation.name][lang]]}
288
293
  for v in self.value:
@@ -7,12 +7,12 @@ from solax_py_library.device.types.device import DeviceType
7
7
 
8
8
  class CabinetValue(BaseModel):
9
9
  soc: int = None
10
- cabinet_alarm: List[bool] = Field(default_factory=lambda :[False, False, False])
11
- pcs_alarm: List[bool] = Field(default_factory=lambda :[False, False, False])
12
- io_alarm: List[bool] = Field(default_factory=lambda :[False, False, False])
13
- bms_alarm: List[bool] = Field(default_factory=lambda :[False, False, False])
14
- air_alarm: List[bool] = Field(default_factory=lambda :[False, False, False])
15
- liquid_alarm: List[bool] = Field(default_factory=lambda :[False, False, False])
10
+ cabinet_alarm: List[bool] = Field(default_factory=lambda: [False, False, False])
11
+ pcs_alarm: List[bool] = Field(default_factory=lambda: [False, False, False])
12
+ io_alarm: List[bool] = Field(default_factory=lambda: [False, False, False])
13
+ bms_alarm: List[bool] = Field(default_factory=lambda: [False, False, False])
14
+ air_alarm: List[bool] = Field(default_factory=lambda: [False, False, False])
15
+ liquid_alarm: List[bool] = Field(default_factory=lambda: [False, False, False])
16
16
 
17
17
  def alarm_info(self, device_type):
18
18
  if device_type == DeviceType.EMS_TYPE: