solax-py-library 1.0.0.12__py3-none-any.whl → 1.0.0.14__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 -0
  3. solax_py_library/snap_shot/address.py +67 -0
  4. solax_py_library/snap_shot/base_modbus.py +14 -0
  5. solax_py_library/snap_shot/core.py +219 -0
  6. solax_py_library/snap_shot/exceptions.py +4 -0
  7. solax_py_library/snap_shot/parser.py +224 -0
  8. solax_py_library/snap_shot/untils.py +17 -0
  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 +86 -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 +40 -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.12.dist-info → solax_py_library-1.0.0.14.dist-info}/METADATA +4 -4
  27. solax_py_library-1.0.0.14.dist-info/RECORD +29 -0
  28. {solax_py_library-1.0.0.12.dist-info → solax_py_library-1.0.0.14.dist-info}/WHEEL +1 -1
  29. solax_py_library-1.0.0.12.dist-info/RECORD +0 -22
@@ -1 +1 @@
1
- __all__ = ["upload"]
1
+ __all__ = ["upload", "snap_shot"]
@@ -0,0 +1,4 @@
1
+ from .core import SnapshotCore
2
+ from .parser import Parser
3
+
4
+ __all__ = ["SnapshotCore", "Parser"]
@@ -0,0 +1,67 @@
1
+ from enum import Enum
2
+
3
+
4
+ class SnapshotMCUSource(Enum):
5
+ MCU_SOURCE_MDSP = 0
6
+ MCU_SOURCE_SDSP = 1
7
+
8
+
9
+ class SnapshotExportDevice(Enum):
10
+ EXPORT_DEVICE_UART = 0
11
+ EXPORT_DEVICE_USB = 1
12
+
13
+
14
+ class SnapshotStartResult(Enum):
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(Enum):
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
+ CRC_Table = [0x0000, 0x8005, 0x1800f, 0x1000a, 0x3801b, 0x3001e, 0x20014, 0x28011, 0x78033, 0x70036,
36
+ 0x6003c, 0x68039, 0x40028, 0x4802d, 0x58027, 0x50022, 0xf8063, 0xf0066, 0xe006c, 0xe8069,
37
+ 0xc0078, 0xc807d, 0xd8077, 0xd0072, 0x80050, 0x88055, 0x9805f, 0x9005a, 0xb804b, 0xb004e,
38
+ 0xa0044, 0xa8041, 0x1f80c3, 0x1f00c6, 0x1e00cc, 0x1e80c9, 0x1c00d8, 0x1c80dd, 0x1d80d7, 0x1d00d2,
39
+ 0x1800f0, 0x1880f5, 0x1980ff, 0x1900fa, 0x1b80eb, 0x1b00ee, 0x1a00e4, 0x1a80e1, 0x1000a0, 0x1080a5,
40
+ 0x1180af, 0x1100aa, 0x1380bb, 0x1300be, 0x1200b4, 0x1280b1, 0x178093, 0x170096, 0x16009c, 0x168099,
41
+ 0x140088, 0x14808d, 0x158087, 0x150082, 0x3f8183, 0x3f0186, 0x3e018c, 0x3e8189, 0x3c0198, 0x3c819d,
42
+ 0x3d8197, 0x3d0192, 0x3801b0, 0x3881b5, 0x3981bf, 0x3901ba, 0x3b81ab, 0x3b01ae, 0x3a01a4, 0x3a81a1,
43
+ 0x3001e0, 0x3081e5, 0x3181ef, 0x3101ea, 0x3381fb, 0x3301fe, 0x3201f4, 0x3281f1, 0x3781d3, 0x3701d6,
44
+ 0x3601dc, 0x3681d9, 0x3401c8, 0x3481cd, 0x3581c7, 0x3501c2, 0x200140, 0x208145, 0x21814f, 0x21014a,
45
+ 0x23815b, 0x23015e, 0x220154, 0x228151, 0x278173, 0x270176, 0x26017c, 0x268179, 0x240168, 0x24816d,
46
+ 0x258167, 0x250162, 0x2f8123, 0x2f0126, 0x2e012c, 0x2e8129, 0x2c0138, 0x2c813d, 0x2d8137, 0x2d0132,
47
+ 0x280110, 0x288115, 0x29811f, 0x29011a, 0x2b810b, 0x2b010e, 0x2a0104, 0x2a8101, 0x7f8303, 0x7f0306,
48
+ 0x7e030c, 0x7e8309, 0x7c0318, 0x7c831d, 0x7d8317, 0x7d0312, 0x780330, 0x788335, 0x79833f, 0x79033a,
49
+ 0x7b832b, 0x7b032e, 0x7a0324, 0x7a8321, 0x700360, 0x708365, 0x71836f, 0x71036a, 0x73837b, 0x73037e,
50
+ 0x720374, 0x728371, 0x778353, 0x770356, 0x76035c, 0x768359, 0x740348, 0x74834d, 0x758347, 0x750342,
51
+ 0x6003c0, 0x6083c5, 0x6183cf, 0x6103ca, 0x6383db, 0x6303de, 0x6203d4, 0x6283d1, 0x6783f3, 0x6703f6,
52
+ 0x6603fc, 0x6683f9, 0x6403e8, 0x6483ed, 0x6583e7, 0x6503e2, 0x6f83a3, 0x6f03a6, 0x6e03ac, 0x6e83a9,
53
+ 0x6c03b8, 0x6c83bd, 0x6d83b7, 0x6d03b2, 0x680390, 0x688395, 0x69839f, 0x69039a, 0x6b838b, 0x6b038e,
54
+ 0x6a0384, 0x6a8381, 0x400280, 0x408285, 0x41828f, 0x41028a, 0x43829b, 0x43029e, 0x420294, 0x428291,
55
+ 0x4782b3, 0x4702b6, 0x4602bc, 0x4682b9, 0x4402a8, 0x4482ad, 0x4582a7, 0x4502a2, 0x4f82e3, 0x4f02e6,
56
+ 0x4e02ec, 0x4e82e9, 0x4c02f8, 0x4c82fd, 0x4d82f7, 0x4d02f2, 0x4802d0, 0x4882d5, 0x4982df, 0x4902da,
57
+ 0x4b82cb, 0x4b02ce, 0x4a02c4, 0x4a82c1, 0x5f8243, 0x5f0246, 0x5e024c, 0x5e8249, 0x5c0258, 0x5c825d,
58
+ 0x5d8257, 0x5d0252, 0x580270, 0x588275, 0x59827f, 0x59027a, 0x5b826b, 0x5b026e, 0x5a0264, 0x5a8261,
59
+ 0x500220, 0x508225, 0x51822f, 0x51022a, 0x53823b, 0x53023e, 0x520234, 0x528231, 0x578213, 0x570216,
60
+ 0x56021c, 0x568219, 0x540208, 0x54820d, 0x558207, 0x550202]
61
+
62
+
63
+ class CommandType(Enum):
64
+ ACK = 0x80 # 接收应答
65
+ TOTAL_PACKETS = 0x81 # 上报总包数
66
+ DATA_PACKET = 0x82 # 具体分包数据
67
+ ERROR = 0x83 # 报错应答
@@ -0,0 +1,14 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List
3
+
4
+ class ModbusClientBase(ABC):
5
+ @abstractmethod
6
+ def read_registers(self, address: int, quantity_of_x: int) -> List[int]:
7
+ """抽象读取寄存器方法"""
8
+ pass
9
+
10
+ @abstractmethod
11
+ def write_registers(self, address: int, values: List[int]) -> bool:
12
+ """抽象写入寄存器方法"""
13
+ pass
14
+
@@ -0,0 +1,219 @@
1
+ from typing import Dict, Optional, List
2
+ import asyncio
3
+
4
+ from .base_modbus import ModbusClientBase
5
+ from .address import *
6
+ from .untils import retry
7
+
8
+
9
+ class SnapshotCore:
10
+ def __init__(self, modbus_client: ModbusClientBase, snap_except=None):
11
+ self.modbus = modbus_client
12
+ self.all_snap_shot_data: Dict[int, List[int]] = {}
13
+ self.snap_except = snap_except
14
+
15
+ def __getitem__(self, index: int) -> Optional[List[int]]:
16
+ return self.all_snap_shot_data.get(index)
17
+
18
+ async def __aiter__(self):
19
+ for key in self.all_snap_shot_data:
20
+ yield key, self.all_snap_shot_data[key]
21
+
22
+ @property
23
+ def all_snap_data(self):
24
+ return self.all_snap_shot_data.copy()
25
+
26
+ @retry(max_attempts=3, delay=0.5)
27
+ async def _set_MCU_source(self):
28
+ print("step 1 设置芯片源")
29
+ result = await self.modbus.write_registers(SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_MCUSOURCE.value,
30
+ [SnapshotMCUSource.MCU_SOURCE_MDSP.value])
31
+ print(f'step 1 设置芯片源回复:0x{result[0]:04x}')
32
+
33
+ @retry(max_attempts=3, delay=0.5)
34
+ async def _set_export_device(self): # 设置输出设备
35
+ print("setp 2 设置导出设备")
36
+
37
+ result = await self.modbus.write_registers(SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_EXPORTDEVICE.value,
38
+ [SnapshotExportDevice.EXPORT_DEVICE_UART.value])
39
+ print(f'setp 2 设置导出设备:0x{result[0]:04x}')
40
+
41
+ @retry(max_attempts=3, delay=0.5)
42
+ async def _get_snapshot_total_number(self) -> int: # 取得快照总数
43
+ print("step 3 获取快照总数")
44
+ try_number = 3
45
+ number = 0
46
+ result = [0]
47
+ while number < try_number:
48
+ result = await self.modbus.read_registers(
49
+ SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_TOTALNUMBER.value, 1)
50
+ print(f"step 3 获取快照总数 :{result}")
51
+ if result[0] > 0:
52
+ break
53
+ number += 1
54
+ return result[0]
55
+
56
+ @retry(max_attempts=3, delay=0.5)
57
+ async def _start_snap_shot(self): # 获取快照开始结果
58
+ print("第4步 录播开始")
59
+ result = False
60
+ readresultcnt = 0
61
+ while readresultcnt < 3:
62
+ result = await self.modbus.write_registers(
63
+ SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_START.value,
64
+ [SnapshotStartResult.SNAPSHOT_START_START.value])
65
+ print(f' 设置录播开始{result}')
66
+ await asyncio.sleep(1)
67
+ response = await self.modbus.read_registers(
68
+ SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_START.value, 1)
69
+ print(f'录波读取设置变量:{response}')
70
+
71
+ if response[0] == SnapshotStartResult.SNAPSHOT_START_SUCCESS.value:
72
+ print(f'设置录播开始成功')
73
+ result = True
74
+ break
75
+ else:
76
+ print(f'设置录播开始失败')
77
+ result = False
78
+ readresultcnt += 1
79
+
80
+ return result
81
+
82
+ @retry(max_attempts=3, delay=0.5)
83
+ async def _get_snapshot_dataPara(self): # 取得快照数据参数 数据总数 通道数 通道数据深度
84
+ print("step 5 获取快照数据参数")
85
+ result = await self.modbus.read_registers(SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_DATASIZE.value, 3)
86
+ print(f"step 5 获取快照数据参数:{result}")
87
+ return result[0]
88
+
89
+ @retry(max_attempts=3, delay=0.5)
90
+ async def _get_Single_snap_shot_data(self, index: int, DataNumber: int): # 获取单个快照数据
91
+ readdatanum = 0
92
+ packindex = 0
93
+
94
+ await self._set_snap_shot_data_index(index) # 设置快照索引
95
+ while readdatanum < DataNumber:
96
+ if await self._get_data_pack_read_state() == False: # 获取数据包读取状态
97
+ raise self.snap_except('读取数据包错误')
98
+
99
+ siglepackdatanum = DataNumber - readdatanum
100
+ if siglepackdatanum > 256:
101
+ siglepackdatanum = 256
102
+ print(f"第{index}次快照,第{packindex}包,读取数据个数:{siglepackdatanum}")
103
+
104
+ await self._get_data_pack_index() # 取得数据包索引
105
+ once_data = await self._get_data_pack(siglepackdatanum) # 获取快照数据的单个pack
106
+ self.all_snap_shot_data.setdefault(index, []).extend(once_data)
107
+
108
+ await self._clear_data_pack_read_state() # 清除数据包读取状态 让下位机切pack
109
+ readdatanum += siglepackdatanum
110
+ packindex += 1
111
+ # print(f'self.all_snap_shot_data:{self.all_snap_shot_data}')
112
+
113
+ @retry(max_attempts=3, delay=0.5) #
114
+ async def _set_snap_shot_data_index(self, index: int): # 设置快照数据索引
115
+ print("step 6 设置快照索引")
116
+ result = await self.modbus.write_registers(
117
+ SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_SNAPSHOTINDEX.value, [index])
118
+ print(f'step 6 设置快照索引返回值:0x{result[0]:04x}')
119
+
120
+ @retry(max_attempts=3, delay=0.5)
121
+ async def _get_data_pack_read_state(self): # 取得数据包就绪状态
122
+ #print(f'取得数据包就绪状态')
123
+ readcnt = 0
124
+ result = False
125
+ while readcnt < 100:
126
+ result = await self.modbus.read_registers(
127
+ SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_PACKDATASTATUS.value, 1)
128
+ status = result[0]
129
+ if (status == 1):
130
+ #print(f'数据包就绪状态为:{status},数据包就绪')
131
+ result = True
132
+ break
133
+ else:
134
+ #print(f'数据包就绪状态为:{status},数据包未就绪')
135
+ readcnt += 1
136
+ await asyncio.sleep(0.5)
137
+ return result
138
+
139
+ @retry(max_attempts=5, delay=0.2)
140
+ async def _get_data_pack_index(self): # 取得数据包索引
141
+ # print(f'获取数据包索引')
142
+ result = await self.modbus.read_registers(
143
+ SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_PACKINDEX.value, 1)
144
+ packnum = result[0]
145
+ # print(f'当前数据包的索引:{packnum}')
146
+ return packnum
147
+
148
+ @retry(max_attempts=3, delay=0.5)
149
+ async def _get_data_pack(self, register_num: int) -> List: # 取得快照数据包
150
+ #print(f'取得快照数据包')
151
+ DataPack = []
152
+ if register_num >= 256:
153
+ response = await self.get_data_pack_h_address(64)
154
+ DataPack.extend(response)
155
+ response = await self._get_data_pack_l_address(64)
156
+ DataPack.extend(response)
157
+ elif register_num >= 64 and register_num < 256:
158
+ response = await self.get_data_pack_h_address(64)
159
+ DataPack.extend(response)
160
+ response = await self._get_data_pack_l_address((register_num - 128) // 2)
161
+ DataPack.extend(response)
162
+ else:
163
+ response = await self.get_data_pack_h_address(register_num // 2)
164
+ DataPack.extend(response)
165
+
166
+ # print(f'获取快照数据包返回值:{DataPack}')
167
+ return DataPack
168
+
169
+ @retry(max_attempts=3, delay=0.5)
170
+ async def get_data_pack_h_address(self, register_num: int) -> List: # 取得数据包大小
171
+ data = await self.modbus.read_registers(
172
+ SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_PACKDATA.value, register_num)
173
+ return data
174
+
175
+ @retry(max_attempts=3, delay=0.5)
176
+ async def _get_data_pack_l_address(self, register_num: int) -> List: # 取得数据包大小
177
+ data = await self.modbus.read_registers(
178
+ SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_PACKDATA.value + 64, register_num)
179
+ return data
180
+
181
+ @retry(max_attempts=5, delay=0.2)
182
+ async def _clear_data_pack_read_state(self): # 清除数据包读取状态
183
+ # print(f'清除数据包读取状态')
184
+ response = await self.modbus.write_registers(
185
+ SnapshotRegisterAddress.SNAPSHOT_REGISTERADDRESS_PACKDATASTATUS.value, [0])
186
+ # print(f'清除数据包就绪状态返回值:{response}')
187
+
188
+ @classmethod
189
+ async def start(cls, modbus_client, snap_shot_index: int = 0, snap_exception=None):
190
+ """启动故障录波
191
+ snap_shot_index : 代表第几个故障 如果传0 就默认读取所有故障
192
+ """
193
+ #print'start.................................')
194
+ instance = cls(modbus_client, snap_exception)
195
+ try:
196
+ # 第一步 设置快照芯片源
197
+ await instance._set_MCU_source()
198
+ # 第二步 设置导出设备
199
+ await instance._set_export_device()
200
+ # 第三步 获取设备总数
201
+ total_number = await instance._get_snapshot_total_number()
202
+ if total_number < 1:
203
+ return instance.snap_except("无效的故障录波总数")
204
+ # 第四步 开始录波
205
+ await instance._start_snap_shot()
206
+ # 第5步 获取快照数据参数
207
+ total_data_length = await instance._get_snapshot_dataPara()
208
+ # 第六步 读数据
209
+ # for index in range(1, total_number + 1):
210
+ # await instance._get_Single_snap_shot_data(index, total_data_length)
211
+ await instance._get_Single_snap_shot_data(1, total_data_length)
212
+
213
+ return instance.all_snap_data
214
+
215
+ except Exception as e:
216
+ return instance.snap_except(f"故障录波失败:{e}")
217
+
218
+ async def __aenter__(self):
219
+ return self
@@ -0,0 +1,4 @@
1
+ class SnapshotError(Exception):
2
+ ...
3
+ class SnapshotTimeoutError(SnapshotError):
4
+ ...
@@ -0,0 +1,224 @@
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}"
@@ -0,0 +1,17 @@
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,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
+ ...