burst-link-protocol 1.0.5__cp311-cp311-win_amd64.whl → 1.1.4__cp311-cp311-win_amd64.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.
- burst_interface_c.cp311-win_amd64.pyd +0 -0
- burst_interface_c.pyi +18 -0
- burst_link_protocol/__init__.py +3 -2
- burst_link_protocol/serial_burst_interface.py +233 -0
- {burst_link_protocol-1.0.5.dist-info → burst_link_protocol-1.1.4.dist-info}/METADATA +4 -1
- burst_link_protocol-1.1.4.dist-info/RECORD +9 -0
- {burst_link_protocol-1.0.5.dist-info → burst_link_protocol-1.1.4.dist-info}/WHEEL +1 -1
- burst_link_protocol-1.0.5.dist-info/RECORD +0 -8
- {burst_link_protocol-1.0.5.dist-info → burst_link_protocol-1.1.4.dist-info}/licenses/LICENSE +0 -0
Binary file
|
burst_interface_c.pyi
CHANGED
@@ -6,3 +6,21 @@ class BurstInterfaceC:
|
|
6
6
|
def decode(self, data: bytes, fail_on_crc_error: bool = False) -> list: ...
|
7
7
|
|
8
8
|
def encode(self, packets: list) -> bytes: ...
|
9
|
+
|
10
|
+
@property
|
11
|
+
def bytes_handled(self) -> int: ...
|
12
|
+
|
13
|
+
@property
|
14
|
+
def bytes_processed(self) -> int: ...
|
15
|
+
|
16
|
+
@property
|
17
|
+
def packets_processed(self) -> int: ...
|
18
|
+
|
19
|
+
@property
|
20
|
+
def crc_errors(self) -> int: ...
|
21
|
+
|
22
|
+
@property
|
23
|
+
def overflow_errors(self) -> int: ...
|
24
|
+
|
25
|
+
@property
|
26
|
+
def decode_errors(self) -> int: ...
|
burst_link_protocol/__init__.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
from burst_interface_c import
|
1
|
+
from burst_interface_c import BurstInterfaceC
|
2
2
|
from .main import BurstInterfacePy
|
3
|
+
from .serial_burst_interface import SerialBurstInterface
|
3
4
|
|
4
|
-
__all__ = ["BurstInterfaceC", "BurstInterfacePy"]
|
5
|
+
__all__ = ["BurstInterfaceC", "BurstInterfacePy", "SerialBurstInterface"]
|
@@ -0,0 +1,233 @@
|
|
1
|
+
from burst_interface_c import BurstInterfaceC
|
2
|
+
import serial
|
3
|
+
import time
|
4
|
+
import threading
|
5
|
+
import asyncio
|
6
|
+
import janus
|
7
|
+
from pydantic import BaseModel, Field
|
8
|
+
|
9
|
+
def to_si(value: float, suffix: str) -> str:
|
10
|
+
"""
|
11
|
+
Convert a value to a string with SI suffix.
|
12
|
+
"""
|
13
|
+
if value == 0:
|
14
|
+
return "0"
|
15
|
+
elif value < 1e-3:
|
16
|
+
return f"{value:.2f} {suffix}"
|
17
|
+
elif value < 1e3:
|
18
|
+
return f"{value:.2f} {suffix}"
|
19
|
+
elif value < 1e6:
|
20
|
+
return f"{value / 1e3:.2f} k{suffix}"
|
21
|
+
elif value < 1e9:
|
22
|
+
return f"{value / 1e6:.2f} M{suffix}"
|
23
|
+
else:
|
24
|
+
return f"{value / 1e9:.2f} G{suffix}"
|
25
|
+
|
26
|
+
|
27
|
+
class BurstSerialStatistics(BaseModel):
|
28
|
+
last_update_timestamp: float = Field(default_factory=time.time)
|
29
|
+
|
30
|
+
bytes_handled: int = 0
|
31
|
+
bytes_processed: int = 0
|
32
|
+
packets_processed: int = 0
|
33
|
+
crc_errors: int = 0
|
34
|
+
overflow_errors: int = 0
|
35
|
+
decode_errors: int = 0
|
36
|
+
|
37
|
+
handled_bytes_per_second: float = 0
|
38
|
+
processed_bytes_per_second: float = 0
|
39
|
+
processed_packets_per_second: float = 0
|
40
|
+
|
41
|
+
def update(
|
42
|
+
self,
|
43
|
+
bytes_handled,
|
44
|
+
bytes_processed,
|
45
|
+
packets_processed,
|
46
|
+
crc_errors,
|
47
|
+
overflow_errors,
|
48
|
+
decode_errors,
|
49
|
+
):
|
50
|
+
|
51
|
+
now = time.time()
|
52
|
+
if now - self.last_update_timestamp > 1:
|
53
|
+
delta_time = now - self.last_update_timestamp
|
54
|
+
self.last_update_timestamp = now
|
55
|
+
|
56
|
+
self.handled_bytes_per_second = (
|
57
|
+
(bytes_handled - self.bytes_handled) / delta_time
|
58
|
+
)
|
59
|
+
self.processed_bytes_per_second = (
|
60
|
+
(bytes_processed - self.bytes_processed) / delta_time
|
61
|
+
)
|
62
|
+
self.processed_packets_per_second = (
|
63
|
+
(packets_processed - self.packets_processed) / delta_time
|
64
|
+
)
|
65
|
+
|
66
|
+
self.bytes_handled = bytes_handled
|
67
|
+
self.bytes_processed = bytes_processed
|
68
|
+
self.packets_processed = packets_processed
|
69
|
+
self.crc_errors = crc_errors
|
70
|
+
self.overflow_errors = overflow_errors
|
71
|
+
self.decode_errors = decode_errors
|
72
|
+
|
73
|
+
return self
|
74
|
+
|
75
|
+
def __str__(self):
|
76
|
+
return (
|
77
|
+
f"Byte Raw: {to_si(self.bytes_handled, 'B')} ({to_si(self.handled_bytes_per_second*8, 'bps')}), "
|
78
|
+
f"Bytes processed: {to_si(self.bytes_processed, 'B')} ({to_si(self.processed_bytes_per_second*8, 'bps')}), "
|
79
|
+
f"Packets processed: {self.packets_processed} ({to_si(self.processed_packets_per_second, 'packets/s')}), "
|
80
|
+
f"Errors (CRC: {self.crc_errors}, Overflow: {self.overflow_errors}, Decode: {self.decode_errors})"
|
81
|
+
)
|
82
|
+
|
83
|
+
class SerialBurstInterface:
|
84
|
+
debug_timings = False
|
85
|
+
debug_io = False
|
86
|
+
|
87
|
+
kill = False
|
88
|
+
block_size = 1000
|
89
|
+
RATE_CHECK_INTERVAL = 1
|
90
|
+
|
91
|
+
interface: BurstInterfaceC
|
92
|
+
last_rate_timestamp: float = 0
|
93
|
+
|
94
|
+
statitsics: BurstSerialStatistics
|
95
|
+
|
96
|
+
@classmethod
|
97
|
+
def from_serial(cls, port: str, bitrate: int):
|
98
|
+
serial_handle: serial.Serial = serial.Serial(port, bitrate, timeout=0.5)
|
99
|
+
serial_handle.set_buffer_size(rx_size=100 * 1024, tx_size=100 * 1024) # type: ignore
|
100
|
+
return cls(serial_handle)
|
101
|
+
|
102
|
+
def __init__(self, serial_handle: serial.Serial):
|
103
|
+
self.handle = serial_handle
|
104
|
+
self.handle.reset_input_buffer()
|
105
|
+
self.handle.reset_output_buffer()
|
106
|
+
|
107
|
+
self.current_stats = BurstSerialStatistics()
|
108
|
+
self.statitsics = BurstSerialStatistics()
|
109
|
+
|
110
|
+
self.receive_task_handle = threading.Thread(target=self.receive_task, daemon=True)
|
111
|
+
self.transmit_task_handle = threading.Thread(target=self.transmit_task, daemon=True)
|
112
|
+
|
113
|
+
self.interface = BurstInterfaceC()
|
114
|
+
self.transmit_packet_queue = janus.Queue()
|
115
|
+
self.receive_packet_queue = janus.Queue()
|
116
|
+
|
117
|
+
self.receive_task_handle.start()
|
118
|
+
self.transmit_task_handle.start()
|
119
|
+
|
120
|
+
@property
|
121
|
+
def statistics(self):
|
122
|
+
return self.current_stats.update(
|
123
|
+
self.interface.bytes_handled,
|
124
|
+
self.interface.bytes_processed,
|
125
|
+
self.interface.packets_processed,
|
126
|
+
self.interface.crc_errors,
|
127
|
+
self.interface.overflow_errors,
|
128
|
+
self.interface.decode_errors,
|
129
|
+
)
|
130
|
+
|
131
|
+
def close(self):
|
132
|
+
self.kill = True
|
133
|
+
self.handle.close()
|
134
|
+
self.transmit_packet_queue.close()
|
135
|
+
self.receive_packet_queue.close()
|
136
|
+
|
137
|
+
def receive_task(self):
|
138
|
+
try:
|
139
|
+
while True:
|
140
|
+
# Read incoming data
|
141
|
+
data = self.handle.read(self.block_size)
|
142
|
+
|
143
|
+
if self.kill:
|
144
|
+
break
|
145
|
+
|
146
|
+
if data:
|
147
|
+
if self.debug_io:
|
148
|
+
print(f"Received burst frame: {' '.join([f'{x:02X}' for x in data])}, length: {len(data)}")
|
149
|
+
try:
|
150
|
+
decoded_packets = self.interface.decode(data, fail_on_crc_error=True)
|
151
|
+
except Exception as e:
|
152
|
+
print(f"Error decoding: {e}")
|
153
|
+
continue
|
154
|
+
|
155
|
+
for packet in decoded_packets:
|
156
|
+
# put all packets in the receive queue
|
157
|
+
if self.debug_io:
|
158
|
+
print(f"Received: {packet}")
|
159
|
+
|
160
|
+
self.receive_packet_queue.sync_q.put(packet)
|
161
|
+
|
162
|
+
time.sleep(0.001)
|
163
|
+
|
164
|
+
except Exception as e:
|
165
|
+
print(f"Error in read task: {e}")
|
166
|
+
self.close()
|
167
|
+
|
168
|
+
def transmit_task(self):
|
169
|
+
try:
|
170
|
+
while True:
|
171
|
+
packet = self.transmit_packet_queue.sync_q.get()
|
172
|
+
if self.debug_io:
|
173
|
+
print(f"Transmitting packet: {' '.join([f'{x:02X}' for x in packet])}")
|
174
|
+
|
175
|
+
data = self.interface.encode([packet])
|
176
|
+
|
177
|
+
if self.debug_io:
|
178
|
+
from cobs import cobs
|
179
|
+
|
180
|
+
daat = cobs.decode(data[:-1])
|
181
|
+
# print in space separated hex
|
182
|
+
print(f"Transmitting burst frame: {' '.join([f'{x:02X}' for x in daat])}")
|
183
|
+
|
184
|
+
# print raw frame
|
185
|
+
print(f"Transmitting 'raw' burst frame: {' '.join([f'{x:02X}' for x in data])}")
|
186
|
+
self.handle.write(data)
|
187
|
+
|
188
|
+
except Exception as e:
|
189
|
+
print(f"Error in transmit task: {e}")
|
190
|
+
self.close()
|
191
|
+
|
192
|
+
async def send(self, data: bytes):
|
193
|
+
await self.transmit_packet_queue.async_q.put(data)
|
194
|
+
|
195
|
+
async def flush_receive_queue(self):
|
196
|
+
while not self.receive_packet_queue.async_q.empty():
|
197
|
+
self.receive_packet_queue.async_q.get_nowait()
|
198
|
+
|
199
|
+
async def send_with_response(self, data: bytes):
|
200
|
+
# Flush all other packets
|
201
|
+
await self.flush_receive_queue()
|
202
|
+
|
203
|
+
start_time = time.time()
|
204
|
+
await self.transmit_packet_queue.async_q.put(data)
|
205
|
+
response = await self.receive_packet_queue.async_q.get()
|
206
|
+
end_time = time.time()
|
207
|
+
|
208
|
+
if self.debug_timings:
|
209
|
+
print(f"Time taken: {(end_time - start_time) * 1000} ms for {len(data)} bytes")
|
210
|
+
|
211
|
+
return response
|
212
|
+
|
213
|
+
async def receive(self):
|
214
|
+
return await self.receive_packet_queue.async_q.get()
|
215
|
+
|
216
|
+
def receive_all(self):
|
217
|
+
packets = []
|
218
|
+
while not self.receive_packet_queue.sync_q.empty():
|
219
|
+
packets.append(self.receive_packet_queue.sync_q.get())
|
220
|
+
return packets
|
221
|
+
|
222
|
+
|
223
|
+
async def main():
|
224
|
+
interface = SerialBurstInterface.from_serial("COM4", 115200)
|
225
|
+
|
226
|
+
for i in range(1000):
|
227
|
+
response = await interface.send_with_response(10 * f"Hello World {i}!".encode())
|
228
|
+
print(f"Received: {response}")
|
229
|
+
await asyncio.sleep(0.01)
|
230
|
+
|
231
|
+
|
232
|
+
if __name__ == "__main__":
|
233
|
+
asyncio.run(main())
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: burst-link-protocol
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.1.4
|
4
4
|
Summary: Binary Utility for Reliable Stream Transfer (BURST) is a library for encoding and decoding binary data streams into and from a byte stream.
|
5
5
|
Author-Email: Floris vernieuwe <floris@vernieuwe.eu>
|
6
6
|
License-File: LICENSE
|
@@ -14,6 +14,8 @@ Requires-Dist: pytest-cov<7.0.0,>=6.0.0
|
|
14
14
|
Requires-Dist: pytest-benchmark<6.0.0,>=5.1.0
|
15
15
|
Requires-Dist: scikit-build-core>=0.10.7
|
16
16
|
Requires-Dist: nanobind>=2.5.0
|
17
|
+
Requires-Dist: pyserial>=3.5
|
18
|
+
Requires-Dist: janus>=2.0.0
|
17
19
|
Provides-Extra: dev
|
18
20
|
Requires-Dist: scikit-build-core[pyproject]<0.11.0,>=0.10.7; extra == "dev"
|
19
21
|
Requires-Dist: nanobind<3.0.0,>=2.5.0; extra == "dev"
|
@@ -61,6 +63,7 @@ uv pip install --reinstall --no-build-isolation -ve .
|
|
61
63
|
Auto rebuild on run
|
62
64
|
```sh
|
63
65
|
uv pip install --reinstall --no-build-isolation -Ceditable.rebuild=true -ve .
|
66
|
+
# or
|
64
67
|
pip install --no-build-isolation -Ceditable.rebuild=true -ve .
|
65
68
|
```
|
66
69
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+
burst_interface_c.cp311-win_amd64.pyd,sha256=CRNcugQv4Ci4ZDiCRwN5-mnQZj_FGW4FPCaIH4bWlfI,558080
|
2
|
+
burst_interface_c.pyi,sha256=qoGRk5fn2FwTtK-Uwh0KJjyqKVfe7Sg2m6nwMKARSO0,554
|
3
|
+
burst_link_protocol/__init__.py,sha256=CeP2vD6WrzeXvMla7nzp3zbOAJqbi4sOYpM1gviF-Xk,218
|
4
|
+
burst_link_protocol/main.py,sha256=Eicgf3yp8DxnI5SspCKD7jm5C4tNZWKKBoYdCfGrUis,1211
|
5
|
+
burst_link_protocol/serial_burst_interface.py,sha256=DurK-J8OlRuzkGrP7DNJLbemVa4zJYqHhqCrT0NflOs,7858
|
6
|
+
burst_link_protocol-1.1.4.dist-info/METADATA,sha256=grJVqJRLZaG6QEXc1jF-8EDBVkFWXTOp1-kMYawrZ2I,2599
|
7
|
+
burst_link_protocol-1.1.4.dist-info/WHEEL,sha256=snOjF3_Qdfl83AvEPF_jBYJIRsss89-Tk83TV05TAGs,106
|
8
|
+
burst_link_protocol-1.1.4.dist-info/licenses/LICENSE,sha256=ES97GlwZLdiS8tIJLfRhCRha2fXrcp6sl3D0jDUoh98,17097
|
9
|
+
burst_link_protocol-1.1.4.dist-info/RECORD,,
|
@@ -1,8 +0,0 @@
|
|
1
|
-
burst_interface_c.cp311-win_amd64.pyd,sha256=G74OFBjx8COo9W9a0lTI1rOkjTLNSp9Prxhhhb4MTiw,595968
|
2
|
-
burst_interface_c.pyi,sha256=XeNtZqvqT2bwCE7Le_DeKOiGPn83dw5WSZteHMzLo0A,201
|
3
|
-
burst_link_protocol/__init__.py,sha256=KISkm1e-VcDx29uyFp-4M76cox3LBfcPCOrrv9gv1QM,135
|
4
|
-
burst_link_protocol/main.py,sha256=Eicgf3yp8DxnI5SspCKD7jm5C4tNZWKKBoYdCfGrUis,1211
|
5
|
-
burst_link_protocol-1.0.5.dist-info/METADATA,sha256=9_-fIKdU8nGot4HR9imtOpZaccCfm953TF6bD1SKABE,2536
|
6
|
-
burst_link_protocol-1.0.5.dist-info/WHEEL,sha256=xGZmaOXqwLlhmjR8ikRHIC1Wm-k3ULVrL-oi9GC-Mmc,106
|
7
|
-
burst_link_protocol-1.0.5.dist-info/licenses/LICENSE,sha256=ES97GlwZLdiS8tIJLfRhCRha2fXrcp6sl3D0jDUoh98,17097
|
8
|
-
burst_link_protocol-1.0.5.dist-info/RECORD,,
|
{burst_link_protocol-1.0.5.dist-info → burst_link_protocol-1.1.4.dist-info}/licenses/LICENSE
RENAMED
File without changes
|