solax-py-library 1.0.0.2507__py3-none-any.whl → 1.0.0.2509__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ from .base import BaseCondition
2
+ from .cabinet_condition import CabinetCondition
3
+ from .date_condition import DateCondition
4
+ from .weather_condition import WeatherCondition
5
+ from .price_condition import EleSellPriceCondition, ElsBuyPriceCondition
6
+ from .system_condition import SystemCondition
7
+
8
+
9
+ __all__ = [
10
+ "BaseCondition",
11
+ "CabinetCondition",
12
+ "DateCondition",
13
+ "WeatherCondition",
14
+ "ElsBuyPriceCondition",
15
+ "EleSellPriceCondition",
16
+ "SystemCondition",
17
+ ]
@@ -1,4 +1,6 @@
1
1
  class BaseCondition(object):
2
+ condition_type = None
3
+
2
4
  def __init__(self, update_value_function, **kwargs):
3
5
  self.value = {}
4
6
  if not callable(update_value_function):
@@ -4,11 +4,14 @@ from solax_py_library.smart_scene.core.condition.base import BaseCondition
4
4
  from solax_py_library.smart_scene.types.condition import (
5
5
  CabinetConditionItemData,
6
6
  CabinetConditionType,
7
+ ConditionType,
7
8
  )
8
9
  from solax_py_library.device.types.alarm import AlarmLevel
9
10
 
10
11
 
11
12
  class CabinetCondition(BaseCondition):
13
+ condition_type = ConditionType.cabinet
14
+
12
15
  def __init__(self, update_value_function, **kwargs):
13
16
  super().__init__(update_value_function, **kwargs)
14
17
  self.value = defaultdict(
@@ -4,10 +4,13 @@ from solax_py_library.smart_scene.core.condition.base import BaseCondition
4
4
  from solax_py_library.smart_scene.types.condition import (
5
5
  DateConditionItemData,
6
6
  DateConditionType,
7
+ ConditionType,
7
8
  )
8
9
 
9
10
 
10
11
  class DateCondition(BaseCondition):
12
+ condition_type = ConditionType.date
13
+
11
14
  def __init__(self, update_value_function, **kwargs):
12
15
  super().__init__(update_value_function, **kwargs)
13
16
 
@@ -6,6 +6,7 @@ from solax_py_library.smart_scene.types.condition import (
6
6
  PriceConditionType,
7
7
  SmartSceneUnit,
8
8
  ConditionFunc,
9
+ ConditionType,
9
10
  )
10
11
  from solax_py_library.utils.time_util import (
11
12
  trans_str_time_to_index,
@@ -88,12 +89,16 @@ class ElePriceCondition(BaseCondition):
88
89
 
89
90
 
90
91
  class EleSellPriceCondition(ElePriceCondition):
92
+ condition_type = ConditionType.sellingPrice
93
+
91
94
  def __init__(self, update_value_function, **kwargs):
92
95
  super().__init__(update_value_function, **kwargs)
93
96
  self.buy = False
94
97
 
95
98
 
96
99
  class ElsBuyPriceCondition(ElePriceCondition):
100
+ condition_type = ConditionType.buyingPrice
101
+
97
102
  def __init__(self, update_value_function, **kwargs):
98
103
  super().__init__(update_value_function, **kwargs)
99
104
  self.buy = True
@@ -2,10 +2,13 @@ from solax_py_library.smart_scene.core.condition.base import BaseCondition
2
2
  from solax_py_library.smart_scene.types.condition import (
3
3
  SystemConditionItemData,
4
4
  SystemConditionType,
5
+ ConditionType,
5
6
  )
6
7
 
7
8
 
8
9
  class SystemCondition(BaseCondition):
10
+ condition_type = ConditionType.systemCondition
11
+
9
12
  def __init__(self, update_value_function, **kwargs):
10
13
  super().__init__(update_value_function, **kwargs)
11
14
  self.grid_active_power = None
@@ -2,11 +2,14 @@ from solax_py_library.smart_scene.core.condition.base import BaseCondition
2
2
  from solax_py_library.smart_scene.types.condition import (
3
3
  WeatherConditionItemData,
4
4
  WeatherConditionType,
5
+ ConditionType,
5
6
  )
6
7
  from solax_py_library.utils.time_util import get_rounded_times
7
8
 
8
9
 
9
10
  class WeatherCondition(BaseCondition):
11
+ condition_type = ConditionType.weather
12
+
10
13
  def __init__(self, update_value_function, **kwargs):
11
14
  super().__init__(update_value_function, **kwargs)
12
15
 
@@ -0,0 +1,3 @@
1
+ from run import SmartSceneRunner
2
+
3
+ __all__ = ["SmartSceneRunner"]
@@ -0,0 +1,145 @@
1
+ import json
2
+ import traceback
3
+ from datetime import datetime
4
+ from typing import List, Set
5
+
6
+ from solax_py_library.smart_scene.types.action import (
7
+ SmartSceneAction,
8
+ )
9
+ from solax_py_library.smart_scene.types.condition import (
10
+ ConditionType,
11
+ RepeatFunc,
12
+ LogicFunc,
13
+ SmartSceneCondition,
14
+ DateConditionType,
15
+ ConditionItem,
16
+ )
17
+ from solax_py_library.smart_scene.types.smart_scene_content import (
18
+ SmartSceneContent,
19
+ SmartSceneOtherInfo,
20
+ )
21
+ from tornado.log import app_log
22
+
23
+
24
+ class SmartSceneRunner:
25
+ def __init__(self, condition_class_map, action_class_map):
26
+ self.condition_class_map = condition_class_map
27
+ self.action_class_map = action_class_map
28
+
29
+ @staticmethod
30
+ def _handle_repeat_info(scene_content: SmartSceneContent, once_flag):
31
+ # once_flag: 是否执行过
32
+ if scene_content.repeatFunction == RepeatFunc.ONCE:
33
+ return False if once_flag else True
34
+ week_list = scene_content.get_weekday_index()
35
+ if datetime.now().weekday() + 1 in week_list:
36
+ return True
37
+ return False
38
+
39
+ def smart_scene_handle(self, scene_instance):
40
+ other_info = SmartSceneOtherInfo.parse_raw(scene_instance.other)
41
+ try:
42
+ scene_data = SmartSceneContent.parse_raw(scene_instance.content)
43
+
44
+ # repeat,是否执行
45
+ if not self._handle_repeat_info(scene_data, other_info.once_flag):
46
+ app_log.info(
47
+ f"场景名称: {scene_data.name}, 场景ID: {scene_instance.scene_id} 不符合重复条件,不执行"
48
+ )
49
+ return
50
+ scene_id = scene_instance.scene_id
51
+ app_log.info(
52
+ f"场景名称: {scene_data.name}, 场景ID: {scene_id}, 判断类型: {scene_data.conditions.operation}:"
53
+ )
54
+
55
+ # 条件判断
56
+ result = self._handle_conditions(
57
+ scene_data.conditions.operation,
58
+ scene_data.conditions,
59
+ other_info,
60
+ )
61
+
62
+ # 动作执行
63
+ new_exec_number = scene_instance.exec_number
64
+ if result:
65
+ self._handle_action(scene_id, scene_data.thenActions, log_prefix="THEN")
66
+ new_exec_number = 1
67
+ other_info.once_flag = True
68
+ elif scene_instance.exec_number == 1 and scene_data.elseThenActions:
69
+ self._handle_action(
70
+ scene_id, scene_data.elseThenActions, log_prefix="ELSE THEN"
71
+ )
72
+ new_exec_number = 0
73
+ other_info.duration_times = 0
74
+
75
+ app_log.info(f"{scene_id} 执行完毕\n")
76
+ scene_instance.exec_number = new_exec_number
77
+ scene_instance.other = json.dumps(other_info.dict())
78
+ return scene_instance
79
+ except Exception:
80
+ app_log.error(
81
+ f"{scene_instance.scene_id} 执行智能场景异常 {traceback.format_exc()}"
82
+ )
83
+
84
+ def _handle_action(
85
+ self, scene_id, actions: List[SmartSceneAction], log_prefix="THEN"
86
+ ):
87
+ if not actions:
88
+ return
89
+ for action_info in actions:
90
+ action_class = self.action_class_map[action_info.type]
91
+ for child_info in action_info.data:
92
+ ret = action_class.do_func(scene_id=scene_id, data=child_info)
93
+ app_log.info(f"{log_prefix}条件 {child_info} 执行结果: {ret}")
94
+
95
+ def _handle_conditions(
96
+ self,
97
+ operation: LogicFunc,
98
+ conditions: SmartSceneCondition,
99
+ other_info: SmartSceneOtherInfo,
100
+ ):
101
+ need_duration_times = conditions.get_duration_info()
102
+ if not self._check_conditions(operation, conditions):
103
+ other_info.duration_times = 0
104
+ return False
105
+ if other_info.duration_times < need_duration_times:
106
+ other_info.duration_times += 1
107
+ app_log.info(
108
+ f"need times: {need_duration_times}, current times: {other_info.duration_times}"
109
+ )
110
+ return other_info.duration_times >= need_duration_times
111
+
112
+ def _check_conditions(self, operation, conditions):
113
+ ret_list = []
114
+ for cond in conditions.value:
115
+ parent_type = cond.type # 父条件类型 (日期、天气、电价)
116
+ condition_class = self.condition_class_map[parent_type]
117
+ ctx = self._build_condition_ctx(cond)
118
+ for child_info in cond.data:
119
+ if child_info.childType == DateConditionType.duration:
120
+ continue
121
+ ret = condition_class.meet_func(data=child_info, ctx=ctx)
122
+ app_log.info(f"IF条件 {child_info} 判断结果: {ret}")
123
+ ret_list.append(ret)
124
+ # 如果该条件判断不满足,并且客户设置为and,则无需继续判断,返回失败
125
+ if not ret and operation == LogicFunc.AND:
126
+ return False
127
+ # 如果条件判断满足,并且客户设置为or,则无需继续判断,返回成功
128
+ if ret and operation == LogicFunc.OR:
129
+ return True
130
+ if (operation == LogicFunc.AND and all(ret_list)) or (
131
+ operation == LogicFunc.OR and any(ret_list)
132
+ ):
133
+ return True
134
+ return False
135
+
136
+ @staticmethod
137
+ def _build_condition_ctx(condition_info: ConditionItem):
138
+ ctx = {}
139
+ if condition_info.type == ConditionType.cabinet:
140
+ ctx["cabinet"] = condition_info.cabinet
141
+ return ctx
142
+
143
+ def update_all_condition_data(self, condition_types: Set[ConditionType]):
144
+ for cond_type in condition_types:
145
+ self.condition_class_map[cond_type].update_value()
@@ -0,0 +1,7 @@
1
+ from price import ElectricityPriceFailure
2
+ from weather import WeatherFailure
3
+
4
+ __all__ = [
5
+ "ElectricityPriceFailure",
6
+ "WeatherFailure",
7
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: solax-py-library
3
- Version: 1.0.0.2507
3
+ Version: 1.0.0.2509
4
4
  Summary: some common tool
5
5
  Author: shenlvyu
6
6
  Author-email: 13296718439@163.com
@@ -19,16 +19,16 @@ solax_py_library/smart_scene/core/action/__init__.py,sha256=47DEQpj8HBSa-_TImW-5
19
19
  solax_py_library/smart_scene/core/action/base.py,sha256=vSC3rL9qfPy41Nlds_0QcTLMbf4n-P18XUBJ3oYiNGk,237
20
20
  solax_py_library/smart_scene/core/action/ems_action.py,sha256=tasL7pUJbnmrEEokAa9Mug5eUFvHuFKGYZKNvNMdrBs,610
21
21
  solax_py_library/smart_scene/core/action/system_action.py,sha256=mVLTg9pZ7tDayLbr3I92a33snA2AGT3meUdm_XYDQEc,10839
22
- solax_py_library/smart_scene/core/condition/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- solax_py_library/smart_scene/core/condition/base.py,sha256=SIxUieoHkiuRQvmUCkI22knFzOlHkmAcbsuOWJeQGHk,429
24
- solax_py_library/smart_scene/core/condition/cabinet_condition.py,sha256=YhjmwgFBlugkMeRz6zfu1bV_Q-2LAbq5vjHfZgewSXE,1518
25
- solax_py_library/smart_scene/core/condition/date_condition.py,sha256=8dbYhftUzGKQJkDqIkV_hjf4PIhKJrUCo9fv-_93sl8,855
26
- solax_py_library/smart_scene/core/condition/price_condition.py,sha256=kAIkg5VxTOd5gTRHzqU23ZgFYSkcvF_haqzVKA-uDZc,3768
27
- solax_py_library/smart_scene/core/condition/system_condition.py,sha256=Pstq_y6fdwh7a39q8JZuabymFeJx5AH5Xjj4MZz4or0,1309
28
- solax_py_library/smart_scene/core/condition/weather_condition.py,sha256=jn8gbQ2ITvwNIlimib7zv-j5BvslFfJebJoftCGECeE,2173
29
- solax_py_library/smart_scene/core/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- solax_py_library/smart_scene/core/service/smart_scene_service.py,sha256=wbC4Y0NK7vQRgJXrHCptjlb7r_C5PHLEHyGU1oaFNjw,15704
31
- solax_py_library/smart_scene/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ solax_py_library/smart_scene/core/condition/__init__.py,sha256=1nN-N52Oq7LKdn6ApKGtSZq5fB1qJzJq8BOKOumfQvY,475
23
+ solax_py_library/smart_scene/core/condition/base.py,sha256=CMVHRuPrVpCjrc5WLHFU9nc-jrE9RLCbXfzBiNkURWE,456
24
+ solax_py_library/smart_scene/core/condition/cabinet_condition.py,sha256=HEVifO3rHk2ElfSCEB0MzVdfrFWlkwqjt6tFhtJ8CLY,1581
25
+ solax_py_library/smart_scene/core/condition/date_condition.py,sha256=-mzTgahn1IphYBBhT5dc_VeVrmgoIUCjhzVqrDJUqLc,915
26
+ solax_py_library/smart_scene/core/condition/price_condition.py,sha256=mzUnrMuG0DHCxaQQe7-rmE_vra9U6vd-1AYDKkbPPUw,3884
27
+ solax_py_library/smart_scene/core/condition/system_condition.py,sha256=g-5LSUAnu7JESUlTRbJ3MKm9cEMzofDZzNNm7_RSy10,1380
28
+ solax_py_library/smart_scene/core/condition/weather_condition.py,sha256=OiKye007PZnocnmcrNQD6ObOEF7VyaYkdqz6D_2lNok,2236
29
+ solax_py_library/smart_scene/core/service/__init__.py,sha256=4qoUm6NPOApn9hamW20oh5bL5cDo1AVjQ9qZtQVf6zM,65
30
+ solax_py_library/smart_scene/core/service/run.py,sha256=9hevnzI0wtp4Szxo_EQFeMm0_-iuq5gdmvjbgmyKOz0,5707
31
+ solax_py_library/smart_scene/exceptions/__init__.py,sha256=lPMBWT7DDAsnvz_g2PX8jgEqr9bOrPa2x3UZ0SKvO0w,145
32
32
  solax_py_library/smart_scene/exceptions/price.py,sha256=3bnY6JzeEskUoXVzEs8bpg6hQzgbinBKY4GP4hBITWU,152
33
33
  solax_py_library/smart_scene/exceptions/smart_scene.py,sha256=lIwzjdpvtrAub4lyTQRD3h2sM95vF6gpUy3Nmq1w1k0,1778
34
34
  solax_py_library/smart_scene/exceptions/weather.py,sha256=bJl1VwiIXEpLQ9VjlVrDoTAIMFqVZdRCas7dtR7eAJc,133
@@ -73,6 +73,6 @@ solax_py_library/utils/cloud_client.py,sha256=5dZrc5fzrNFSXqTPZd7oHt-Y9Jj6RCigB7
73
73
  solax_py_library/utils/common.py,sha256=bfnZcX9uM-PjJrYAFv1UMmZgt6bGR7MaOd7jRPNHGxw,1238
74
74
  solax_py_library/utils/struct_util.py,sha256=4SKx5IyAke88PGHPHDK3OEDtyGHdapGoQ1BnGR0F2ts,913
75
75
  solax_py_library/utils/time_util.py,sha256=bY5kj9dmyOuLEQ6uYGQK7jU7y1RMiHZgevEKnkcQcSU,1461
76
- solax_py_library-1.0.0.2507.dist-info/METADATA,sha256=QFXm7_nZgxPq1nYNqz-sRWxbuSCBSCMgwFdZkRqtQEc,1827
77
- solax_py_library-1.0.0.2507.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
78
- solax_py_library-1.0.0.2507.dist-info/RECORD,,
76
+ solax_py_library-1.0.0.2509.dist-info/METADATA,sha256=Z1mbr2wt6filJOXO4rpQUuBH2uEEJjJdULIWrv8ycHU,1827
77
+ solax_py_library-1.0.0.2509.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
78
+ solax_py_library-1.0.0.2509.dist-info/RECORD,,
@@ -1,411 +0,0 @@
1
- import json
2
- import os.path
3
- import struct
4
- import traceback
5
- import threading
6
- import subprocess as sp
7
-
8
- import requests
9
- from datetime import datetime, timedelta
10
-
11
- from tornado.log import app_log
12
-
13
- from domain.ems_enum.smart_scene.condition import ConditionFunc
14
- from settings.const import (
15
- RedisKey,
16
- Role,
17
- DeviceInfo,
18
- PCSOperateFunctionName,
19
- Enumeration,
20
- FilePath,
21
- )
22
- from web.web_ext import config_json
23
- from models.tables import Device
24
- from utils.common import options, get_logger_sn, md5_encrypt, read_write_json_file
25
- from services.redis_service import RedisService
26
- from models.sqlite_db import session_maker
27
- from utils.redis_utils import RedisProducer
28
- from services.strategy_control_service import StrategyControlService
29
- from solax_py_library.utils.cloud_client import CloudClient
30
-
31
- redis_host = options.REDIS_HOST
32
- redis_port = options.REDIS_PORT
33
- db = options.REDIS_DB
34
- LOCAL_URL = "http://127.0.0.1:8000/ems/cloud/"
35
-
36
-
37
- class SmartSceneService(object):
38
- def __init__(self):
39
- self.ems_sn = get_logger_sn()
40
- self.redis_service = RedisService()
41
- self.redis_producer = RedisProducer().producer
42
- self.strategy_control_service = StrategyControlService()
43
-
44
- def request_local_redis(self, redis_topic, payload):
45
- """本地redis分发任务"""
46
- try:
47
- self.redis_producer.publish(redis_topic, payload)
48
- except Exception:
49
- app_log.info(traceback.format_exc())
50
-
51
- def request_local_server(self, apiName, apiPostData, scene_id=None):
52
- """
53
- 访问本地接口, 调用server8000
54
- 需要使用admin权限
55
- :param apiName: 接口名称
56
- :param apiPostData: 请求参数
57
- :param scene_id: 请求标识
58
- :return:
59
- """
60
- username = Role.SUPER_ADMIN
61
- user_info = {"username": username, "password": md5_encrypt(self.ems_sn)}
62
- token = self.redis_service.make_token(username, user_info)
63
- url = LOCAL_URL + apiName
64
- headers = {"token": token}
65
- try:
66
- response = requests.post(url, json=apiPostData, headers=headers, timeout=5)
67
- app_log.info(
68
- f"scene_id: {scene_id}, request apiName: {apiName}, response: {response.text}"
69
- )
70
- return json.loads(response.text)
71
- except Exception:
72
- app_log.info(traceback.format_exc())
73
- return False
74
-
75
- def get_token(self):
76
- token = self.redis_service.get_token()
77
- if not token:
78
- client = CloudClient(base_url=config_json.get("CLOUD_URL", ""))
79
- token = client.get_token(
80
- ems_sn=self.ems_sn, sn_secret=config_json.get("MQTT_PASSWORD")
81
- )
82
- if not token:
83
- app_log.info("访问token接口失败")
84
- return False
85
- self.redis_service.set_token(token)
86
- return token
87
- else:
88
- return token
89
-
90
- def get_weather_data_from_redis(self):
91
- try:
92
- weather_info = self.redis_service.get_ele_price_info() or {}
93
- return weather_info
94
- except Exception:
95
- app_log.error(traceback.format_exc())
96
- return {}
97
-
98
- def get_weather_data_from_cloud(self):
99
- """获取未来24小时天气数据"""
100
- try:
101
- token = self.get_token()
102
- client = CloudClient(base_url=config_json.get("CLOUD_URL", ""))
103
- weather_info = client.get_weather_data_from_cloud(
104
- ems_sn=self.ems_sn,
105
- token=token,
106
- )
107
- if not weather_info:
108
- app_log.error(f"获取天气数据失败 异常 {traceback.format_exc()}")
109
- return False
110
- app_log.info(f"获取天气数据成功: {weather_info}")
111
- self.redis_service.set_weather_info(weather_info)
112
- except Exception:
113
- app_log.error(f"获取天气数据失败 异常 {traceback.format_exc()}")
114
- return False
115
-
116
- def trans_str_time_to_index(self, now_time, minute=15):
117
- """将时间按照minute切换为索引,时间格式为 %H-%M"""
118
- time_list = [int(i) for i in now_time.split(":")]
119
- time_int = time_list[0] * 4 + time_list[1] // minute
120
- return time_int
121
-
122
- def get_electrovalence_data_from_cloud(self):
123
- try:
124
- token = self.get_token()
125
- client = CloudClient(base_url=config_json.get("CLOUD_URL", ""))
126
- ele_price_info = client.get_electrovalence_data_from_cloud(
127
- ems_sn=self.ems_sn, token=token
128
- )
129
- app_log.info(f"获取电价数据成功: {ele_price_info}")
130
- self.redis_service.set_ele_price_info(ele_price_info)
131
- except Exception:
132
- app_log.error(f"获取电价数据失败 异常 {traceback.format_exc()}")
133
- return False
134
-
135
- def get_electrovalence_data_from_redis(self):
136
- """从自定义电价的缓存中获取"""
137
- month_temp = self.redis_service.hget(
138
- RedisKey.MONTH_TEMPLATE, RedisKey.MONTH_TEMPLATE
139
- )
140
- month_temp = json.loads(month_temp) if month_temp else {}
141
- if month_temp == {}:
142
- return {}
143
- month = datetime.strftime(datetime.now(), "%m")
144
- month_map = {
145
- "01": "Jan",
146
- "02": "Feb",
147
- "03": "Mar",
148
- "04": "Apr",
149
- "05": "May",
150
- "06": "Jun",
151
- "07": "Jul",
152
- "08": "Aug",
153
- "09": "Sep",
154
- "10": "Oct",
155
- "11": "Nov",
156
- "12": "Dec",
157
- }
158
- month = month_map.get(month, None)
159
- if month is None:
160
- return {}
161
- template_id = str(month_temp.get(month, 0))
162
- price_info = self.redis_service.hget(
163
- RedisKey.ELECTRICITY_PRICE_TEMPLATE, template_id
164
- )
165
- price_info = json.loads(price_info) if price_info else {}
166
- if price_info == {}:
167
- return {}
168
-
169
- stationInfo = self.redis_service.get(RedisKey.STATION_INFO)
170
- stationInfo = json.loads(stationInfo) if stationInfo else {}
171
- currencyCode = stationInfo.get("currencyCode")
172
- currency_file = read_write_json_file(FilePath.CURRENCY_PATH)
173
- ele_unit = False
174
- try:
175
- for j in currency_file["list"]:
176
- if currencyCode == j["code"]:
177
- ele_unit = j["unit"].split(":")[-1] + "/kWh"
178
- break
179
- except:
180
- app_log.error(f"获取本地电价单位出错 {traceback.format_exc()}")
181
- ele_price_info = {
182
- "buy": [None] * 192,
183
- "sell": [None] * 192,
184
- "date": datetime.strftime(datetime.now(), "%Y-%m-%d"),
185
- "ele_unit": ele_unit if ele_unit else "/kWh",
186
- }
187
- for period in price_info["periodConfiguration"]:
188
- start_index = self.trans_str_time_to_index(period["startTime"])
189
- end_index = self.trans_str_time_to_index(period["endTime"])
190
- slotName = period["slotName"]
191
- for price in price_info["priceAllocation"]:
192
- if price["slotName"] == slotName:
193
- ele_price_info["buy"][start_index:end_index] = [
194
- price["buyPrice"]
195
- ] * (end_index - start_index)
196
- ele_price_info["sell"][start_index:end_index] = [
197
- price["salePrice"]
198
- ] * (end_index - start_index)
199
- break
200
- return ele_price_info
201
-
202
- def get_electrovalence_data(self):
203
- try:
204
- online_status = self.redis_service.hget(RedisKey.LED_STATUS_KEY, "blue")
205
- online_status = (
206
- json.loads(online_status).get("status") if online_status else 0
207
- )
208
- if online_status:
209
- ele_price_info = self.redis_service.get_ele_price_info() or {}
210
- today = datetime.strftime(datetime.now(), "%Y-%m-%d")
211
- date_now = ele_price_info.get("date", "")
212
- if today not in date_now:
213
- ele_price_info = {}
214
- else:
215
- app_log.info("设备离线,获取本地电价")
216
- ele_price_info = self.get_electrovalence_data_from_redis()
217
- return ele_price_info
218
- except Exception:
219
- app_log.error(traceback.format_exc())
220
- return {}
221
-
222
- def get_highest_or_lowest_price(
223
- self, start_time, end_time, hours, price_list, func="expensive_hours"
224
- ):
225
- """获取一段时间内,电价最高或最低的几个小时"""
226
- start_index = self.trans_str_time_to_index(start_time)
227
- end_index = self.trans_str_time_to_index(end_time)
228
- arr = price_list[start_index:end_index]
229
- if None in arr:
230
- return False
231
- indices = list(range(end_index - start_index))
232
- if func == "expensive_hours":
233
- reverse = True
234
- else:
235
- reverse = False
236
- sorted_indices = sorted(indices, key=lambda i: arr[i], reverse=reverse)
237
- return sorted_indices[: int(hours * 4)], start_index
238
-
239
- def get_rounded_times(self):
240
- """
241
- 返回距离当前时间最近的15min的整点时间以及后一整点5min时间(天气是预测未来15min的,也就是在00:00时,只能拿到00:15的数据)
242
- """
243
- now = datetime.now()
244
- # 确定当前时间所属的15分钟区间
245
- index_1 = now.minute // 15
246
- index_2 = now.minute % 15
247
- left_time = now.replace(minute=15 * index_1, second=0, microsecond=0)
248
- right_time = left_time + timedelta(minutes=15)
249
- if index_2 < 8:
250
- nearest_time = left_time
251
- else:
252
- nearest_time = right_time
253
- return datetime.strftime(nearest_time, "%Y-%m-%d %H:%M:%S"), datetime.strftime(
254
- right_time, "%Y-%m-%d %H:%M:%S"
255
- )
256
-
257
- def compare_the_magnitudes(self, function, compare_value, base_value):
258
- """比较两个值"""
259
- if function == ConditionFunc.GT:
260
- return compare_value > base_value
261
- elif function == ConditionFunc.EQ:
262
- return compare_value == base_value
263
- elif function == ConditionFunc.LT:
264
- return compare_value < base_value
265
- return False
266
-
267
- def get_cabinet_type(self):
268
- # 获取机柜类型
269
- cabinet_type = 1
270
- with session_maker(change=False) as session:
271
- result = (
272
- session.query(Device.deviceModel)
273
- .filter(Device.deviceType == DeviceInfo.ESS_TYPE, Device.isDelete == 0)
274
- .first()
275
- )
276
-
277
- if result:
278
- cabinet_type = result[0]
279
- if cabinet_type in [3, 4, 7, 8]:
280
- key_name = "AELIO"
281
- else:
282
- key_name = "TRENE"
283
- return key_name
284
-
285
- def set_manual_mode(self, work_mode, power, soc, cabinet_type):
286
- """设置手动模式"""
287
- # 充电
288
- if work_mode == 3:
289
- strategy_info = {
290
- "chargePower": power,
291
- "chargeTargetSoc": soc,
292
- "runState": 1,
293
- }
294
- self.redis_service.hset(
295
- RedisKey.MANUAL_STRATEGY_INFO,
296
- RedisKey.CHARGE_MODE,
297
- json.dumps(strategy_info),
298
- )
299
- self.strategy_control_service.apply_or_stop_strategy(
300
- 0, RedisKey.MANUAL_STRATEGY_INFO, RedisKey.DISCHARGE_MODE
301
- )
302
- # 将运行模式修改为手动
303
- self.redis_service.set(RedisKey.RUN_STRATEGY_TYPE, 1)
304
- elif work_mode == 4:
305
- strategy_info = {
306
- "dischargePower": power,
307
- "dischargeTargetSoc": soc,
308
- "runState": 1,
309
- }
310
- self.redis_service.hset(
311
- RedisKey.MANUAL_STRATEGY_INFO,
312
- RedisKey.DISCHARGE_MODE,
313
- json.dumps(strategy_info),
314
- )
315
- # 将充电状态修改
316
- self.strategy_control_service.apply_or_stop_strategy(
317
- 0, RedisKey.MANUAL_STRATEGY_INFO, RedisKey.CHARGE_MODE
318
- )
319
- # 将运行模式修改为手动
320
- self.redis_service.set(RedisKey.RUN_STRATEGY_TYPE, 1)
321
- # 停止
322
- else:
323
- self.strategy_control_service.apply_or_stop_strategy(
324
- 0, RedisKey.MANUAL_STRATEGY_INFO, RedisKey.DISCHARGE_MODE
325
- )
326
- self.strategy_control_service.apply_or_stop_strategy(
327
- 0, RedisKey.MANUAL_STRATEGY_INFO, RedisKey.CHARGE_MODE
328
- )
329
- self.redis_service.set(RedisKey.RUN_STRATEGY_TYPE, 1)
330
- if work_mode == 3:
331
- manualType = 1
332
- elif work_mode == 4:
333
- manualType = 2
334
- else:
335
- manualType = 3
336
- data = {
337
- "power": power,
338
- "soc": soc,
339
- "workMode": work_mode,
340
- "manualType": manualType,
341
- "useMode": 3,
342
- }
343
- if cabinet_type in ["TRENE"]:
344
- t = threading.Thread(
345
- target=self.request_local_redis, args=(options.REDIS_POWER_CONTROL, "")
346
- )
347
- else:
348
- func_name = PCSOperateFunctionName.SET_AELIO_USE_MODE
349
- channel = options.REDIS_WRITE_SERIAL_DEVICE
350
- future_data = {}
351
- future_data["func_name"] = func_name
352
- future_data["operationMode"] = Enumeration.SINGLE_DEVICE_MODE
353
- future_data["data"] = data
354
- t = threading.Thread(
355
- target=self.request_local_redis,
356
- args=(channel, json.dumps(future_data)),
357
- daemon=True,
358
- )
359
- t.start()
360
- return True
361
-
362
- def struct_transform(self, value, fmt, order="big"):
363
- """将10进制的原始值转换为modbus协议需要的精度与类型的值"""
364
- value = int(value)
365
- if order == "little":
366
- opt = "<"
367
- else:
368
- opt = ">"
369
- try:
370
- if fmt == "int16":
371
- ret = struct.pack(f"{opt}h", value)
372
- ret_list = struct.unpack(f"{opt}H", ret)
373
- elif fmt == "uint16":
374
- ret = struct.pack(f"{opt}H", value)
375
- ret_list = struct.unpack(f"{opt}H", ret)
376
- elif fmt == "int32":
377
- ret = struct.pack(f"{opt}i", value)
378
- ret_list = struct.unpack(f"{opt}HH", ret)
379
- elif fmt == "uint32":
380
- ret = struct.pack(f"{opt}I", value)
381
- ret_list = struct.unpack(f"{opt}HH", ret)
382
- elif fmt == "int64":
383
- ret = struct.pack(f"{opt}q", value)
384
- ret_list = struct.unpack(f"{opt}HHHH", ret)
385
- elif fmt == "uint64":
386
- ret = struct.pack(f"{opt}Q", value)
387
- ret_list = struct.unpack(f"{opt}HHHH", ret)
388
- else:
389
- ret_list = [0]
390
- except Exception:
391
- if "16" in fmt:
392
- ret_list = [0]
393
- elif "32" in fmt:
394
- ret_list = [0, 0]
395
- else:
396
- ret_list = [0, 0, 0, 0]
397
- return list(ret_list)
398
-
399
- def ems_do_control(self, data):
400
- for do_info in data:
401
- do_number = do_info.DoNumber
402
- do_value = do_info.DoValue
403
- if 1 <= do_number <= 8 and do_value in [0, 1]:
404
- path = DeviceInfo.DI_DO_GPIO_MAPPING[f"DO{do_number}"] + "/value"
405
- if not os.path.exists(path):
406
- app_log.info(f"DO path not exists {do_info}")
407
- else:
408
- cmd = f"echo {do_value} > {path}"
409
- ret = sp.getstatusoutput(cmd)
410
- ret = True if ret[0] == 0 else False
411
- app_log.info(f"DO {do_info} 控制结果: {ret}")