burst-link-protocol 1.0.4__tar.gz → 1.1.4__tar.gz
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_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/.vscode/c_cpp_properties.json +1 -1
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/.vscode/tasks.json +2 -1
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/CMakeLists.txt +48 -19
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/PKG-INFO +4 -1
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/README.md +1 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/pyproject.toml +3 -1
- burst_link_protocol-1.0.4/src/decoder.c → burst_link_protocol-1.1.4/src/burst/burst_decoder.c +61 -4
- burst_link_protocol-1.1.4/src/burst/burst_decoder.h +56 -0
- burst_link_protocol-1.1.4/src/burst/burst_encoder.c +120 -0
- burst_link_protocol-1.1.4/src/burst/burst_encoder.h +38 -0
- burst_link_protocol-1.0.4/src/crc.c → burst_link_protocol-1.1.4/src/burst/burst_generic.c +1 -1
- burst_link_protocol-1.1.4/src/burst/burst_generic.h +30 -0
- burst_link_protocol-1.1.4/src/burst_link_protocol/__init__.py +5 -0
- burst_link_protocol-1.1.4/src/burst_link_protocol/serial_burst_interface.py +233 -0
- burst_link_protocol-1.1.4/src/burst_link_protocol.h +7 -0
- burst_link_protocol-1.1.4/src/python_bindings.cpp +75 -0
- burst_link_protocol-1.1.4/test/test_burst_decoding.py +25 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/uv.lock +23 -1
- burst_link_protocol-1.0.4/.github/dependabot.yml +0 -11
- burst_link_protocol-1.0.4/platform/zephyr/CMakeLists.txt +0 -9
- burst_link_protocol-1.0.4/src/burst_interface.h +0 -56
- burst_link_protocol-1.0.4/src/burst_link_protocol/__init__.py +0 -4
- burst_link_protocol-1.0.4/src/crc.h +0 -4
- burst_link_protocol-1.0.4/src/encoder.c +0 -92
- burst_link_protocol-1.0.4/src/python_bindings.cpp +0 -80
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/.github/workflows/pip.yml +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/.github/workflows/wheel.yml +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/.gitignore +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/.vscode/launch.json +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/.vscode/settings.json +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/LICENSE +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/ROADMAP.md +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/docs/design.drawio +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/docs/design.svg +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/src/burst_link_protocol/main.py +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/test/test_burst.py +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/test/test_c.py +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/test/test_c_validation.py +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/test/test_python_validation.py +0 -0
- {burst_link_protocol-1.0.4 → burst_link_protocol-1.1.4}/test/test_speed.py +0 -0
@@ -4,7 +4,7 @@
|
|
4
4
|
"name": "Win32",
|
5
5
|
"includePath": [
|
6
6
|
"${workspaceFolder}/**",
|
7
|
-
"${workspaceFolder
|
7
|
+
"${workspaceFolder/.venv/lib/python3.13/site-packages/nanobind/include/nanobind", //To be fixed
|
8
8
|
"${env:PYTHON_INCLUDE}"
|
9
9
|
],
|
10
10
|
"defines": [
|
@@ -1,8 +1,33 @@
|
|
1
1
|
cmake_minimum_required(VERSION 3.15...3.26)
|
2
|
+
# If espidf is reading this CMakeLists.txt, skip so its ignored
|
2
3
|
|
3
|
-
project(i_think_this_name_does_not_matter LANGUAGES C CXX)
|
4
4
|
|
5
|
-
|
5
|
+
# If ESP-IDF is reading this CMakeLists.txt, register it as an ESP-IDF component
|
6
|
+
if(DEFINED ENV{IDF_PATH})
|
7
|
+
idf_component_register(SRCS
|
8
|
+
"src/burst/burst_decoder.c"
|
9
|
+
"src/burst/burst_encoder.c"
|
10
|
+
"src/burst/burst_generic.c"
|
11
|
+
INCLUDE_DIRS "src")
|
12
|
+
|
13
|
+
return()
|
14
|
+
endif()
|
15
|
+
|
16
|
+
# If Zephyr is reading this CMakeLists.txt, register it as a Zephyr module
|
17
|
+
if(Zephyr_FOUND)
|
18
|
+
zephyr_library_named(burst-link-protocol)
|
19
|
+
zephyr_library_sources(
|
20
|
+
"src/burst/burst_decoder.c"
|
21
|
+
"src/burst/burst_encoder.c"
|
22
|
+
"src/burst/burst_generic.c"
|
23
|
+
)
|
24
|
+
|
25
|
+
target_include_directories(burst-link-protocol PUBLIC src)
|
26
|
+
return()
|
27
|
+
endif()
|
28
|
+
|
29
|
+
project(i_think_this_name_does_not_matter LANGUAGES C CXX)
|
30
|
+
if(NOT SKBUILD)
|
6
31
|
message(WARNING "\
|
7
32
|
This CMake file is meant to be executed using 'scikit-build'. Running
|
8
33
|
it directly will almost certainly not produce the desired result. If
|
@@ -30,30 +55,34 @@ find_package(Python 3.8
|
|
30
55
|
REQUIRED COMPONENTS Interpreter Development.Module
|
31
56
|
OPTIONAL_COMPONENTS Development.SABIModule)
|
32
57
|
|
33
|
-
|
58
|
+
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
34
59
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
|
35
60
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
|
36
61
|
endif()
|
37
62
|
|
38
63
|
option(COVERAGE "Enable coverage reporting" ON)
|
39
64
|
if(COVERAGE)
|
40
|
-
|
41
|
-
|
65
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage")
|
66
|
+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage")
|
42
67
|
endif()
|
43
68
|
|
44
69
|
execute_process(
|
45
|
-
|
46
|
-
|
47
|
-
|
70
|
+
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
|
71
|
+
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR)
|
72
|
+
list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}")
|
48
73
|
|
49
74
|
# Import nanobind through CMake's find_package mechanism
|
50
75
|
find_package(nanobind CONFIG REQUIRED)
|
51
76
|
|
52
77
|
# We are now ready to compile the actual extension module
|
53
78
|
nanobind_add_module(
|
79
|
+
|
54
80
|
# Name of the extension
|
55
81
|
burst_interface_c
|
56
82
|
|
83
|
+
# domain name
|
84
|
+
NB_DOMAIN burst_interface_domain
|
85
|
+
|
57
86
|
# Target the stable ABI for Python 3.12+, which reduces
|
58
87
|
# the number of binary wheels that must be built. This
|
59
88
|
# does nothing on older Python versions
|
@@ -68,23 +97,23 @@ nanobind_add_module(
|
|
68
97
|
NB_STATIC
|
69
98
|
|
70
99
|
# Source code goes here
|
71
|
-
src/
|
72
|
-
src/
|
100
|
+
src/burst/burst_decoder.c
|
101
|
+
src/burst/burst_encoder.c
|
102
|
+
src/burst/burst_generic.c
|
73
103
|
src/python_bindings.cpp
|
74
|
-
src/crc.c
|
75
104
|
)
|
76
105
|
|
77
106
|
nanobind_add_stub(
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
107
|
+
|
108
|
+
burst_interface_c_stub
|
109
|
+
INSTALL_TIME
|
110
|
+
MODULE burst_interface_c
|
111
|
+
OUTPUT burst_interface_c.pyi
|
112
|
+
PYTHON_PATH $<TARGET_FILE_DIR:burst_interface_c>
|
113
|
+
DEPENDS burst_interface_c
|
85
114
|
)
|
86
115
|
|
87
116
|
target_include_directories(burst_interface_c PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
88
117
|
|
89
118
|
# Install directive for scikit-build-core
|
90
|
-
install(TARGETS burst_interface_c LIBRARY DESTINATION ".")
|
119
|
+
install(TARGETS burst_interface_c LIBRARY DESTINATION ".")
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "burst-link-protocol"
|
3
|
-
version = "1.
|
3
|
+
version = "1.1.4" # Choose one version reference here
|
4
4
|
description = "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
|
requires-python = ">=3.10,<4.0"
|
6
6
|
authors = [
|
@@ -16,6 +16,8 @@ dependencies = [
|
|
16
16
|
"pytest-benchmark>=5.1.0,<6.0.0",
|
17
17
|
"scikit-build-core>=0.10.7",
|
18
18
|
"nanobind>=2.5.0",
|
19
|
+
"pyserial>=3.5",
|
20
|
+
"janus>=2.0.0",
|
19
21
|
]
|
20
22
|
license-files = ["LICENSE"]
|
21
23
|
|
burst_link_protocol-1.0.4/src/decoder.c → burst_link_protocol-1.1.4/src/burst/burst_decoder.c
RENAMED
@@ -1,11 +1,10 @@
|
|
1
|
+
#include "burst_decoder.h"
|
2
|
+
|
1
3
|
#include <stdbool.h>
|
2
4
|
#include <stddef.h>
|
3
5
|
#include <stdint.h>
|
4
6
|
#include <stdio.h>
|
5
7
|
|
6
|
-
#include "burst_interface.h"
|
7
|
-
#include "crc.h"
|
8
|
-
|
9
8
|
void burst_decoder_init(burst_decoder_t *ctx, uint8_t *buffer, size_t size) {
|
10
9
|
ctx->buffer = buffer;
|
11
10
|
ctx->buffer_size = size;
|
@@ -54,7 +53,7 @@ burst_status_t burst_decoder_complete_packet(burst_decoder_t *ctx) {
|
|
54
53
|
}
|
55
54
|
|
56
55
|
// Calculate the CRC over the packet data excluding the last two CRC bytes.
|
57
|
-
uint16_t computed_crc =
|
56
|
+
uint16_t computed_crc = burst_crc16(ctx->buffer, ctx->out_head - CRC_SIZE);
|
58
57
|
|
59
58
|
// Extract the received CRC from the last two bytes (big-endian).
|
60
59
|
uint16_t received_crc = ((uint16_t)ctx->buffer[ctx->out_head - CRC_SIZE] << 8) | ctx->buffer[ctx->out_head - 1];
|
@@ -82,6 +81,7 @@ burst_status_t burst_decoder_add_byte(burst_decoder_t *ctx, uint8_t byte) {
|
|
82
81
|
return burst_decoder_complete_packet(ctx);
|
83
82
|
}
|
84
83
|
|
84
|
+
/* fallthrough */
|
85
85
|
case COBS_DECODE_READ_CODE:
|
86
86
|
|
87
87
|
// If last code was 0xF, its a overhead byte
|
@@ -119,6 +119,9 @@ burst_status_t burst_decoder_add_byte(burst_decoder_t *ctx, uint8_t byte) {
|
|
119
119
|
|
120
120
|
return BURST_DATA_CONSUMED;
|
121
121
|
}
|
122
|
+
|
123
|
+
// This should never happen, but some compilers are dumb
|
124
|
+
return BURST_DECODE_ERROR;
|
122
125
|
}
|
123
126
|
|
124
127
|
burst_packet_t burst_decoder_get_packet(burst_decoder_t *ctx) {
|
@@ -134,3 +137,57 @@ burst_packet_t burst_decoder_get_packet(burst_decoder_t *ctx) {
|
|
134
137
|
packet.size = ctx->out_head;
|
135
138
|
return packet;
|
136
139
|
}
|
140
|
+
|
141
|
+
void burst_managed_decoder_init(burst_managed_decoder_t *burst_managed_decoder, uint8_t *buffer, size_t size, burst_managed_decoder_callback_t callback,
|
142
|
+
void *user_data) {
|
143
|
+
burst_managed_decoder->callback_function = callback;
|
144
|
+
burst_managed_decoder->user_data = user_data;
|
145
|
+
burst_decoder_init(&burst_managed_decoder->decoder, buffer, size);
|
146
|
+
}
|
147
|
+
|
148
|
+
int burst_managed_decoder_handle_data(burst_managed_decoder_t *burst_managed_decoder, const uint8_t *data, size_t len) {
|
149
|
+
if (len == 0) {
|
150
|
+
return 0; // No data to process
|
151
|
+
}
|
152
|
+
|
153
|
+
burst_managed_decoder->statistics.bytes_ingested += len;
|
154
|
+
size_t bytes_consumed = 0;
|
155
|
+
while (bytes_consumed < len) {
|
156
|
+
uint8_t *data_ptr = (uint8_t *)data + bytes_consumed;
|
157
|
+
size_t data_len = len - bytes_consumed;
|
158
|
+
|
159
|
+
burst_status_t status = bust_decoder_add_data(&burst_managed_decoder->decoder, data_ptr, data_len, &bytes_consumed);
|
160
|
+
|
161
|
+
switch (status) {
|
162
|
+
case BURST_PACKET_READY: {
|
163
|
+
burst_packet_t packet = burst_decoder_get_packet(&burst_managed_decoder->decoder);
|
164
|
+
|
165
|
+
if (packet.size > 0 && burst_managed_decoder->callback_function != NULL) {
|
166
|
+
// Call the callback function with the received data
|
167
|
+
burst_managed_decoder->callback_function(packet.data, packet.size, burst_managed_decoder->user_data);
|
168
|
+
}
|
169
|
+
|
170
|
+
burst_managed_decoder->statistics.bytes_processed += packet.size;
|
171
|
+
burst_managed_decoder->statistics.packets_processed++;
|
172
|
+
|
173
|
+
continue;
|
174
|
+
}
|
175
|
+
|
176
|
+
case BURST_CRC_ERROR:
|
177
|
+
burst_managed_decoder->statistics.crc_errors++;
|
178
|
+
continue;
|
179
|
+
|
180
|
+
case BURST_DECODE_ERROR:
|
181
|
+
burst_managed_decoder->statistics.decode_errors++;
|
182
|
+
continue;
|
183
|
+
|
184
|
+
case BURST_OVERFLOW_ERROR:
|
185
|
+
burst_managed_decoder->statistics.overflow_errors++;
|
186
|
+
continue;
|
187
|
+
|
188
|
+
default:
|
189
|
+
continue;
|
190
|
+
}
|
191
|
+
}
|
192
|
+
return bytes_consumed;
|
193
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#ifndef BURST_DECODER_H
|
2
|
+
#define BURST_DECODER_H
|
3
|
+
|
4
|
+
#include <burst/burst_generic.h>
|
5
|
+
#include <stdbool.h>
|
6
|
+
typedef int (*burst_managed_decoder_callback_t)(const uint8_t *data, size_t length, void *user_data);
|
7
|
+
|
8
|
+
typedef struct
|
9
|
+
{
|
10
|
+
uint32_t bytes_ingested; // Total number of bytes ingested
|
11
|
+
uint32_t bytes_processed; // Total number of bytes after processing
|
12
|
+
uint32_t packets_processed; // Successfully decoded packets
|
13
|
+
|
14
|
+
// Error statistics
|
15
|
+
uint32_t crc_errors;
|
16
|
+
uint32_t overflow_errors;
|
17
|
+
uint32_t decode_errors;
|
18
|
+
} burst_decoder_statistics_t;
|
19
|
+
|
20
|
+
typedef struct
|
21
|
+
{
|
22
|
+
uint8_t *buffer; // Output buffer for decoded data.
|
23
|
+
size_t buffer_size; // Size of the output buffer.
|
24
|
+
size_t out_head; // Current count of decoded bytes stored.
|
25
|
+
enum cobs_decode_inc_state
|
26
|
+
{
|
27
|
+
COBS_DECODE_READ_CODE,
|
28
|
+
COBS_DECODE_RUN,
|
29
|
+
COBS_DECODE_FINISH_RUN
|
30
|
+
} state;
|
31
|
+
uint8_t block, code;
|
32
|
+
|
33
|
+
bool finished; // true if the packet is complete and available in the buffer
|
34
|
+
|
35
|
+
burst_decoder_statistics_t statistics; // Statistics for the decoder
|
36
|
+
|
37
|
+
} burst_decoder_t;
|
38
|
+
|
39
|
+
typedef struct
|
40
|
+
{
|
41
|
+
burst_decoder_t decoder; // Decoder instance
|
42
|
+
burst_decoder_statistics_t statistics; // Statistics for the decoder
|
43
|
+
burst_managed_decoder_callback_t callback_function; // Callback function for decoded packets
|
44
|
+
void *user_data; // User data for the callback function
|
45
|
+
} burst_managed_decoder_t;
|
46
|
+
|
47
|
+
void burst_decoder_init(burst_decoder_t *ctx, uint8_t *buffer, size_t size);
|
48
|
+
void burst_decoder_reset(burst_decoder_t *ctx);
|
49
|
+
burst_status_t burst_decoder_add_byte(burst_decoder_t *ctx, uint8_t byte);
|
50
|
+
burst_status_t bust_decoder_add_data(burst_decoder_t *ctx, const uint8_t *data, size_t size, size_t *consumed_bytes);
|
51
|
+
burst_packet_t burst_decoder_get_packet(burst_decoder_t *ctx);
|
52
|
+
|
53
|
+
void burst_managed_decoder_init(burst_managed_decoder_t *burst_managed_decoder, uint8_t *buffer, size_t size, burst_managed_decoder_callback_t callback, void *user_data);
|
54
|
+
int burst_managed_decoder_handle_data(burst_managed_decoder_t *burst_managed_decoder, const uint8_t *data, size_t len);
|
55
|
+
|
56
|
+
#endif // BURST_DECODER_H
|
@@ -0,0 +1,120 @@
|
|
1
|
+
#include "burst_encoder.h"
|
2
|
+
|
3
|
+
#include <stddef.h>
|
4
|
+
#include <stdint.h>
|
5
|
+
|
6
|
+
// COBS encoding with CRC appending.
|
7
|
+
// The input to be encoded is the raw packet data followed by the two CRC bytes.
|
8
|
+
// The algorithm works by maintaining a "code" (the count of nonzero bytes)
|
9
|
+
// and inserting that count at the start of each block. When a zero is encountered
|
10
|
+
// (or when the block length reaches COBS_MAX_CODE) the block is terminated.
|
11
|
+
burst_status_t burst_encoder_add_packet(burst_encoder_t *ctx, const uint8_t *data, size_t size) {
|
12
|
+
// Compute the CRC over the raw packet data.
|
13
|
+
uint16_t crc = burst_crc16(data, size);
|
14
|
+
|
15
|
+
uint8_t crc_high = (crc >> 8) & 0xFF;
|
16
|
+
uint8_t crc_low = crc & 0xFF;
|
17
|
+
// The total number of bytes to encode: raw data + 2 bytes of CRC.
|
18
|
+
size_t total_bytes = size + CRC_SIZE;
|
19
|
+
|
20
|
+
// Initialize COBS block state.
|
21
|
+
uint8_t code = 1; // Code value starts at 1.
|
22
|
+
// Reserve space for the code byte.
|
23
|
+
if (ctx->out_head >= ctx->buffer_size) return BURST_OVERFLOW_ERROR;
|
24
|
+
size_t code_index = ctx->out_head;
|
25
|
+
ctx->buffer[ctx->out_head++] = 0; // Placeholder for the code.
|
26
|
+
|
27
|
+
// Process each byte from the raw data and then the CRC.
|
28
|
+
for (size_t i = 0; i < total_bytes; i++) {
|
29
|
+
uint8_t byte;
|
30
|
+
if (i < size)
|
31
|
+
byte = data[i];
|
32
|
+
else if (i == size)
|
33
|
+
byte = crc_high;
|
34
|
+
else // i == size + 1
|
35
|
+
byte = crc_low;
|
36
|
+
|
37
|
+
if (byte == 0) {
|
38
|
+
// Write the current code to the reserved position.
|
39
|
+
ctx->buffer[code_index] = code;
|
40
|
+
// Start a new block.
|
41
|
+
code = 1;
|
42
|
+
if (ctx->out_head >= ctx->buffer_size) return BURST_OVERFLOW_ERROR;
|
43
|
+
code_index = ctx->out_head;
|
44
|
+
ctx->buffer[ctx->out_head++] = 0; // Reserve placeholder for new code.
|
45
|
+
} else {
|
46
|
+
// Append the nonzero byte.
|
47
|
+
if (ctx->out_head >= ctx->buffer_size) return BURST_OVERFLOW_ERROR;
|
48
|
+
ctx->buffer[ctx->out_head++] = byte;
|
49
|
+
code++;
|
50
|
+
// If the maximum code value is reached, finish the block.
|
51
|
+
if (code == COBS_MAX_CODE) {
|
52
|
+
ctx->buffer[code_index] = code;
|
53
|
+
code = 1;
|
54
|
+
if (ctx->out_head >= ctx->buffer_size) return BURST_OVERFLOW_ERROR;
|
55
|
+
code_index = ctx->out_head;
|
56
|
+
ctx->buffer[ctx->out_head++] = 0; // Reserve new placeholder.
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
// Finalize the last block.
|
62
|
+
ctx->buffer[code_index] = code;
|
63
|
+
|
64
|
+
// Append the packet delimiter.
|
65
|
+
if (ctx->out_head >= ctx->buffer_size) return BURST_OVERFLOW_ERROR;
|
66
|
+
ctx->buffer[ctx->out_head++] = COBS_DELIMITER;
|
67
|
+
|
68
|
+
return BURST_PACKET_READY;
|
69
|
+
}
|
70
|
+
|
71
|
+
void burst_encoder_init(burst_encoder_t *ctx, uint8_t *buffer, size_t size) {
|
72
|
+
ctx->buffer = buffer;
|
73
|
+
ctx->buffer_size = size;
|
74
|
+
ctx->out_head = 0;
|
75
|
+
}
|
76
|
+
|
77
|
+
burst_packet_t burst_encoder_flush(burst_encoder_t *ctx) {
|
78
|
+
burst_packet_t packet;
|
79
|
+
packet.data = ctx->buffer;
|
80
|
+
packet.size = ctx->out_head;
|
81
|
+
// Reset the encoder context for the next use.
|
82
|
+
ctx->out_head = 0;
|
83
|
+
return packet;
|
84
|
+
}
|
85
|
+
|
86
|
+
void burst_managed_encoder_init(burst_managed_encoder_t *burst_managed_encoder, uint8_t *buffer, size_t size) {
|
87
|
+
burst_encoder_init(&burst_managed_encoder->encoder, buffer, size);
|
88
|
+
}
|
89
|
+
|
90
|
+
int burst_managed_encoder_add_packet(burst_managed_encoder_t *burst_managed_encoder, const uint8_t *data, size_t len) {
|
91
|
+
|
92
|
+
if (len == 0) {
|
93
|
+
return 0; // No data to process
|
94
|
+
}
|
95
|
+
|
96
|
+
burst_managed_encoder->statistics.bytes_ingested += len;
|
97
|
+
|
98
|
+
burst_status_t status = burst_encoder_add_packet(&burst_managed_encoder->encoder, data, len);
|
99
|
+
|
100
|
+
if (status == BURST_OVERFLOW_ERROR) {
|
101
|
+
// Overflow error, reset the encoder and return an error
|
102
|
+
burst_managed_encoder->statistics.overflow_errors++;
|
103
|
+
burst_managed_encoder->statistics.bytes_discarted += burst_encoder_flush(&burst_managed_encoder->encoder).size;
|
104
|
+
|
105
|
+
return -1;
|
106
|
+
}
|
107
|
+
|
108
|
+
burst_managed_encoder->statistics.packets_processed++;
|
109
|
+
return 0;
|
110
|
+
}
|
111
|
+
|
112
|
+
int burst_managed_encoder_free_space(burst_managed_encoder_t *burst_managed_encoder) {
|
113
|
+
return burst_managed_encoder->encoder.buffer_size - burst_managed_encoder->encoder.out_head;
|
114
|
+
}
|
115
|
+
|
116
|
+
burst_packet_t burst_managed_encoder_flush(burst_managed_encoder_t *burst_managed_encoder) {
|
117
|
+
burst_packet_t packet = burst_encoder_flush(&burst_managed_encoder->encoder);
|
118
|
+
burst_managed_encoder->statistics.bytes_processed += packet.size;
|
119
|
+
return packet;
|
120
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#ifndef BURST_ENCODER_H
|
2
|
+
#define BURST_ENCODER_H
|
3
|
+
|
4
|
+
#include <burst/burst_generic.h>
|
5
|
+
#include <stdint.h>
|
6
|
+
|
7
|
+
typedef struct {
|
8
|
+
uint32_t bytes_ingested; // Total number of bytes ingested
|
9
|
+
uint32_t bytes_processed; // Total number of bytes after processing
|
10
|
+
uint32_t packets_processed; // Successfully decoded packets
|
11
|
+
|
12
|
+
// Error statistics
|
13
|
+
uint32_t overflow_errors;
|
14
|
+
uint32_t bytes_discarted; // Total number of bytes discarded
|
15
|
+
|
16
|
+
} burst_encoder_statistics_t;
|
17
|
+
|
18
|
+
typedef struct {
|
19
|
+
uint8_t *buffer; // Output buffer for encoded packets.
|
20
|
+
size_t buffer_size; // Total size of the output buffer.
|
21
|
+
size_t out_head; // Current offset (number of bytes written).
|
22
|
+
} burst_encoder_t;
|
23
|
+
|
24
|
+
typedef struct {
|
25
|
+
burst_encoder_t encoder; // Decoder instance
|
26
|
+
burst_encoder_statistics_t statistics; // Statistics for the decoder
|
27
|
+
} burst_managed_encoder_t;
|
28
|
+
|
29
|
+
// Encoder
|
30
|
+
void burst_encoder_init(burst_encoder_t *ctx, uint8_t *buffer, size_t size);
|
31
|
+
burst_status_t burst_encoder_add_packet(burst_encoder_t *ctx, const uint8_t *data, size_t size);
|
32
|
+
burst_packet_t burst_encoder_flush(burst_encoder_t *ctx);
|
33
|
+
void burst_managed_encoder_init(burst_managed_encoder_t *burst_managed_encoder, uint8_t *buffer, size_t size);
|
34
|
+
int burst_managed_encoder_add_packet(burst_managed_encoder_t *burst_managed_encoder, const uint8_t *data, size_t len);
|
35
|
+
burst_packet_t burst_managed_encoder_flush(burst_managed_encoder_t *burst_managed_encoder);
|
36
|
+
int burst_managed_encoder_free_space(burst_managed_encoder_t *burst_managed_encoder);
|
37
|
+
|
38
|
+
#endif // BURST_ENCODER_H
|
@@ -5,7 +5,7 @@
|
|
5
5
|
* Calculate CRC16-CCITT (polynomial 0x1021, initial value 0xFFFF) over
|
6
6
|
* the provided data.
|
7
7
|
*/
|
8
|
-
uint16_t
|
8
|
+
uint16_t burst_crc16(const uint8_t *data, size_t length) {
|
9
9
|
uint16_t crc = 0xFFFF;
|
10
10
|
for (size_t i = 0; i < length; i++) {
|
11
11
|
crc ^= ((uint16_t)data[i]) << 8;
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
#ifndef BURST_GENERIC_H
|
3
|
+
#define BURST_GENERIC_H
|
4
|
+
#include <stdint.h>
|
5
|
+
#include <stddef.h>
|
6
|
+
|
7
|
+
// Status codes returned by the encoder.
|
8
|
+
typedef enum
|
9
|
+
{
|
10
|
+
BURST_DATA_CONSUMED,
|
11
|
+
BURST_PACKET_READY,
|
12
|
+
BURST_OVERFLOW_ERROR,
|
13
|
+
BURST_ENCODE_ERROR,
|
14
|
+
BURST_CRC_ERROR,
|
15
|
+
BURST_DECODE_ERROR
|
16
|
+
} burst_status_t;
|
17
|
+
|
18
|
+
typedef struct
|
19
|
+
{
|
20
|
+
uint8_t *data;
|
21
|
+
size_t size;
|
22
|
+
} burst_packet_t;
|
23
|
+
|
24
|
+
#define COBS_DELIMITER 0x00
|
25
|
+
#define COBS_MAX_CODE 0xFF
|
26
|
+
#define CRC_SIZE sizeof(uint16_t)
|
27
|
+
|
28
|
+
uint16_t burst_crc16(const uint8_t *data, size_t length);
|
29
|
+
|
30
|
+
#endif // BURST_GENERIC_H
|
@@ -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())
|
@@ -0,0 +1,75 @@
|
|
1
|
+
#include <nanobind/nanobind.h>
|
2
|
+
extern "C" {
|
3
|
+
#include <burst_link_protocol.h>
|
4
|
+
}
|
5
|
+
|
6
|
+
namespace nb = nanobind;
|
7
|
+
using namespace nb::literals;
|
8
|
+
|
9
|
+
struct BurstInterface {
|
10
|
+
burst_managed_decoder_t decoder = {0};
|
11
|
+
uint8_t decoder_buffer[1024] = {0};
|
12
|
+
burst_encoder_t encoder = {0};
|
13
|
+
uint8_t encoder_buffer[1024] = {0};
|
14
|
+
|
15
|
+
// Create buffer to hold the the vector of bytes likt a list
|
16
|
+
|
17
|
+
nb::list result;
|
18
|
+
|
19
|
+
BurstInterface() {
|
20
|
+
burst_managed_decoder_init(&decoder, decoder_buffer, sizeof(decoder_buffer), add_packet, this);
|
21
|
+
burst_encoder_init(&encoder, encoder_buffer, sizeof(encoder_buffer));
|
22
|
+
}
|
23
|
+
|
24
|
+
static int add_packet(const uint8_t *data, size_t size, void *user_data) {
|
25
|
+
// Create a bytes object from the data and append it to the result list
|
26
|
+
// nb::bytes packet_bytes(reinterpret_cast<const char *>(data), size);
|
27
|
+
// result.append(packet_bytes);
|
28
|
+
BurstInterface *self = static_cast<BurstInterface *>(user_data);
|
29
|
+
nb::bytes packet_bytes(reinterpret_cast<const char *>(data), size);
|
30
|
+
self->result.append(packet_bytes); // Append the packet to the result list
|
31
|
+
return 0; // Return 0 to indicate success
|
32
|
+
}
|
33
|
+
nb::list decode(nb::bytes data, bool fail_on_crc_error = false) {
|
34
|
+
result.clear(); // Clear the result list before returning
|
35
|
+
burst_managed_decoder_handle_data(&decoder, (uint8_t *)data.data(), data.size());
|
36
|
+
|
37
|
+
return result; // Return the result list containing the decoded packets
|
38
|
+
}
|
39
|
+
|
40
|
+
nb::bytes encode(nb::list data) {
|
41
|
+
for (size_t i = 0; i < data.size(); i++) {
|
42
|
+
nb::bytes data_bytes = data[i];
|
43
|
+
burst_encoder_add_packet(&encoder, (uint8_t *)data_bytes.data(), data_bytes.size());
|
44
|
+
}
|
45
|
+
// flush the encoder
|
46
|
+
burst_packet_t packet = burst_encoder_flush(&encoder);
|
47
|
+
return nb::bytes(reinterpret_cast<const char *>(packet.data), packet.size);
|
48
|
+
}
|
49
|
+
|
50
|
+
uint32_t get_bytes_ingested() { return decoder.statistics.bytes_ingested; }
|
51
|
+
uint32_t get_bytes_processed() { return decoder.statistics.bytes_processed; }
|
52
|
+
uint32_t get_packets_processed() { return decoder.statistics.packets_processed; }
|
53
|
+
|
54
|
+
uint32_t get_crc_error_count() { return decoder.statistics.crc_errors; }
|
55
|
+
uint32_t get_overrun_count() { return decoder.statistics.overflow_errors; }
|
56
|
+
uint32_t get_packet_error_count() { return decoder.statistics.decode_errors; }
|
57
|
+
|
58
|
+
// Statistics
|
59
|
+
};
|
60
|
+
|
61
|
+
NB_MODULE(burst_interface_c, m) {
|
62
|
+
nb::set_leak_warnings(false);
|
63
|
+
|
64
|
+
nb::class_<BurstInterface>(m, "BurstInterfaceC")
|
65
|
+
.def(nb::init<>())
|
66
|
+
.def("decode", &BurstInterface::decode, "data"_a, "fail_on_crc_error"_a = false)
|
67
|
+
.def("encode", &BurstInterface::encode, "packets"_a)
|
68
|
+
// Use a lambda
|
69
|
+
.def_prop_ro("bytes_handled", &BurstInterface::get_bytes_ingested)
|
70
|
+
.def_prop_ro("bytes_processed", &BurstInterface::get_bytes_processed)
|
71
|
+
.def_prop_ro("packets_processed", &BurstInterface::get_packets_processed)
|
72
|
+
.def_prop_ro("crc_errors", &BurstInterface::get_crc_error_count)
|
73
|
+
.def_prop_ro("overflow_errors", &BurstInterface::get_overrun_count)
|
74
|
+
.def_prop_ro("decode_errors", &BurstInterface::get_packet_error_count);
|
75
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from burst_link_protocol import BurstInterfaceC, BurstInterfacePy
|
2
|
+
|
3
|
+
payloads = [
|
4
|
+
# Malformed packet
|
5
|
+
("04 01 ff 0e 06 d3 23 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 03 85 1f 00 0b 02 04 02 07 02 0b 02 04 02 08 02 0b 02 04 02 0c 02 0b 02 02 02 0d 02 0b 02 02 03 aa 94 00 04 02 ff 0e 03 d3 23 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 03 85 1f 00")
|
6
|
+
# Good packet
|
7
|
+
("04 02 ff 0e 03 e1 23 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 03 99 fe 00")
|
8
|
+
("04 01 ff 0e 06 ef be ad de c1 02 0b 02 01 02 01 02 0b 02 04 02 02 02 0b 02 04 02 03 02 0b 02 04 02 04 02 0b 02 04 02 05 02 0b 02 04 02 06 02 0b 02 04 02 a6 02 0b 02 04 02 07 02 0b 02 04 02 08 02 0b 02 04 02 0c 02 0b 02 02 02 0d 02 0b 02 02 03 aa 94 00")
|
9
|
+
]
|
10
|
+
|
11
|
+
|
12
|
+
def test_multi_encoded_payloads():
|
13
|
+
c_interface = BurstInterfaceC()
|
14
|
+
py_interface = BurstInterfacePy()
|
15
|
+
for payload in payloads:
|
16
|
+
payload = bytes.fromhex(payload.replace(" ", ""))
|
17
|
+
print(f"Testing payload: {payload.hex()}")
|
18
|
+
py_decoded_packet = py_interface.decode(payload)
|
19
|
+
print(f"Decoded payload: {py_decoded_packet}")
|
20
|
+
c_decoded_packet = c_interface.decode(payload, fail_on_crc_error=True)
|
21
|
+
print(f"Decoded payload: {c_decoded_packet}")
|
22
|
+
|
23
|
+
|
24
|
+
if __name__ == "__main__":
|
25
|
+
test_multi_encoded_payloads()
|
@@ -4,13 +4,15 @@ requires-python = ">=3.10, <4.0"
|
|
4
4
|
|
5
5
|
[[package]]
|
6
6
|
name = "burst-link-protocol"
|
7
|
-
version = "1.
|
7
|
+
version = "1.1.4"
|
8
8
|
source = { editable = "." }
|
9
9
|
dependencies = [
|
10
10
|
{ name = "cobs" },
|
11
11
|
{ name = "crc" },
|
12
|
+
{ name = "janus" },
|
12
13
|
{ name = "nanobind" },
|
13
14
|
{ name = "numpy" },
|
15
|
+
{ name = "pyserial" },
|
14
16
|
{ name = "pytest" },
|
15
17
|
{ name = "pytest-benchmark" },
|
16
18
|
{ name = "pytest-cov" },
|
@@ -30,9 +32,11 @@ dev = [
|
|
30
32
|
requires-dist = [
|
31
33
|
{ name = "cobs", specifier = ">=1.2.1,<2.0.0" },
|
32
34
|
{ name = "crc", specifier = ">=7.1.0,<8.0.0" },
|
35
|
+
{ name = "janus", specifier = ">=2.0.0" },
|
33
36
|
{ name = "nanobind", specifier = ">=2.5.0" },
|
34
37
|
{ name = "nanobind", marker = "extra == 'dev'", specifier = ">=2.5.0,<3.0.0" },
|
35
38
|
{ name = "numpy", specifier = ">=2.2.3,<3.0.0" },
|
39
|
+
{ name = "pyserial", specifier = ">=3.5" },
|
36
40
|
{ name = "pytest", specifier = ">=8.3.4,<9.0.0" },
|
37
41
|
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.4,<9.0.0" },
|
38
42
|
{ name = "pytest-benchmark", specifier = ">=5.1.0,<6.0.0" },
|
@@ -161,6 +165,15 @@ wheels = [
|
|
161
165
|
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
|
162
166
|
]
|
163
167
|
|
168
|
+
[[package]]
|
169
|
+
name = "janus"
|
170
|
+
version = "2.0.0"
|
171
|
+
source = { registry = "https://pypi.org/simple" }
|
172
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/7f/69884b6618be4baf6ebcacc716ee8680a842428a19f403db6d1c0bb990aa/janus-2.0.0.tar.gz", hash = "sha256:0970f38e0e725400496c834a368a67ee551dc3b5ad0a257e132f5b46f2e77770", size = 22910 }
|
173
|
+
wheels = [
|
174
|
+
{ url = "https://files.pythonhosted.org/packages/68/34/65604740edcb20e1bda6a890348ed7d282e7dd23aa00401cbe36fd0edbd9/janus-2.0.0-py3-none-any.whl", hash = "sha256:7e6449d34eab04cd016befbd7d8c0d8acaaaab67cb59e076a69149f9031745f9", size = 12161 },
|
175
|
+
]
|
176
|
+
|
164
177
|
[[package]]
|
165
178
|
name = "nanobind"
|
166
179
|
version = "2.5.0"
|
@@ -268,6 +281,15 @@ wheels = [
|
|
268
281
|
{ url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335 },
|
269
282
|
]
|
270
283
|
|
284
|
+
[[package]]
|
285
|
+
name = "pyserial"
|
286
|
+
version = "3.5"
|
287
|
+
source = { registry = "https://pypi.org/simple" }
|
288
|
+
sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125 }
|
289
|
+
wheels = [
|
290
|
+
{ url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585 },
|
291
|
+
]
|
292
|
+
|
271
293
|
[[package]]
|
272
294
|
name = "pytest"
|
273
295
|
version = "8.3.5"
|
@@ -1,56 +0,0 @@
|
|
1
|
-
#ifndef BURST_INTERFACE_H
|
2
|
-
#define BURST_INTERFACE_H
|
3
|
-
|
4
|
-
#include <stdbool.h>
|
5
|
-
#include <stddef.h>
|
6
|
-
#include <stdint.h>
|
7
|
-
|
8
|
-
#define COBS_DELIMITER 0x00
|
9
|
-
#define COBS_MAX_CODE 0xFF
|
10
|
-
|
11
|
-
// Status codes returned by the encoder.
|
12
|
-
typedef enum { BURST_DATA_CONSUMED, BURST_PACKET_READY, BURST_OVERFLOW_ERROR, BURST_ENCODE_ERROR, BURST_CRC_ERROR, BURST_DECODE_ERROR } burst_status_t;
|
13
|
-
|
14
|
-
typedef struct {
|
15
|
-
uint8_t *data;
|
16
|
-
size_t size;
|
17
|
-
} burst_packet_t;
|
18
|
-
|
19
|
-
typedef struct {
|
20
|
-
uint8_t *buffer; // Output buffer for encoded packets.
|
21
|
-
size_t buffer_size; // Total size of the output buffer.
|
22
|
-
size_t out_head; // Current offset (number of bytes written).
|
23
|
-
} burst_encoder_t;
|
24
|
-
|
25
|
-
typedef struct {
|
26
|
-
uint8_t *buffer; // Output buffer for decoded data.
|
27
|
-
size_t buffer_size; // Size of the output buffer.
|
28
|
-
size_t out_head; // Current count of decoded bytes stored.
|
29
|
-
|
30
|
-
// uint8_t remaining_bytes; // Current block’s code byte; 0 indicates a new block is
|
31
|
-
// expected.
|
32
|
-
// uint8_t bytes_remaining; // Number of data bytes left to copy for the current
|
33
|
-
// block.
|
34
|
-
// bool pending_zero; // true if a zero should be inserted before starting the
|
35
|
-
// next block.
|
36
|
-
|
37
|
-
enum cobs_decode_inc_state { COBS_DECODE_READ_CODE, COBS_DECODE_RUN, COBS_DECODE_FINISH_RUN } state;
|
38
|
-
uint8_t block, code;
|
39
|
-
|
40
|
-
bool finished; // true if the packet is complete and available in the buffer
|
41
|
-
} burst_decoder_t;
|
42
|
-
|
43
|
-
// Encoder
|
44
|
-
void burst_encoder_init(burst_encoder_t *ctx, uint8_t *buffer, size_t size);
|
45
|
-
burst_status_t burst_encoder_add_packet(burst_encoder_t *ctx, const uint8_t *data, size_t size);
|
46
|
-
burst_packet_t burst_encoder_flush(burst_encoder_t *ctx);
|
47
|
-
|
48
|
-
// Decoder
|
49
|
-
void burst_decoder_init(burst_decoder_t *ctx, uint8_t *buffer, size_t size);
|
50
|
-
burst_status_t bust_decoder_add_data(burst_decoder_t *ctx, const uint8_t *data, size_t size, size_t *consumed_bytes);
|
51
|
-
void burst_decoder_reset(burst_decoder_t *ctx);
|
52
|
-
burst_status_t burst_decoder_add_byte(burst_decoder_t *ctx, uint8_t byte);
|
53
|
-
|
54
|
-
burst_packet_t burst_decoder_get_packet(burst_decoder_t *ctx);
|
55
|
-
|
56
|
-
#endif // ENCODER_H
|
@@ -1,92 +0,0 @@
|
|
1
|
-
#include "burst_interface.h"
|
2
|
-
#include "crc.h" // Assumes crc16_ccitt() is available.
|
3
|
-
#include <stddef.h>
|
4
|
-
#include <stdint.h>
|
5
|
-
|
6
|
-
// COBS encoding with CRC appending.
|
7
|
-
// The input to be encoded is the raw packet data followed by the two CRC bytes.
|
8
|
-
// The algorithm works by maintaining a "code" (the count of nonzero bytes)
|
9
|
-
// and inserting that count at the start of each block. When a zero is encountered
|
10
|
-
// (or when the block length reaches COBS_MAX_CODE) the block is terminated.
|
11
|
-
burst_status_t burst_encoder_add_packet(burst_encoder_t *ctx, const uint8_t *data, size_t size)
|
12
|
-
{
|
13
|
-
// Compute the CRC over the raw packet data.
|
14
|
-
uint16_t crc = crc16_ccitt(data, size);
|
15
|
-
|
16
|
-
uint8_t crc_high = (crc >> 8) & 0xFF;
|
17
|
-
uint8_t crc_low = crc & 0xFF;
|
18
|
-
// The total number of bytes to encode: raw data + 2 bytes of CRC.
|
19
|
-
size_t total_bytes = size + CRC_SIZE;
|
20
|
-
|
21
|
-
// Initialize COBS block state.
|
22
|
-
uint8_t code = 1; // Code value starts at 1.
|
23
|
-
// Reserve space for the code byte.
|
24
|
-
if (ctx->out_head >= ctx->buffer_size)
|
25
|
-
return BURST_OVERFLOW_ERROR;
|
26
|
-
size_t code_index = ctx->out_head;
|
27
|
-
ctx->buffer[ctx->out_head++] = 0; // Placeholder for the code.
|
28
|
-
|
29
|
-
// Process each byte from the raw data and then the CRC.
|
30
|
-
for (size_t i = 0; i < total_bytes; i++) {
|
31
|
-
uint8_t byte;
|
32
|
-
if (i < size)
|
33
|
-
byte = data[i];
|
34
|
-
else if (i == size)
|
35
|
-
byte = crc_high;
|
36
|
-
else // i == size + 1
|
37
|
-
byte = crc_low;
|
38
|
-
|
39
|
-
if (byte == 0) {
|
40
|
-
// Write the current code to the reserved position.
|
41
|
-
ctx->buffer[code_index] = code;
|
42
|
-
// Start a new block.
|
43
|
-
code = 1;
|
44
|
-
if (ctx->out_head >= ctx->buffer_size)
|
45
|
-
return BURST_OVERFLOW_ERROR;
|
46
|
-
code_index = ctx->out_head;
|
47
|
-
ctx->buffer[ctx->out_head++] = 0; // Reserve placeholder for new code.
|
48
|
-
} else {
|
49
|
-
// Append the nonzero byte.
|
50
|
-
if (ctx->out_head >= ctx->buffer_size)
|
51
|
-
return BURST_OVERFLOW_ERROR;
|
52
|
-
ctx->buffer[ctx->out_head++] = byte;
|
53
|
-
code++;
|
54
|
-
// If the maximum code value is reached, finish the block.
|
55
|
-
if (code == COBS_MAX_CODE) {
|
56
|
-
ctx->buffer[code_index] = code;
|
57
|
-
code = 1;
|
58
|
-
if (ctx->out_head >= ctx->buffer_size)
|
59
|
-
return BURST_OVERFLOW_ERROR;
|
60
|
-
code_index = ctx->out_head;
|
61
|
-
ctx->buffer[ctx->out_head++] = 0; // Reserve new placeholder.
|
62
|
-
}
|
63
|
-
}
|
64
|
-
}
|
65
|
-
|
66
|
-
// Finalize the last block.
|
67
|
-
ctx->buffer[code_index] = code;
|
68
|
-
|
69
|
-
// Append the packet delimiter.
|
70
|
-
if (ctx->out_head >= ctx->buffer_size)
|
71
|
-
return BURST_OVERFLOW_ERROR;
|
72
|
-
ctx->buffer[ctx->out_head++] = COBS_DELIMITER;
|
73
|
-
|
74
|
-
return BURST_PACKET_READY;
|
75
|
-
}
|
76
|
-
|
77
|
-
void burst_encoder_init(burst_encoder_t *ctx, uint8_t *buffer, size_t size)
|
78
|
-
{
|
79
|
-
ctx->buffer = buffer;
|
80
|
-
ctx->buffer_size = size;
|
81
|
-
ctx->out_head = 0;
|
82
|
-
}
|
83
|
-
|
84
|
-
burst_packet_t burst_encoder_flush(burst_encoder_t *ctx)
|
85
|
-
{
|
86
|
-
burst_packet_t packet;
|
87
|
-
packet.data = ctx->buffer;
|
88
|
-
packet.size = ctx->out_head;
|
89
|
-
// Reset the encoder context for the next use.
|
90
|
-
ctx->out_head = 0;
|
91
|
-
return packet;
|
92
|
-
}
|
@@ -1,80 +0,0 @@
|
|
1
|
-
#include <nanobind/nanobind.h>
|
2
|
-
extern "C"
|
3
|
-
{
|
4
|
-
#include <burst_interface.h>
|
5
|
-
}
|
6
|
-
|
7
|
-
namespace nb = nanobind;
|
8
|
-
using namespace nb::literals;
|
9
|
-
|
10
|
-
struct BurstInterface
|
11
|
-
{
|
12
|
-
burst_decoder_t decoder;
|
13
|
-
uint8_t decoder_buffer[1024] = {0};
|
14
|
-
burst_encoder_t encoder;
|
15
|
-
uint8_t encoder_buffer[1024] = {0};
|
16
|
-
|
17
|
-
BurstInterface()
|
18
|
-
{
|
19
|
-
burst_decoder_init(&decoder, decoder_buffer, sizeof(decoder_buffer));
|
20
|
-
burst_encoder_init(&encoder, encoder_buffer, sizeof(encoder_buffer));
|
21
|
-
}
|
22
|
-
|
23
|
-
nb::list decode(nb::bytes data, bool fail_on_crc_error = false)
|
24
|
-
{
|
25
|
-
nb::list result;
|
26
|
-
uint8_t *data_ptr = (uint8_t *)data.data();
|
27
|
-
size_t data_size = data.size();
|
28
|
-
|
29
|
-
size_t bytes_consumed = 0;
|
30
|
-
while (bytes_consumed < data_size)
|
31
|
-
{
|
32
|
-
burst_status_t status = bust_decoder_add_data(&decoder, data_ptr + bytes_consumed, data_size - bytes_consumed, &bytes_consumed);
|
33
|
-
|
34
|
-
if (status == BURST_PACKET_READY)
|
35
|
-
{
|
36
|
-
burst_packet_t packet = burst_decoder_get_packet(&decoder);
|
37
|
-
nb::bytes packet_bytes(reinterpret_cast<const char *>(packet.data), packet.size);
|
38
|
-
result.append(packet_bytes);
|
39
|
-
}
|
40
|
-
|
41
|
-
if (fail_on_crc_error)
|
42
|
-
{
|
43
|
-
if (status == BURST_CRC_ERROR)
|
44
|
-
{
|
45
|
-
throw std::runtime_error("CRC error");
|
46
|
-
}
|
47
|
-
if (status == BURST_DECODE_ERROR)
|
48
|
-
{
|
49
|
-
throw std::runtime_error("Decode error");
|
50
|
-
}
|
51
|
-
if (status == BURST_OVERFLOW_ERROR)
|
52
|
-
{
|
53
|
-
throw std::runtime_error("Overflow error");
|
54
|
-
}
|
55
|
-
}
|
56
|
-
}
|
57
|
-
return result;
|
58
|
-
}
|
59
|
-
|
60
|
-
nb::bytes encode(nb::list data)
|
61
|
-
{
|
62
|
-
for (size_t i = 0; i < data.size(); i++)
|
63
|
-
{
|
64
|
-
nb::bytes data_bytes = data[i];
|
65
|
-
burst_encoder_add_packet(&encoder, (uint8_t *)data_bytes.data(), data_bytes.size());
|
66
|
-
}
|
67
|
-
// flush the encoder
|
68
|
-
burst_packet_t packet = burst_encoder_flush(&encoder);
|
69
|
-
return nb::bytes(reinterpret_cast<const char *>(packet.data), packet.size);
|
70
|
-
}
|
71
|
-
};
|
72
|
-
|
73
|
-
NB_MODULE(burst_interface_c, m)
|
74
|
-
{
|
75
|
-
|
76
|
-
nb::class_<BurstInterface>(m, "BurstInterfaceC")
|
77
|
-
.def(nb::init<>())
|
78
|
-
.def("decode", &BurstInterface::decode, "data"_a, "fail_on_crc_error"_a = false)
|
79
|
-
.def("encode", &BurstInterface::encode, "packets"_a);
|
80
|
-
}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|