solax-py-library 1.0.0.14__py3-none-any.whl → 1.0.0.16__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.
Files changed (29) hide show
  1. solax_py_library/__init__.py +1 -1
  2. solax_py_library/snap_shot/__init__.py +4 -4
  3. solax_py_library/snap_shot/address.py +299 -67
  4. solax_py_library/snap_shot/base_modbus.py +14 -14
  5. solax_py_library/snap_shot/core.py +249 -219
  6. solax_py_library/snap_shot/exceptions.py +6 -4
  7. solax_py_library/snap_shot/parser.py +226 -224
  8. solax_py_library/snap_shot/untils.py +20 -17
  9. solax_py_library/upload/__init__.py +3 -3
  10. solax_py_library/upload/api/__init__.py +3 -3
  11. solax_py_library/upload/api/service.py +24 -24
  12. solax_py_library/upload/core/__init__.py +3 -3
  13. solax_py_library/upload/core/data_adapter/__init__.py +5 -5
  14. solax_py_library/upload/core/data_adapter/base.py +9 -9
  15. solax_py_library/upload/core/data_adapter/csv.py +26 -26
  16. solax_py_library/upload/core/upload_service/__init__.py +15 -15
  17. solax_py_library/upload/core/upload_service/base.py +43 -43
  18. solax_py_library/upload/core/upload_service/ftp.py +97 -86
  19. solax_py_library/upload/errors/__init__.py +10 -10
  20. solax_py_library/upload/errors/base.py +10 -10
  21. solax_py_library/upload/errors/upload_error.py +21 -21
  22. solax_py_library/upload/test/test_ftp.py +81 -40
  23. solax_py_library/upload/types/__init__.py +11 -11
  24. solax_py_library/upload/types/client.py +19 -19
  25. solax_py_library/upload/types/ftp.py +37 -37
  26. {solax_py_library-1.0.0.14.dist-info → solax_py_library-1.0.0.16.dist-info}/METADATA +4 -4
  27. solax_py_library-1.0.0.16.dist-info/RECORD +29 -0
  28. {solax_py_library-1.0.0.14.dist-info → solax_py_library-1.0.0.16.dist-info}/WHEEL +1 -1
  29. solax_py_library-1.0.0.14.dist-info/RECORD +0 -29
@@ -1,224 +1,226 @@
1
- import re
2
- import struct
3
-
4
- from .address import CRC_Table, CommandType
5
-
6
-
7
- class Parser:
8
- def parse(self, char16):
9
- """
10
- :return:
11
- """
12
- task_id = self.ascii_to_str(char16[12: 76]) # task_id
13
- device_type = int(char16[76: 78]) # 设备类型
14
- device_sn = self.ascii_to_str(char16[78: 118]) # 设备SN
15
- business_type = int(char16[118: 120])
16
- data = char16[120:]
17
- return {
18
- "task_id": task_id, "sn": device_sn, "device_type": device_type,
19
- "business_type": business_type, "message": char16, "data": data
20
- }
21
-
22
- def ascii_to_str(self, data: str):
23
- """阿斯科码转字符串"""
24
- pattern = re.compile('.{2}')
25
- message = '\\u00' + '\\u00'.join(pattern.findall(data))
26
- message = message.replace("\\u0000", "")
27
- message = bytes(message, encoding='utf-8').decode("unicode-escape")
28
- return message
29
-
30
- def upload_data(self, big_data: bytes, task_id: str, upload_data):
31
- business_type = 1 # 1 代表调试信息
32
- data_type = 0 # 0代表故障录波
33
-
34
- # 分片上传(每包1K)
35
- chunk_size = 1024
36
- total_chunks = (len(big_data) + chunk_size - 1) // chunk_size
37
-
38
- # 遍历分片并构造数据包
39
- for chunk_idx, chunk_data in self.data_chunk_generator(big_data, chunk_size):
40
- frame = self.build_data_packet_frame(
41
- task_id=task_id,
42
- business_type=business_type,
43
- data_type=data_type,
44
- total_packets=total_chunks,
45
- current_packet=chunk_idx,
46
- chunk_data=chunk_data
47
- )
48
-
49
- print(f"发送包 {chunk_idx + 1}/{total_chunks}, 长度={len(frame)} 字节")
50
- upload_data(frame)
51
-
52
- def data_chunk_generator(self, data: bytes, chunk_size: int = 1024):
53
- """
54
- 将大数据按固定包大小分片
55
- :param data: 原始数据字节流
56
- :param chunk_size: 每包最大字节数(默认1024
57
- :yield: (当前包序号, 分片数据)
58
- """
59
- total = len(data)
60
- if total == 0:
61
- yield (0, b'') # 空数据单独处理
62
- return
63
-
64
- total_chunks = (total + chunk_size - 1) // chunk_size
65
-
66
- for i in range(total_chunks):
67
- start = i * chunk_size
68
- end = start + chunk_size
69
- yield (i, data[start:end])
70
-
71
- import struct
72
-
73
- def build_response_frame(
74
- self,
75
- task_id: str,
76
- business_type: int,
77
- ack_code: int
78
- ) -> bytes:
79
- """
80
- 构建接收应答帧(指令类型 0x80)
81
- :param task_id: 任务ID(32字节)
82
- :param business_type: 业务类型(0或1)
83
- :param ack_code: 0-成功,1-失败,2-执行中
84
- """
85
- # 固定字段
86
- header = bytes([0x24, 0x24])
87
- func_code = bytes([0x05, 0x07]) # 主功能码5 + 子功能码7
88
-
89
- # 数据域
90
- task_bytes = task_id.encode('ascii')[:32].ljust(32, b'\x00')
91
- business_byte = bytes([business_type])
92
- cmd_byte = bytes([CommandType.ACK.value])
93
- ack_byte = bytes([ack_code])
94
- data_part = task_bytes + business_byte + cmd_byte + ack_byte
95
-
96
- # 计算长度(功能码2字节 + 数据域37字节 + CRC2字节 = 41)
97
- frame_length = len(func_code) + len(data_part) + 2
98
- length_bytes = struct.pack('<H', frame_length)
99
-
100
- # 构建临时帧并计算CRC
101
- temp_frame = header + length_bytes + func_code + data_part
102
- crc = self.crc(temp_frame.hex()).upper()
103
- full_frame = temp_frame + bytes.fromhex(crc)
104
- return full_frame
105
-
106
- def build_total_packets_frame(
107
- self,
108
- task_id: str,
109
- business_type: int,
110
- data_type: int,
111
- total_packets: int
112
- ) -> bytes:
113
- """
114
- 构建上报总包数帧(指令类型 0x81)
115
- :param total_packets: 总包数(若为0,平台停止接收)
116
- """
117
- header = bytes([0x24, 0x24])
118
- func_code = bytes([0x05, 0x07])
119
-
120
- # 数据域
121
- task_bytes = task_id.encode('ascii')[:32].ljust(32, b'\x00')
122
- business_byte = bytes([business_type])
123
- cmd_byte = bytes([CommandType.TOTAL_PACKETS.value])
124
- data_length = struct.pack('<H', 2) # 数据长度固定2字节
125
- data_type_byte = bytes([data_type])
126
- total_packets_bytes = struct.pack('<H', total_packets)
127
-
128
- data_part = (
129
- task_bytes + business_byte + cmd_byte +
130
- data_length + data_type_byte + total_packets_bytes
131
- )
132
-
133
- # 计算长度
134
- frame_length = len(func_code) + len(data_part) + 2
135
- length_bytes = struct.pack('<H', frame_length)
136
-
137
- # 附加CRC
138
- temp_frame = header + length_bytes + func_code + data_part
139
- crc = self.crc(temp_frame.hex()).upper()
140
- return temp_frame + bytes.fromhex(crc)
141
-
142
- def build_data_packet_frame(
143
- self,
144
- task_id: str,
145
- business_type: int,
146
- data_type: int,
147
- total_packets: int,
148
- current_packet: int,
149
- chunk_data: bytes
150
- ) -> bytes:
151
- """
152
- 构建具体分包数据帧(指令类型 0x82)
153
- """
154
- header = bytes([0x24, 0x24])
155
- func_code = bytes([0x05, 0x07])
156
-
157
- # 数据域
158
- task_bytes = task_id.encode('ascii')[:32].ljust(32, b'\x00')
159
- business_byte = bytes([business_type])
160
- cmd_byte = bytes([CommandType.DATA_PACKET.value])
161
- data_type_byte = bytes([data_type])
162
- total_packets_bytes = struct.pack('<H', total_packets)
163
- current_packet_bytes = struct.pack('<H', current_packet)
164
- data_length = struct.pack('<H', len(chunk_data))
165
-
166
- data_part = (
167
- task_bytes + business_byte + cmd_byte + data_type_byte +
168
- total_packets_bytes + current_packet_bytes + data_length + chunk_data
169
- )
170
-
171
- # 计算长度
172
- frame_length = len(func_code) + len(data_part) + 2
173
- length_bytes = struct.pack('<H', frame_length)
174
-
175
- # 附加CRC
176
- temp_frame = header + length_bytes + func_code + data_part
177
- crc = self.crc(temp_frame.hex()).upper()
178
- return temp_frame + bytes.fromhex(crc)
179
-
180
- def build_error_frame(
181
- self,
182
- task_id: str,
183
- business_type: int,
184
- error_type: int
185
- ) -> bytes:
186
- """
187
- 构建报错应答帧(指令类型 0x83)
188
-
189
- """
190
- header = bytes([0x24, 0x24])
191
- func_code = bytes([0x05, 0x07])
192
-
193
- # 数据域
194
- task_bytes = task_id.encode('ascii')[:32].ljust(32, b'\x00')
195
- business_byte = bytes([business_type])
196
- cmd_byte = bytes([CommandType.ERROR.value])
197
- error_byte = bytes([error_type])
198
- data_part = task_bytes + business_byte + cmd_byte + error_byte
199
-
200
- # 计算长度
201
- frame_length = len(func_code) + len(data_part) + 2
202
- length_bytes = struct.pack('<H', frame_length)
203
-
204
- # 附加CRC
205
- temp_frame = header + length_bytes + func_code + data_part
206
- crc = self.crc(temp_frame.hex()).upper()
207
- return temp_frame + bytes.fromhex(crc)
208
-
209
-
210
-
211
- def crc(self, message):
212
- """获取校验位, message: 除去校验位"""
213
- crc = 0
214
- i = 0
215
- length = len(message) // 2
216
- while length > 0:
217
- crc &= 0xffffffff
218
- da = crc // 256
219
- da &= 0xff
220
- crc <<= 8
221
- crc ^= CRC_Table[da ^ int(message[i:i + 2], 16)]
222
- i += 2
223
- length -= 1
224
- return f"{crc & 0xffff:04x}"
1
+ import re
2
+ import struct
3
+
4
+ from .address import CRC_Table, CommandType
5
+
6
+
7
+ class Parser:
8
+ def parse(self, char16):
9
+ """
10
+ :return:
11
+ """
12
+ task_id = self.ascii_to_str(char16[12:76]) # task_id
13
+ device_type = int(char16[76:78]) # 设备类型
14
+ device_sn = self.ascii_to_str(char16[78:118]) # 设备SN
15
+ business_type = int(char16[118:120])
16
+ data = char16[120:]
17
+ return {
18
+ "task_id": task_id,
19
+ "sn": device_sn,
20
+ "device_type": device_type,
21
+ "business_type": business_type,
22
+ "message": char16,
23
+ "data": data,
24
+ }
25
+
26
+ def ascii_to_str(self, data: str):
27
+ """阿斯科码转字符串"""
28
+ pattern = re.compile(".{2}")
29
+ message = "\\u00" + "\\u00".join(pattern.findall(data))
30
+ message = message.replace("\\u0000", "")
31
+ message = bytes(message, encoding="utf-8").decode("unicode-escape")
32
+ return message
33
+
34
+ def upload_data(self, big_data: bytes, task_id: str, upload_data):
35
+ business_type = 1 # 1 代表调试信息
36
+ data_type = 0 # 0代表故障录波
37
+
38
+ # 分片上传(每包1K)
39
+ chunk_size = 1024
40
+ total_chunks = (len(big_data) + chunk_size - 1) // chunk_size
41
+
42
+ # 遍历分片并构造数据包
43
+ for chunk_idx, chunk_data in self.data_chunk_generator(big_data, chunk_size):
44
+ frame = self.build_data_packet_frame(
45
+ task_id=task_id,
46
+ business_type=business_type,
47
+ data_type=data_type,
48
+ total_packets=total_chunks,
49
+ current_packet=chunk_idx,
50
+ chunk_data=chunk_data,
51
+ )
52
+
53
+ print(f"发送包 {chunk_idx + 1}/{total_chunks}, 长度={len(frame)} 字节")
54
+ upload_data(frame)
55
+
56
+ def data_chunk_generator(self, data: bytes, chunk_size: int = 1024):
57
+ """
58
+ 将大数据按固定包大小分片
59
+ :param data: 原始数据字节流
60
+ :param chunk_size: 每包最大字节数(默认1024)
61
+ :yield: (当前包序号, 分片数据)
62
+ """
63
+ total = len(data)
64
+ if total == 0:
65
+ yield (0, b"") # 空数据单独处理
66
+ return
67
+
68
+ total_chunks = (total + chunk_size - 1) // chunk_size
69
+
70
+ for i in range(total_chunks):
71
+ start = i * chunk_size
72
+ end = start + chunk_size
73
+ yield (i, data[start:end])
74
+
75
+ import struct
76
+
77
+ def build_response_frame(
78
+ self, task_id: str, business_type: int, ack_code: int
79
+ ) -> bytes:
80
+ """
81
+ 构建接收应答帧(指令类型 0x80)
82
+ :param task_id: 任务ID(32字节)
83
+ :param business_type: 业务类型(01
84
+ :param ack_code: 0-成功,1-失败,2-执行中
85
+ """
86
+ # 固定字段
87
+ header = bytes([0x24, 0x24])
88
+ func_code = bytes([0x05, 0x07]) # 主功能码5 + 子功能码7
89
+
90
+ # 数据域
91
+ task_bytes = task_id.encode("ascii")[:32].ljust(32, b"\x00")
92
+ business_byte = bytes([business_type])
93
+ cmd_byte = bytes([CommandType.ACK.value])
94
+ ack_byte = bytes([ack_code])
95
+ data_part = task_bytes + business_byte + cmd_byte + ack_byte
96
+
97
+ # 计算长度(功能码2字节 + 数据域37字节 + CRC2字节 = 41)
98
+ frame_length = len(func_code) + len(data_part) + 2
99
+ length_bytes = struct.pack("<H", frame_length)
100
+
101
+ # 构建临时帧并计算CRC
102
+ temp_frame = header + length_bytes + func_code + data_part
103
+ crc = self.crc(temp_frame.hex()).upper()
104
+ full_frame = temp_frame + bytes.fromhex(crc)
105
+ return full_frame
106
+
107
+ def build_total_packets_frame(
108
+ self, task_id: str, business_type: int, data_type: int, total_packets: int
109
+ ) -> bytes:
110
+ """
111
+ 构建上报总包数帧(指令类型 0x81)
112
+ :param total_packets: 总包数(若为0,平台停止接收)
113
+ """
114
+ header = bytes([0x24, 0x24])
115
+ func_code = bytes([0x05, 0x07])
116
+
117
+ # 数据域
118
+ task_bytes = task_id.encode("ascii")[:32].ljust(32, b"\x00")
119
+ business_byte = bytes([business_type])
120
+ cmd_byte = bytes([CommandType.TOTAL_PACKETS.value])
121
+ data_length = struct.pack("<H", 2) # 数据长度固定2字节
122
+ data_type_byte = bytes([data_type])
123
+ total_packets_bytes = struct.pack("<H", total_packets)
124
+
125
+ data_part = (
126
+ task_bytes
127
+ + business_byte
128
+ + cmd_byte
129
+ + data_length
130
+ + data_type_byte
131
+ + total_packets_bytes
132
+ )
133
+
134
+ # 计算长度
135
+ frame_length = len(func_code) + len(data_part) + 2
136
+ length_bytes = struct.pack("<H", frame_length)
137
+
138
+ # 附加CRC
139
+ temp_frame = header + length_bytes + func_code + data_part
140
+ crc = self.crc(temp_frame.hex()).upper()
141
+ return temp_frame + bytes.fromhex(crc)
142
+
143
+ def build_data_packet_frame(
144
+ self,
145
+ task_id: str,
146
+ business_type: int,
147
+ data_type: int,
148
+ total_packets: int,
149
+ current_packet: int,
150
+ chunk_data: bytes,
151
+ ) -> bytes:
152
+ """
153
+ 构建具体分包数据帧(指令类型 0x82)
154
+ """
155
+ header = bytes([0x24, 0x24])
156
+ func_code = bytes([0x05, 0x07])
157
+
158
+ # 数据域
159
+ task_bytes = task_id.encode("ascii")[:32].ljust(32, b"\x00")
160
+ business_byte = bytes([business_type])
161
+ cmd_byte = bytes([CommandType.DATA_PACKET.value])
162
+ data_type_byte = bytes([data_type])
163
+ total_packets_bytes = struct.pack("<H", total_packets)
164
+ current_packet_bytes = struct.pack("<H", current_packet)
165
+ data_length = struct.pack("<H", len(chunk_data))
166
+
167
+ data_part = (
168
+ task_bytes
169
+ + business_byte
170
+ + cmd_byte
171
+ + data_type_byte
172
+ + total_packets_bytes
173
+ + current_packet_bytes
174
+ + data_length
175
+ + chunk_data
176
+ )
177
+
178
+ # 计算长度
179
+ frame_length = len(func_code) + len(data_part) + 2
180
+ length_bytes = struct.pack("<H", frame_length)
181
+
182
+ # 附加CRC
183
+ temp_frame = header + length_bytes + func_code + data_part
184
+ crc = self.crc(temp_frame.hex()).upper()
185
+ return temp_frame + bytes.fromhex(crc)
186
+
187
+ def build_error_frame(
188
+ self, task_id: str, business_type: int, error_type: int
189
+ ) -> bytes:
190
+ """
191
+ 构建报错应答帧(指令类型 0x83)
192
+
193
+ """
194
+ header = bytes([0x24, 0x24])
195
+ func_code = bytes([0x05, 0x07])
196
+
197
+ # 数据域
198
+ task_bytes = task_id.encode("ascii")[:32].ljust(32, b"\x00")
199
+ business_byte = bytes([business_type])
200
+ cmd_byte = bytes([CommandType.ERROR.value])
201
+ error_byte = bytes([error_type])
202
+ data_part = task_bytes + business_byte + cmd_byte + error_byte
203
+
204
+ # 计算长度
205
+ frame_length = len(func_code) + len(data_part) + 2
206
+ length_bytes = struct.pack("<H", frame_length)
207
+
208
+ # 附加CRC
209
+ temp_frame = header + length_bytes + func_code + data_part
210
+ crc = self.crc(temp_frame.hex()).upper()
211
+ return temp_frame + bytes.fromhex(crc)
212
+
213
+ def crc(self, message):
214
+ """获取校验位, message: 除去校验位"""
215
+ crc = 0
216
+ i = 0
217
+ length = len(message) // 2
218
+ while length > 0:
219
+ crc &= 0xFFFFFFFF
220
+ da = crc // 256
221
+ da &= 0xFF
222
+ crc <<= 8
223
+ crc ^= CRC_Table[da ^ int(message[i : i + 2], 16)]
224
+ i += 2
225
+ length -= 1
226
+ return f"{crc & 0xffff:04x}"
@@ -1,17 +1,20 @@
1
- from .exceptions import SnapshotTimeoutError
2
- import asyncio
3
-
4
- def retry(max_attempts=3, delay=0.5):
5
- def decorator(func):
6
- async def wrapper(*args, **kwargs):
7
- attempts = 0
8
- while attempts < max_attempts:
9
- try:
10
- return await func(*args, **kwargs)
11
- except Exception as e:
12
- print(f"尝试 {attempts+1} 失败: {e}")
13
- await asyncio.sleep(delay)
14
- attempts += 1
15
- raise SnapshotTimeoutError(f"操作失败 {max_attempts} 次")
16
- return wrapper
17
- return decorator
1
+ from .exceptions import SnapshotTimeoutError
2
+ import asyncio
3
+
4
+
5
+ def retry(max_attempts=3, delay=0.5):
6
+ def decorator(func):
7
+ async def wrapper(*args, **kwargs):
8
+ attempts = 0
9
+ while attempts < max_attempts:
10
+ try:
11
+ return await func(*args, **kwargs)
12
+ except Exception as e:
13
+ print(f"尝试 {attempts+1} 失败: {e}")
14
+ await asyncio.sleep(delay)
15
+ attempts += 1
16
+ raise SnapshotTimeoutError(f"操作失败 {max_attempts} 次")
17
+
18
+ return wrapper
19
+
20
+ return decorator
@@ -1,3 +1,3 @@
1
- from . import api, types, core, errors
2
-
3
- __all__ = ["api", "core", "errors", "types"]
1
+ from . import api, types, core, errors
2
+
3
+ __all__ = ["api", "core", "errors", "types"]
@@ -1,3 +1,3 @@
1
- from .service import upload, upload_service
2
-
3
- __all__ = ["upload", "upload_service"]
1
+ from .service import upload, upload_service
2
+
3
+ __all__ = ["upload", "upload_service"]
@@ -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.upload.errors.base import UploadBaseError
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 UploadBaseError
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.upload.errors.base import UploadBaseError
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 UploadBaseError
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,3 +1,3 @@
1
- from . import upload_service, data_adapter
2
-
3
- __all__ = ["upload_service", "data_adapter"]
1
+ from . import upload_service, data_adapter
2
+
3
+ __all__ = ["upload_service", "data_adapter"]
@@ -1,5 +1,5 @@
1
- from .base import BaseDataAdapter
2
- from .csv import CSVDataAdapter
3
-
4
-
5
- __all__ = ["BaseDataAdapter", "CSVDataAdapter"]
1
+ from .base import BaseDataAdapter
2
+ from .csv import CSVDataAdapter
3
+
4
+
5
+ __all__ = ["BaseDataAdapter", "CSVDataAdapter"]
@@ -1,9 +1,9 @@
1
- from abc import ABCMeta, abstractmethod
2
-
3
-
4
- class BaseDataAdapter(metaclass=ABCMeta):
5
- data_type = None
6
-
7
- @abstractmethod
8
- def parse_data(self, data):
9
- ...
1
+ from abc import ABCMeta, abstractmethod
2
+
3
+
4
+ class BaseDataAdapter(metaclass=ABCMeta):
5
+ data_type = None
6
+
7
+ @abstractmethod
8
+ def parse_data(self, data):
9
+ ...