solax-py-library 1.0.0.23__py3-none-any.whl → 1.0.0.26__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- solax_py_library/__init__.py +1 -1
- solax_py_library/device/constant/cabinet.py +2 -0
- solax_py_library/device/core/interver/__init__.py +36 -36
- solax_py_library/device/core/interver/base.py +215 -215
- solax_py_library/device/types/alarm.py +16 -0
- solax_py_library/device/types/inverter_config.py +41 -41
- solax_py_library/device/types/modbus_point.py +30 -30
- solax_py_library/exception.py +10 -10
- solax_py_library/smart_scene/__init__.py +0 -0
- solax_py_library/smart_scene/constant/__init__.py +0 -0
- solax_py_library/smart_scene/constant/message_entry.py +179 -0
- solax_py_library/smart_scene/core/__init__.py +0 -0
- solax_py_library/smart_scene/core/action/__init__.py +0 -0
- solax_py_library/smart_scene/core/action/base.py +10 -0
- solax_py_library/smart_scene/core/action/ems_action.py +6 -0
- solax_py_library/smart_scene/core/action/system_action.py +6 -0
- solax_py_library/smart_scene/core/condition/__init__.py +17 -0
- solax_py_library/smart_scene/core/condition/base.py +17 -0
- solax_py_library/smart_scene/core/condition/cabinet_condition.py +44 -0
- solax_py_library/smart_scene/core/condition/date_condition.py +23 -0
- solax_py_library/smart_scene/core/condition/price_condition.py +110 -0
- solax_py_library/smart_scene/core/condition/system_condition.py +35 -0
- solax_py_library/smart_scene/core/condition/weather_condition.py +61 -0
- solax_py_library/smart_scene/core/service/__init__.py +3 -0
- solax_py_library/smart_scene/core/service/runner.py +156 -0
- solax_py_library/smart_scene/exceptions/__init__.py +7 -0
- solax_py_library/smart_scene/exceptions/price.py +5 -0
- solax_py_library/smart_scene/exceptions/smart_scene.py +82 -0
- solax_py_library/smart_scene/exceptions/weather.py +5 -0
- solax_py_library/smart_scene/types/__init__.py +0 -0
- solax_py_library/smart_scene/types/action.py +164 -0
- solax_py_library/smart_scene/types/condition.py +299 -0
- solax_py_library/smart_scene/types/smart_scene_content.py +173 -0
- solax_py_library/snap_shot/__init__.py +3 -3
- solax_py_library/snap_shot/constant/__init__.py +5 -5
- solax_py_library/snap_shot/constant/crc_table.py +258 -258
- solax_py_library/snap_shot/core/base_modbus.py +14 -14
- solax_py_library/snap_shot/core/parser.py +261 -225
- solax_py_library/snap_shot/core/snap_shot.py +25 -7
- solax_py_library/snap_shot/exceptions/__init__.py +3 -3
- solax_py_library/snap_shot/exceptions/snap_shot.py +9 -9
- solax_py_library/snap_shot/types/__init__.py +15 -15
- solax_py_library/snap_shot/types/address.py +39 -39
- solax_py_library/test/__init__.py +0 -0
- solax_py_library/test/test_smart_scene/__init__.py +0 -0
- solax_py_library/test/test_smart_scene/test_condition.py +11 -0
- solax_py_library/test/test_utils/__init__.py +0 -0
- solax_py_library/test/test_utils/test_cloud_client.py +14 -0
- solax_py_library/upload/__init__.py +3 -3
- solax_py_library/upload/api/service.py +24 -24
- solax_py_library/upload/core/upload_service/ftp.py +1 -0
- solax_py_library/upload/exceptions/__init__.py +8 -8
- solax_py_library/upload/exceptions/upload_error.py +21 -21
- solax_py_library/utils/cloud_client.py +210 -0
- solax_py_library/utils/struct_util.py +42 -30
- solax_py_library/utils/time_util.py +38 -0
- {solax_py_library-1.0.0.23.dist-info → solax_py_library-1.0.0.26.dist-info}/METADATA +2 -1
- solax_py_library-1.0.0.26.dist-info/RECORD +80 -0
- solax_py_library-1.0.0.23.dist-info/RECORD +0 -46
- {solax_py_library-1.0.0.23.dist-info → solax_py_library-1.0.0.26.dist-info}/WHEEL +0 -0
@@ -1,39 +1,39 @@
|
|
1
|
-
from enum import IntEnum
|
2
|
-
|
3
|
-
|
4
|
-
class SnapshotMCUSource(IntEnum):
|
5
|
-
MCU_SOURCE_MDSP = 0
|
6
|
-
MCU_SOURCE_SDSP = 1
|
7
|
-
|
8
|
-
|
9
|
-
class SnapshotExportDevice(IntEnum):
|
10
|
-
EXPORT_DEVICE_UART = 0
|
11
|
-
EXPORT_DEVICE_USB = 1
|
12
|
-
|
13
|
-
|
14
|
-
class SnapshotStartResult(IntEnum):
|
15
|
-
SNAPSHOT_START_START = 0xAAAA
|
16
|
-
SNAPSHOT_START_SUCCESS = 0xA5A5
|
17
|
-
SNAPSHOT_START_FAILED = 0x5A5A
|
18
|
-
SNAPSHOT_START_STOP = 0xBBBB
|
19
|
-
|
20
|
-
|
21
|
-
class SnapshotRegisterAddress(IntEnum):
|
22
|
-
SNAPSHOT_REGISTERADDRESS_MCUSOURCE = 0x4011
|
23
|
-
SNAPSHOT_REGISTERADDRESS_EXPORTDEVICE = 0x4012
|
24
|
-
SNAPSHOT_REGISTERADDRESS_START = 0x4013
|
25
|
-
SNAPSHOT_REGISTERADDRESS_TOTALNUMBER = 0x4070
|
26
|
-
SNAPSHOT_REGISTERADDRESS_SNAPSHOTINDEX = 0x4071
|
27
|
-
SNAPSHOT_REGISTERADDRESS_PACKDATASTATUS = 0x4072
|
28
|
-
SNAPSHOT_REGISTERADDRESS_DATASIZE = 0x4073
|
29
|
-
SNAPSHOT_REGISTERADDRESS_CHANNELNUMBER = 0x4074
|
30
|
-
SNAPSHOT_REGISTERADDRESS_CHANNELDATASIZE = 0x4075
|
31
|
-
SNAPSHOT_REGISTERADDRESS_PACKINDEX = 0x407F
|
32
|
-
SNAPSHOT_REGISTERADDRESS_PACKDATA = 0x4080
|
33
|
-
|
34
|
-
|
35
|
-
class CommandType(IntEnum):
|
36
|
-
ACK = 0x80 # 接收应答
|
37
|
-
TOTAL_PACKETS = 0x81 # 上报总包数
|
38
|
-
DATA_PACKET = 0x82 # 具体分包数据
|
39
|
-
ERROR = 0x83 # 报错应答
|
1
|
+
from enum import IntEnum
|
2
|
+
|
3
|
+
|
4
|
+
class SnapshotMCUSource(IntEnum):
|
5
|
+
MCU_SOURCE_MDSP = 0
|
6
|
+
MCU_SOURCE_SDSP = 1
|
7
|
+
|
8
|
+
|
9
|
+
class SnapshotExportDevice(IntEnum):
|
10
|
+
EXPORT_DEVICE_UART = 0
|
11
|
+
EXPORT_DEVICE_USB = 1
|
12
|
+
|
13
|
+
|
14
|
+
class SnapshotStartResult(IntEnum):
|
15
|
+
SNAPSHOT_START_START = 0xAAAA
|
16
|
+
SNAPSHOT_START_SUCCESS = 0xA5A5
|
17
|
+
SNAPSHOT_START_FAILED = 0x5A5A
|
18
|
+
SNAPSHOT_START_STOP = 0xBBBB
|
19
|
+
|
20
|
+
|
21
|
+
class SnapshotRegisterAddress(IntEnum):
|
22
|
+
SNAPSHOT_REGISTERADDRESS_MCUSOURCE = 0x4011
|
23
|
+
SNAPSHOT_REGISTERADDRESS_EXPORTDEVICE = 0x4012
|
24
|
+
SNAPSHOT_REGISTERADDRESS_START = 0x4013
|
25
|
+
SNAPSHOT_REGISTERADDRESS_TOTALNUMBER = 0x4070
|
26
|
+
SNAPSHOT_REGISTERADDRESS_SNAPSHOTINDEX = 0x4071
|
27
|
+
SNAPSHOT_REGISTERADDRESS_PACKDATASTATUS = 0x4072
|
28
|
+
SNAPSHOT_REGISTERADDRESS_DATASIZE = 0x4073
|
29
|
+
SNAPSHOT_REGISTERADDRESS_CHANNELNUMBER = 0x4074
|
30
|
+
SNAPSHOT_REGISTERADDRESS_CHANNELDATASIZE = 0x4075
|
31
|
+
SNAPSHOT_REGISTERADDRESS_PACKINDEX = 0x407F
|
32
|
+
SNAPSHOT_REGISTERADDRESS_PACKDATA = 0x4080
|
33
|
+
|
34
|
+
|
35
|
+
class CommandType(IntEnum):
|
36
|
+
ACK = 0x80 # 接收应答
|
37
|
+
TOTAL_PACKETS = 0x81 # 上报总包数
|
38
|
+
DATA_PACKET = 0x82 # 具体分包数据
|
39
|
+
ERROR = 0x83 # 报错应答
|
File without changes
|
File without changes
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from unittest import TestCase
|
2
|
+
|
3
|
+
from solax_py_library.smart_scene.core.condition import DateCondition, BaseCondition
|
4
|
+
|
5
|
+
|
6
|
+
class TestCondition(TestCase):
|
7
|
+
def test_condition(self):
|
8
|
+
date_condition = DateCondition(
|
9
|
+
update_value_function=lambda: 1,
|
10
|
+
)
|
11
|
+
assert isinstance(date_condition, BaseCondition)
|
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from unittest import TestCase
|
2
|
+
|
3
|
+
from solax_py_library.utils.cloud_client import CloudClient
|
4
|
+
|
5
|
+
|
6
|
+
class TestCloudClient(TestCase):
|
7
|
+
def test_get_weather(self):
|
8
|
+
client = CloudClient()
|
9
|
+
ret = client.get_weather_data_from_cloud(
|
10
|
+
"https://aliyun-sit.solaxtech.net:5050",
|
11
|
+
"XMG11A011L",
|
12
|
+
"b080a22827484db6bd509d496f9df90b",
|
13
|
+
)
|
14
|
+
print(ret)
|
@@ -1,3 +1,3 @@
|
|
1
|
-
from . import api, types, core, exceptions
|
2
|
-
|
3
|
-
__all__ = ["api", "core", "exceptions", "types"]
|
1
|
+
from . import api, types, core, exceptions
|
2
|
+
|
3
|
+
__all__ = ["api", "core", "exceptions", "types"]
|
@@ -1,24 +1,24 @@
|
|
1
|
-
from typing import Dict, Any
|
2
|
-
|
3
|
-
from solax_py_library.upload.core.upload_service import upload_service_map
|
4
|
-
from solax_py_library.exception import SolaxBaseError
|
5
|
-
from solax_py_library.upload.types.client import UploadType, UploadData
|
6
|
-
|
7
|
-
|
8
|
-
def upload_service(upload_type: UploadType, configuration: Dict[str, Any]):
|
9
|
-
"""
|
10
|
-
upload_type: 上传类型。
|
11
|
-
configuration: 配置信息
|
12
|
-
"""
|
13
|
-
upload_class = upload_service_map.get(upload_type)
|
14
|
-
if not upload_class:
|
15
|
-
raise SolaxBaseError
|
16
|
-
return upload_class(**configuration)
|
17
|
-
|
18
|
-
|
19
|
-
async def upload(
|
20
|
-
upload_type: UploadType, configuration: Dict[str, Any], upload_data: UploadData
|
21
|
-
):
|
22
|
-
service = upload_service(upload_type, configuration)
|
23
|
-
with service as s:
|
24
|
-
await s.upload(upload_data)
|
1
|
+
from typing import Dict, Any
|
2
|
+
|
3
|
+
from solax_py_library.upload.core.upload_service import upload_service_map
|
4
|
+
from solax_py_library.exception import SolaxBaseError
|
5
|
+
from solax_py_library.upload.types.client import UploadType, UploadData
|
6
|
+
|
7
|
+
|
8
|
+
def upload_service(upload_type: UploadType, configuration: Dict[str, Any]):
|
9
|
+
"""
|
10
|
+
upload_type: 上传类型。
|
11
|
+
configuration: 配置信息
|
12
|
+
"""
|
13
|
+
upload_class = upload_service_map.get(upload_type)
|
14
|
+
if not upload_class:
|
15
|
+
raise SolaxBaseError
|
16
|
+
return upload_class(**configuration)
|
17
|
+
|
18
|
+
|
19
|
+
async def upload(
|
20
|
+
upload_type: UploadType, configuration: Dict[str, Any], upload_data: UploadData
|
21
|
+
):
|
22
|
+
service = upload_service(upload_type, configuration)
|
23
|
+
with service as s:
|
24
|
+
await s.upload(upload_data)
|
@@ -79,6 +79,7 @@ class FTPUploadService(BaseUploadService):
|
|
79
79
|
try:
|
80
80
|
if not self._is_connect:
|
81
81
|
raise ConnectError(message="not connect yet")
|
82
|
+
# self._client.set_pasv(False)
|
82
83
|
self._cwd_folder()
|
83
84
|
with open(data.file_path, "rb") as f:
|
84
85
|
self._client.storbinary(f"STOR {data.file_name}", f)
|
@@ -1,8 +1,8 @@
|
|
1
|
-
from .upload_error import ConfigurationError, ConnectError, LoginError, SendDataError
|
2
|
-
|
3
|
-
__all__ = [
|
4
|
-
"ConnectError",
|
5
|
-
"LoginError",
|
6
|
-
"SendDataError",
|
7
|
-
"ConfigurationError",
|
8
|
-
]
|
1
|
+
from .upload_error import ConfigurationError, ConnectError, LoginError, SendDataError
|
2
|
+
|
3
|
+
__all__ = [
|
4
|
+
"ConnectError",
|
5
|
+
"LoginError",
|
6
|
+
"SendDataError",
|
7
|
+
"ConfigurationError",
|
8
|
+
]
|
@@ -1,21 +1,21 @@
|
|
1
|
-
from solax_py_library.exception import SolaxBaseError
|
2
|
-
|
3
|
-
|
4
|
-
class ConnectError(SolaxBaseError):
|
5
|
-
code = 0x1001
|
6
|
-
message = "connect error"
|
7
|
-
|
8
|
-
|
9
|
-
class LoginError(SolaxBaseError):
|
10
|
-
code = 0x1002
|
11
|
-
message = "authentication error"
|
12
|
-
|
13
|
-
|
14
|
-
class SendDataError(SolaxBaseError):
|
15
|
-
code = 0x1003
|
16
|
-
message = "send data error"
|
17
|
-
|
18
|
-
|
19
|
-
class ConfigurationError(SolaxBaseError):
|
20
|
-
code = 0x1004
|
21
|
-
message = "server configuration error"
|
1
|
+
from solax_py_library.exception import SolaxBaseError
|
2
|
+
|
3
|
+
|
4
|
+
class ConnectError(SolaxBaseError):
|
5
|
+
code = 0x1001
|
6
|
+
message = "connect error"
|
7
|
+
|
8
|
+
|
9
|
+
class LoginError(SolaxBaseError):
|
10
|
+
code = 0x1002
|
11
|
+
message = "authentication error"
|
12
|
+
|
13
|
+
|
14
|
+
class SendDataError(SolaxBaseError):
|
15
|
+
code = 0x1003
|
16
|
+
message = "send data error"
|
17
|
+
|
18
|
+
|
19
|
+
class ConfigurationError(SolaxBaseError):
|
20
|
+
code = 0x1004
|
21
|
+
message = "server configuration error"
|
@@ -0,0 +1,210 @@
|
|
1
|
+
import json
|
2
|
+
import traceback
|
3
|
+
from datetime import datetime
|
4
|
+
|
5
|
+
import requests
|
6
|
+
|
7
|
+
from solax_py_library.utils.time_util import trans_str_time_to_index
|
8
|
+
|
9
|
+
|
10
|
+
class CloudClient:
|
11
|
+
def __init__(self, base_url):
|
12
|
+
self.base_url = base_url
|
13
|
+
|
14
|
+
def get_token(self, ems_sn, sn_secret):
|
15
|
+
token_url = self.base_url + "/device/token/getByRegistrationSn"
|
16
|
+
try:
|
17
|
+
response = requests.post(
|
18
|
+
token_url,
|
19
|
+
json={
|
20
|
+
"registrationSn": ems_sn,
|
21
|
+
"snSecret": sn_secret,
|
22
|
+
},
|
23
|
+
timeout=5,
|
24
|
+
)
|
25
|
+
if response.content:
|
26
|
+
response_data = json.loads(response.content)
|
27
|
+
print(f"获取token结果 {response_data}")
|
28
|
+
if response_data.get("code") == 0 and response_data.get("result"):
|
29
|
+
token = response_data["result"]
|
30
|
+
return token
|
31
|
+
except Exception as e:
|
32
|
+
print(f"访问token接口失败: {str(e)}")
|
33
|
+
|
34
|
+
def get_weather_data_from_cloud(self, ems_sn, token):
|
35
|
+
"""获取未来24小时天气数据"""
|
36
|
+
try:
|
37
|
+
weather_url = self.base_url + "/ess/web/v1/powerStation/station/solcast/get"
|
38
|
+
headers = {"token": token, "Content-Type": "application/json"}
|
39
|
+
post_dict = {"registerNo": ems_sn, "day": 1}
|
40
|
+
response = requests.post(
|
41
|
+
url=weather_url, data=json.dumps(post_dict), headers=headers, timeout=5
|
42
|
+
)
|
43
|
+
# 访问失败或获取数据失败,则重复插入最后一条数据
|
44
|
+
if response.status_code != 200:
|
45
|
+
print(f"获取天气数据失败 状态码 {response.status_code}")
|
46
|
+
return False
|
47
|
+
response_data = response.json()
|
48
|
+
if response_data.get("result") is None:
|
49
|
+
print(f"获取天气数据失败 返回数据 {response_data}")
|
50
|
+
return False
|
51
|
+
weather_info = {
|
52
|
+
"timeList": [],
|
53
|
+
"irradiance": {"valueList": []},
|
54
|
+
"temperature": {"valueList": []},
|
55
|
+
"humidity": {"valueList": []},
|
56
|
+
"wind": {"valueList": []},
|
57
|
+
"barometricPressure": {"valueList": []},
|
58
|
+
"rain": {"valueList": []},
|
59
|
+
}
|
60
|
+
for info in response_data["result"]:
|
61
|
+
weather_info["timeList"].append(info["localTime"])
|
62
|
+
weather_info["irradiance"]["valueList"].append(float(info["ghi"]))
|
63
|
+
weather_info["temperature"]["valueList"].append(float(info["air_temp"]))
|
64
|
+
weather_info["humidity"]["valueList"].append(
|
65
|
+
float(info["relative_humidity"])
|
66
|
+
)
|
67
|
+
weather_info["wind"]["valueList"].append(float(info["wind_speed_10m"]))
|
68
|
+
weather_info["barometricPressure"]["valueList"].append(
|
69
|
+
float(info.get("surface_pressure", 0))
|
70
|
+
)
|
71
|
+
rain = 1 if float(info["precipitation_rate"]) > 2.5 else 0
|
72
|
+
weather_info["rain"]["valueList"].append(rain)
|
73
|
+
data_length = len(weather_info["irradiance"]["valueList"])
|
74
|
+
if weather_info["timeList"] == []:
|
75
|
+
weather_info = {}
|
76
|
+
else:
|
77
|
+
weather_info["irradiance"]["maxValue"] = max(
|
78
|
+
weather_info["irradiance"]["valueList"]
|
79
|
+
)
|
80
|
+
weather_info["irradiance"]["avgValue"] = round(
|
81
|
+
sum(weather_info["irradiance"]["valueList"]) / data_length, 3
|
82
|
+
)
|
83
|
+
weather_info["irradiance"]["minValue"] = min(
|
84
|
+
weather_info["irradiance"]["valueList"]
|
85
|
+
)
|
86
|
+
|
87
|
+
weather_info["temperature"]["maxValue"] = max(
|
88
|
+
weather_info["temperature"]["valueList"]
|
89
|
+
)
|
90
|
+
weather_info["temperature"]["avgValue"] = round(
|
91
|
+
sum(weather_info["temperature"]["valueList"]) / data_length, 3
|
92
|
+
)
|
93
|
+
weather_info["temperature"]["minValue"] = min(
|
94
|
+
weather_info["temperature"]["valueList"]
|
95
|
+
)
|
96
|
+
|
97
|
+
weather_info["humidity"]["maxValue"] = max(
|
98
|
+
weather_info["humidity"]["valueList"]
|
99
|
+
)
|
100
|
+
weather_info["humidity"]["avgValue"] = round(
|
101
|
+
sum(weather_info["humidity"]["valueList"]) / data_length, 3
|
102
|
+
)
|
103
|
+
weather_info["humidity"]["minValue"] = min(
|
104
|
+
weather_info["humidity"]["valueList"]
|
105
|
+
)
|
106
|
+
|
107
|
+
weather_info["wind"]["maxValue"] = max(
|
108
|
+
weather_info["wind"]["valueList"]
|
109
|
+
)
|
110
|
+
weather_info["wind"]["avgValue"] = round(
|
111
|
+
sum(weather_info["wind"]["valueList"]) / data_length, 3
|
112
|
+
)
|
113
|
+
weather_info["wind"]["minValue"] = min(
|
114
|
+
weather_info["wind"]["valueList"]
|
115
|
+
)
|
116
|
+
|
117
|
+
weather_info["barometricPressure"]["maxValue"] = max(
|
118
|
+
weather_info["barometricPressure"]["valueList"]
|
119
|
+
)
|
120
|
+
weather_info["barometricPressure"]["avgValue"] = round(
|
121
|
+
sum(weather_info["barometricPressure"]["valueList"]) / data_length,
|
122
|
+
3,
|
123
|
+
)
|
124
|
+
weather_info["barometricPressure"]["minValue"] = min(
|
125
|
+
weather_info["barometricPressure"]["valueList"]
|
126
|
+
)
|
127
|
+
|
128
|
+
weather_info["rain"]["maxValue"] = max(
|
129
|
+
weather_info["rain"]["valueList"]
|
130
|
+
)
|
131
|
+
weather_info["rain"]["minValue"] = min(
|
132
|
+
weather_info["rain"]["valueList"]
|
133
|
+
)
|
134
|
+
return weather_info
|
135
|
+
print("获取天气数据成功")
|
136
|
+
except Exception:
|
137
|
+
print(f"获取天气数据失败 异常 {traceback.format_exc()}")
|
138
|
+
return False
|
139
|
+
|
140
|
+
def get_electrovalence_data_from_cloud(self, ems_sn, token):
|
141
|
+
try:
|
142
|
+
price_url = self.base_url + "/powerStation/station/getCurrentElectrovalence"
|
143
|
+
response = requests.post(
|
144
|
+
url=price_url,
|
145
|
+
headers={"token": token, "Content-Type": "application/json"},
|
146
|
+
json={"registerNo": ems_sn},
|
147
|
+
timeout=5,
|
148
|
+
)
|
149
|
+
# 访问失败或获取数据失败,则重复插入最后一条数据
|
150
|
+
if response.status_code != 200:
|
151
|
+
print(f"获取电价数据失败 状态码 {response.status_code}")
|
152
|
+
return False
|
153
|
+
response_data = response.json()
|
154
|
+
if response_data.get("result") is None:
|
155
|
+
print(f"获取电价数据失败 返回数据 {response_data}")
|
156
|
+
return False
|
157
|
+
today = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")
|
158
|
+
ele_price_info = {
|
159
|
+
"buy": [None] * 192,
|
160
|
+
"sell": [None] * 192,
|
161
|
+
"date": today,
|
162
|
+
}
|
163
|
+
ele_price_info["ele_unit"] = response_data["result"]["unit"]
|
164
|
+
if ele_price_info["ele_unit"] == "¥":
|
165
|
+
rate = 1
|
166
|
+
ele_price_info["ele_unit"] = "¥/kWh"
|
167
|
+
else:
|
168
|
+
rate = 100
|
169
|
+
ele_price_info["ele_unit"] = "Cents €/kWh"
|
170
|
+
for detail_info in response_data["result"]["list"]:
|
171
|
+
start_index = trans_str_time_to_index(detail_info["startTime"])
|
172
|
+
end_index = trans_str_time_to_index(detail_info["endTime"])
|
173
|
+
# 处理欧分的情况
|
174
|
+
if detail_info["buyPrice"] is not None:
|
175
|
+
buy_price = round(detail_info["buyPrice"] * rate, 5)
|
176
|
+
else:
|
177
|
+
buy_price = detail_info["buyPrice"]
|
178
|
+
if detail_info["salePrice"] is not None:
|
179
|
+
sale_price = round(detail_info["salePrice"] * rate, 5)
|
180
|
+
else:
|
181
|
+
sale_price = detail_info["salePrice"]
|
182
|
+
ele_price_info["buy"][start_index:end_index] = [buy_price] * (
|
183
|
+
end_index - start_index
|
184
|
+
)
|
185
|
+
ele_price_info["sell"][start_index:end_index] = [sale_price] * (
|
186
|
+
end_index - start_index
|
187
|
+
)
|
188
|
+
if response_data["result"].get("tomorrow") is not None:
|
189
|
+
for detail_info in response_data["result"]["tomorrow"]:
|
190
|
+
start_index = trans_str_time_to_index(detail_info["startTime"]) + 96
|
191
|
+
end_index = trans_str_time_to_index(detail_info["endTime"]) + 96
|
192
|
+
if detail_info["buyPrice"] is not None:
|
193
|
+
buy_price = round(detail_info["buyPrice"] * rate, 5)
|
194
|
+
else:
|
195
|
+
buy_price = detail_info["buyPrice"]
|
196
|
+
if detail_info["salePrice"] is not None:
|
197
|
+
sale_price = round(detail_info["salePrice"] * rate, 5)
|
198
|
+
else:
|
199
|
+
sale_price = detail_info["salePrice"]
|
200
|
+
ele_price_info["buy"][start_index:end_index] = [buy_price] * (
|
201
|
+
end_index - start_index
|
202
|
+
)
|
203
|
+
ele_price_info["sell"][start_index:end_index] = [sale_price] * (
|
204
|
+
end_index - start_index
|
205
|
+
)
|
206
|
+
print("获取电价数据成功")
|
207
|
+
return ele_price_info
|
208
|
+
except Exception:
|
209
|
+
print(f"获取电价数据失败 异常 {traceback.format_exc()}")
|
210
|
+
return False
|
@@ -1,30 +1,42 @@
|
|
1
|
-
import copy
|
2
|
-
import struct
|
3
|
-
from typing import List
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
"""
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
"""
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
1
|
+
import copy
|
2
|
+
import struct
|
3
|
+
from typing import List
|
4
|
+
|
5
|
+
format_map = {
|
6
|
+
"int8": {"format_str": "bb", "length": 1},
|
7
|
+
"uint8": {"format_str": "BB", "length": 1},
|
8
|
+
"int16": {"format_str": "h", "length": 1},
|
9
|
+
"uint16": {"format_str": "H", "length": 1},
|
10
|
+
"int32": {"format_str": "i", "length": 2},
|
11
|
+
"uint32": {"format_str": "I", "length": 2},
|
12
|
+
"int64": {"format_str": "q", "length": 4},
|
13
|
+
"uint64": {"format_str": "Q", "length": 4},
|
14
|
+
"float": {"format_str": "f", "length": 1},
|
15
|
+
}
|
16
|
+
|
17
|
+
|
18
|
+
def unpack(data: List, data_format, reversed=False):
|
19
|
+
"""
|
20
|
+
:param data: 数据字节, 入参均是由modbus读取到的list[uint16]进行转换
|
21
|
+
:param data_format: 数据格式
|
22
|
+
:param reversed: 是否翻转大小端
|
23
|
+
"""
|
24
|
+
cur_data = copy.deepcopy(data)
|
25
|
+
data_format = data_format.lower()
|
26
|
+
if data_format not in format_map:
|
27
|
+
raise Exception("暂不支持")
|
28
|
+
pack_str = ("<" if reversed else ">") + "H" * len(cur_data)
|
29
|
+
to_pack_data = struct.pack(pack_str, *cur_data)
|
30
|
+
struct_format = ("<" if reversed else ">") + format_map[data_format]["format_str"]
|
31
|
+
return struct.unpack(struct_format, to_pack_data)
|
32
|
+
|
33
|
+
|
34
|
+
def pack(value, fmt, order="big"):
|
35
|
+
"""将10进制的原始值转换为modbus协议需要的精度与类型的值"""
|
36
|
+
opt = "<" if order == "little" else ">"
|
37
|
+
if fmt not in format_map:
|
38
|
+
raise Exception("暂不支持")
|
39
|
+
value = int(value)
|
40
|
+
ret = struct.pack(f'{opt}{format_map[fmt]["format_str"]}', value)
|
41
|
+
ret_list = struct.unpack(f'{opt}{"H" * format_map[fmt]["length"]}', ret)
|
42
|
+
return list(ret_list)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from datetime import datetime, timedelta
|
2
|
+
|
3
|
+
|
4
|
+
def trans_str_time_to_index(now_time, minute=15):
|
5
|
+
"""将时间按照minute切换为索引,时间格式为 %H-%M"""
|
6
|
+
time_list = [int(i) for i in now_time.split(":")]
|
7
|
+
time_int = time_list[0] * 4 + time_list[1] // minute
|
8
|
+
return time_int
|
9
|
+
|
10
|
+
|
11
|
+
def get_highest_or_lowest_value(start_time, end_time, hours, price_list, reverse=False):
|
12
|
+
start_index = trans_str_time_to_index(start_time)
|
13
|
+
end_index = trans_str_time_to_index(end_time)
|
14
|
+
arr = price_list[start_index:end_index]
|
15
|
+
if None in arr:
|
16
|
+
return False
|
17
|
+
indices = list(range(end_index - start_index))
|
18
|
+
sorted_indices = sorted(indices, key=lambda i: arr[i], reverse=reverse)
|
19
|
+
return sorted_indices[: int(hours * 4)], start_index
|
20
|
+
|
21
|
+
|
22
|
+
def get_rounded_times():
|
23
|
+
"""
|
24
|
+
返回距离当前时间最近的15min的整点时间以及后一整点5min时间(天气是预测未来15min的,也就是在00:00时,只能拿到00:15的数据)
|
25
|
+
"""
|
26
|
+
now = datetime.now()
|
27
|
+
# 确定当前时间所属的15分钟区间
|
28
|
+
index_1 = now.minute // 15
|
29
|
+
index_2 = now.minute % 15
|
30
|
+
left_time = now.replace(minute=15 * index_1, second=0, microsecond=0)
|
31
|
+
right_time = left_time + timedelta(minutes=15)
|
32
|
+
if index_2 < 8:
|
33
|
+
nearest_time = left_time
|
34
|
+
else:
|
35
|
+
nearest_time = right_time
|
36
|
+
return datetime.strftime(nearest_time, "%Y-%m-%d %H:%M:%S"), datetime.strftime(
|
37
|
+
right_time, "%Y-%m-%d %H:%M:%S"
|
38
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: solax-py-library
|
3
|
-
Version: 1.0.0.
|
3
|
+
Version: 1.0.0.26
|
4
4
|
Summary: some common tool
|
5
5
|
Author: shenlvyu
|
6
6
|
Author-email: 13296718439@163.com
|
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
15
15
|
Requires-Dist: pydantic (>=1.10.0,<2.0.0)
|
16
|
+
Requires-Dist: requests (==2.32.3)
|
16
17
|
Requires-Dist: typing-extensions (==4.7.1)
|
17
18
|
Description-Content-Type: text/markdown
|
18
19
|
|