@technoculture/data-bridge 0.1.1 → 0.1.2
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.
- package/CMakeLists.txt +10 -2
- package/deps/include/data_bridge/config.hpp +69 -0
- package/deps/include/data_bridge/protocol/crc16.hpp +8 -0
- package/deps/include/data_bridge/protocol/crc32.hpp +19 -0
- package/deps/include/data_bridge/protocol/packet.hpp +175 -0
- package/deps/include/data_bridge/protocol/reassembler.hpp +80 -0
- package/deps/include/data_bridge/transport/factory.hpp +0 -0
- package/deps/include/data_bridge/transport/iserial_port.hpp +15 -0
- package/deps/include/data_bridge/transport/serial_port.hpp +28 -0
- package/deps/src/CMakeLists.txt +18 -0
- package/deps/src/protocol/crc16.cpp +19 -0
- package/deps/src/protocol/packet.cpp +1 -0
- package/deps/src/transport/platform/linux/linux_serial.cpp +53 -0
- package/deps/src/transport/platform/windows/windows_serial.cpp +51 -0
- package/dist/index.d.mts +41 -72
- package/dist/index.d.ts +41 -72
- package/dist/index.js +196 -102
- package/dist/index.mjs +194 -103
- package/lib/index.ts +12 -160
- package/lib/native.ts +71 -0
- package/lib/reliable.ts +234 -0
- package/lib/resilient.ts +30 -6
- package/package.json +6 -4
- package/prebuilds/darwin-arm64/Release/data_bridge_node.node +0 -0
- package/src/addon.cpp +248 -137
- package/prebuilds/darwin-arm64/.ninja_deps +0 -0
- package/prebuilds/darwin-arm64/.ninja_log +0 -6
- package/prebuilds/darwin-arm64/CMakeCache.txt +0 -398
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeCCompiler.cmake +0 -84
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeCXXCompiler.cmake +0 -104
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeDetermineCompilerABI_C.bin +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeDetermineCompilerABI_CXX.bin +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeSystem.cmake +0 -15
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdC/CMakeCCompilerId.c +0 -905
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdC/a.out +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdC/apple-sdk.c +0 -1
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdCXX/CMakeCXXCompilerId.cpp +0 -920
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdCXX/a.out +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdCXX/apple-sdk.cpp +0 -1
- package/prebuilds/darwin-arm64/CMakeFiles/CMakeConfigureLog.yaml +0 -531
- package/prebuilds/darwin-arm64/CMakeFiles/InstallScripts.json +0 -7
- package/prebuilds/darwin-arm64/CMakeFiles/TargetDirectories.txt +0 -3
- package/prebuilds/darwin-arm64/CMakeFiles/cmake.check_cache +0 -1
- package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/Users/satyamtiwary/Documents/Python-Things/data-bridge/src/protocol/crc16.cpp.o +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/Users/satyamtiwary/Documents/Python-Things/data-bridge/src/transport/platform/linux/linux_serial.cpp.o +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/src/addon.cpp.o +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/src/serial_wrapper.cpp.o +0 -0
- package/prebuilds/darwin-arm64/CMakeFiles/rules.ninja +0 -64
- package/prebuilds/darwin-arm64/build.ninja +0 -192
- package/prebuilds/darwin-arm64/cmake_install.cmake +0 -61
package/CMakeLists.txt
CHANGED
|
@@ -3,8 +3,16 @@ project(data_bridge_node)
|
|
|
3
3
|
|
|
4
4
|
set(CMAKE_CXX_STANDARD 17)
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
|
|
6
|
+
# Check for bundled dependencies (NPM package mode)
|
|
7
|
+
set(LOCAL_DEPS "${CMAKE_CURRENT_SOURCE_DIR}/deps")
|
|
8
|
+
if(EXISTS "${LOCAL_DEPS}/include")
|
|
9
|
+
message(STATUS "Using bundled dependencies in ${LOCAL_DEPS}")
|
|
10
|
+
set(PROJECT_ROOT "${LOCAL_DEPS}")
|
|
11
|
+
else()
|
|
12
|
+
# Development mode (Monorepo)
|
|
13
|
+
get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
|
|
14
|
+
message(STATUS "Using development source root: ${PROJECT_ROOT}")
|
|
15
|
+
endif()
|
|
8
16
|
|
|
9
17
|
# Include Node-API headers
|
|
10
18
|
include_directories(${CMAKE_JS_INC})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <cstdlib>
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for Data Bridge protocol.
|
|
8
|
+
*
|
|
9
|
+
* Parameters can be set via environment variables.
|
|
10
|
+
* If not set, defaults are used.
|
|
11
|
+
*
|
|
12
|
+
* Environment variables:
|
|
13
|
+
* DATA_BRIDGE_MAX_RETRIES - Maximum retry attempts (default: 10)
|
|
14
|
+
* DATA_BRIDGE_RETRY_TIMEOUT_MS - Timeout between retries in ms (default: 2000)
|
|
15
|
+
* DATA_BRIDGE_FRAGMENT_SIZE - Maximum fragment payload size (default: 256)
|
|
16
|
+
* DATA_BRIDGE_ACK_TIMEOUT_MS - Timeout waiting for ACK in ms (default: 500)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
namespace DataBridgeConfig {
|
|
20
|
+
|
|
21
|
+
inline int getEnvInt(const char* name, int defaultValue) {
|
|
22
|
+
const char* val = std::getenv(name);
|
|
23
|
+
if (val == nullptr) return defaultValue;
|
|
24
|
+
try {
|
|
25
|
+
return std::stoi(val);
|
|
26
|
+
} catch (...) {
|
|
27
|
+
return defaultValue;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Maximum number of retry attempts before giving up
|
|
32
|
+
inline uint8_t maxRetries() {
|
|
33
|
+
static uint8_t value = static_cast<uint8_t>(
|
|
34
|
+
getEnvInt("DATA_BRIDGE_MAX_RETRIES", 10)
|
|
35
|
+
);
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Timeout between retries in milliseconds
|
|
40
|
+
inline uint16_t retryTimeoutMs() {
|
|
41
|
+
static uint16_t value = static_cast<uint16_t>(
|
|
42
|
+
getEnvInt("DATA_BRIDGE_RETRY_TIMEOUT_MS", 2000)
|
|
43
|
+
);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Maximum payload size per fragment (for large message fragmentation)
|
|
48
|
+
inline uint16_t fragmentSize() {
|
|
49
|
+
static uint16_t value = static_cast<uint16_t>(
|
|
50
|
+
getEnvInt("DATA_BRIDGE_FRAGMENT_SIZE", 256)
|
|
51
|
+
);
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Timeout waiting for ACK before retry
|
|
56
|
+
inline uint16_t ackTimeoutMs() {
|
|
57
|
+
static uint16_t value = static_cast<uint16_t>(
|
|
58
|
+
getEnvInt("DATA_BRIDGE_ACK_TIMEOUT_MS", 500)
|
|
59
|
+
);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Baud rate (default for most embedded devices)
|
|
64
|
+
inline int baudRate() {
|
|
65
|
+
static int value = getEnvInt("DATA_BRIDGE_BAUD_RATE", 115200);
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
} // namespace DataBridgeConfig
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <cstdint>
|
|
3
|
+
#include <cstddef>
|
|
4
|
+
|
|
5
|
+
class CRC32 {
|
|
6
|
+
public:
|
|
7
|
+
static uint32_t calculate(const uint8_t* data, size_t length) {
|
|
8
|
+
uint32_t crc = 0xFFFFFFFF;
|
|
9
|
+
for (size_t i = 0; i < length; ++i) {
|
|
10
|
+
uint8_t byte = data[i];
|
|
11
|
+
crc = crc ^ byte;
|
|
12
|
+
for (int j = 0; j < 8; ++j) {
|
|
13
|
+
uint32_t mask = -(crc & 1);
|
|
14
|
+
crc = (crc >> 1) ^ (0xEDB88320 & mask);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return ~crc;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <vector>
|
|
3
|
+
#include <string>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <utility>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <data_bridge/protocol/crc32.hpp>
|
|
9
|
+
#include <data_bridge/config.hpp>
|
|
10
|
+
|
|
11
|
+
// Cross-platform packed struct support
|
|
12
|
+
#if defined(_MSC_VER)
|
|
13
|
+
#define PACKED_STRUCT_BEGIN __pragma(pack(push, 1))
|
|
14
|
+
#define PACKED_STRUCT_END __pragma(pack(pop))
|
|
15
|
+
#define PACKED_ATTR
|
|
16
|
+
#elif defined(__GNUC__) || defined(__clang__)
|
|
17
|
+
#define PACKED_STRUCT_BEGIN
|
|
18
|
+
#define PACKED_STRUCT_END
|
|
19
|
+
#define PACKED_ATTR __attribute__((packed))
|
|
20
|
+
#else
|
|
21
|
+
#define PACKED_STRUCT_BEGIN
|
|
22
|
+
#define PACKED_STRUCT_END
|
|
23
|
+
#define PACKED_ATTR
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
struct Packet {
|
|
27
|
+
PACKED_STRUCT_BEGIN
|
|
28
|
+
struct Header {
|
|
29
|
+
uint8_t type;
|
|
30
|
+
uint8_t seq_id;
|
|
31
|
+
uint16_t fragment_id;
|
|
32
|
+
uint16_t total_frags;
|
|
33
|
+
uint16_t payload_len;
|
|
34
|
+
uint32_t crc32;
|
|
35
|
+
} PACKED_ATTR;
|
|
36
|
+
PACKED_STRUCT_END
|
|
37
|
+
|
|
38
|
+
// Packet Types
|
|
39
|
+
static constexpr uint8_t TYPE_DATA = 0x10;
|
|
40
|
+
static constexpr uint8_t TYPE_ACK = 0x20;
|
|
41
|
+
static constexpr uint8_t TYPE_NACK = 0x30;
|
|
42
|
+
static constexpr uint8_t TYPE_SYN = 0x40;
|
|
43
|
+
|
|
44
|
+
// COBS delimiter
|
|
45
|
+
static constexpr uint8_t COBS_DELIMITER = 0x00;
|
|
46
|
+
|
|
47
|
+
// Configuration accessors (use env vars or defaults)
|
|
48
|
+
static uint8_t maxRetries() { return DataBridgeConfig::maxRetries(); }
|
|
49
|
+
static uint16_t retryTimeoutMs() { return DataBridgeConfig::retryTimeoutMs(); }
|
|
50
|
+
static uint16_t fragmentSize() { return DataBridgeConfig::fragmentSize(); }
|
|
51
|
+
|
|
52
|
+
struct Frame {
|
|
53
|
+
Header header;
|
|
54
|
+
std::vector<uint8_t> payload;
|
|
55
|
+
bool valid;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Consistent Overhead Byte Stuffing (COBS) Encoding
|
|
59
|
+
static std::vector<uint8_t> cobs_encode(const std::vector<uint8_t>& data) {
|
|
60
|
+
std::vector<uint8_t> encoded;
|
|
61
|
+
encoded.reserve(data.size() + data.size() / 254 + 2);
|
|
62
|
+
|
|
63
|
+
size_t code_idx = 0;
|
|
64
|
+
encoded.push_back(0); // Placeholder for the first code
|
|
65
|
+
uint8_t code = 1;
|
|
66
|
+
|
|
67
|
+
for (uint8_t byte : data) {
|
|
68
|
+
if (byte == 0) {
|
|
69
|
+
encoded[code_idx] = code;
|
|
70
|
+
code_idx = encoded.size();
|
|
71
|
+
encoded.push_back(0); // Placeholder for next code
|
|
72
|
+
code = 1;
|
|
73
|
+
} else {
|
|
74
|
+
encoded.push_back(byte);
|
|
75
|
+
code++;
|
|
76
|
+
if (code == 0xFF) { // Max run length reached
|
|
77
|
+
encoded[code_idx] = code;
|
|
78
|
+
code_idx = encoded.size();
|
|
79
|
+
encoded.push_back(0);
|
|
80
|
+
code = 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
encoded[code_idx] = code;
|
|
85
|
+
return encoded;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// COBS Decoding
|
|
89
|
+
static std::vector<uint8_t> cobs_decode(const std::vector<uint8_t>& data) {
|
|
90
|
+
std::vector<uint8_t> decoded;
|
|
91
|
+
decoded.reserve(data.size());
|
|
92
|
+
|
|
93
|
+
size_t i = 0;
|
|
94
|
+
while (i < data.size()) {
|
|
95
|
+
uint8_t code = data[i];
|
|
96
|
+
i++;
|
|
97
|
+
if (code == 0) break; // Should not happen in valid COBS before delimiter
|
|
98
|
+
|
|
99
|
+
for (uint8_t j = 1; j < code; j++) {
|
|
100
|
+
if (i >= data.size()) return {}; // Error: truncated
|
|
101
|
+
decoded.push_back(data[i++]);
|
|
102
|
+
}
|
|
103
|
+
if (code < 0xFF && i < data.size()) {
|
|
104
|
+
decoded.push_back(0);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return decoded;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static std::vector<uint8_t> serialize(uint8_t type, uint8_t seq, const std::string& payload, uint16_t frag_id = 0, uint16_t total_frags = 1) {
|
|
111
|
+
Header header;
|
|
112
|
+
header.type = type;
|
|
113
|
+
header.seq_id = seq;
|
|
114
|
+
header.fragment_id = frag_id;
|
|
115
|
+
header.total_frags = total_frags;
|
|
116
|
+
header.payload_len = static_cast<uint16_t>(payload.size());
|
|
117
|
+
header.crc32 = 0; // Calculated later
|
|
118
|
+
|
|
119
|
+
std::vector<uint8_t> raw_packet;
|
|
120
|
+
raw_packet.resize(sizeof(Header) + payload.size());
|
|
121
|
+
|
|
122
|
+
std::memcpy(raw_packet.data(), &header, sizeof(Header));
|
|
123
|
+
std::memcpy(raw_packet.data() + sizeof(Header), payload.data(), payload.size());
|
|
124
|
+
|
|
125
|
+
// Calculate CRC32 of Header + Payload (with CRC field zeroed)
|
|
126
|
+
uint32_t crc = CRC32::calculate(raw_packet.data(), raw_packet.size());
|
|
127
|
+
|
|
128
|
+
// Update header with calculated CRC
|
|
129
|
+
header.crc32 = crc;
|
|
130
|
+
std::memcpy(raw_packet.data(), &header, sizeof(Header));
|
|
131
|
+
|
|
132
|
+
// Encode with COBS
|
|
133
|
+
std::vector<uint8_t> encoded = cobs_encode(raw_packet);
|
|
134
|
+
encoded.push_back(COBS_DELIMITER);
|
|
135
|
+
return encoded;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static Frame deserialize(std::vector<uint8_t>& buffer) {
|
|
139
|
+
// Find delimiter
|
|
140
|
+
auto it = std::find(buffer.begin(), buffer.end(), COBS_DELIMITER);
|
|
141
|
+
if (it == buffer.end()) return {{}, {}, false}; // No complete frame yet
|
|
142
|
+
|
|
143
|
+
std::vector<uint8_t> frame_data(buffer.begin(), it);
|
|
144
|
+
buffer.erase(buffer.begin(), it + 1); // Remove processed frame + delimiter
|
|
145
|
+
|
|
146
|
+
if (frame_data.empty()) return {{}, {}, false};
|
|
147
|
+
|
|
148
|
+
std::vector<uint8_t> decoded = cobs_decode(frame_data);
|
|
149
|
+
if (decoded.size() < sizeof(Header)) return {{}, {}, false};
|
|
150
|
+
|
|
151
|
+
Header header;
|
|
152
|
+
std::memcpy(&header, decoded.data(), sizeof(Header));
|
|
153
|
+
|
|
154
|
+
// Validate Length
|
|
155
|
+
if (decoded.size() != sizeof(Header) + header.payload_len) return {{}, {}, false};
|
|
156
|
+
|
|
157
|
+
// Validate CRC
|
|
158
|
+
uint32_t received_crc = header.crc32;
|
|
159
|
+
|
|
160
|
+
// Zero out CRC in buffer to recompute
|
|
161
|
+
Header* header_ptr = reinterpret_cast<Header*>(decoded.data());
|
|
162
|
+
header_ptr->crc32 = 0;
|
|
163
|
+
|
|
164
|
+
uint32_t computed_crc = CRC32::calculate(decoded.data(), decoded.size());
|
|
165
|
+
|
|
166
|
+
if (received_crc == computed_crc) {
|
|
167
|
+
std::vector<uint8_t> payload(decoded.begin() + sizeof(Header), decoded.end());
|
|
168
|
+
// Retrieve original header
|
|
169
|
+
header.crc32 = received_crc;
|
|
170
|
+
return {header, payload, true};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {{}, {}, false};
|
|
174
|
+
}
|
|
175
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <data_bridge/protocol/packet.hpp>
|
|
3
|
+
#include <vector>
|
|
4
|
+
#include <iostream>
|
|
5
|
+
|
|
6
|
+
class Reassembler {
|
|
7
|
+
public:
|
|
8
|
+
struct Result {
|
|
9
|
+
bool complete;
|
|
10
|
+
std::vector<uint8_t> payload;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
Reassembler() : current_seq_id_(255), expected_frag_(0), active_(false) {}
|
|
14
|
+
|
|
15
|
+
// Returns true if fragment was accepted (in sequence).
|
|
16
|
+
// Caller should send ACK if this returns true.
|
|
17
|
+
// If returns false, caller might ignore or send NACK/ACK-of-last-good.
|
|
18
|
+
bool process_fragment(const Packet::Frame& frame) {
|
|
19
|
+
if (!frame.valid) return false;
|
|
20
|
+
|
|
21
|
+
uint8_t seq = frame.header.seq_id;
|
|
22
|
+
uint16_t frag = frame.header.fragment_id;
|
|
23
|
+
|
|
24
|
+
// New Sequence ?
|
|
25
|
+
if (seq != current_seq_id_) {
|
|
26
|
+
// Accept if it's a new message (logic can be more complex for strict strictness,
|
|
27
|
+
// e.g. only seq+1, but for now accept any new seq as new message start)
|
|
28
|
+
if (frag == 0) {
|
|
29
|
+
reset(seq);
|
|
30
|
+
} else {
|
|
31
|
+
return false; // Received middle of new message without start?
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (frag != expected_frag_) {
|
|
36
|
+
// Out of order fragment
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Valid fragment
|
|
41
|
+
buffer_.insert(buffer_.end(), frame.payload.begin(), frame.payload.end());
|
|
42
|
+
expected_frag_++;
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
bool is_complete(const Packet::Frame& last_frame) const {
|
|
47
|
+
if (!active_) return false;
|
|
48
|
+
return expected_frag_ == last_frame.header.total_frags;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
bool is_duplicate(const Packet::Frame& frame) const {
|
|
52
|
+
if (!active_) return false;
|
|
53
|
+
uint8_t seq = frame.header.seq_id;
|
|
54
|
+
uint16_t frag = frame.header.fragment_id;
|
|
55
|
+
return (seq == current_seq_id_ && frag < expected_frag_);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
std::vector<uint8_t> get_data() const {
|
|
59
|
+
return buffer_;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
size_t get_buffered_size() const {
|
|
63
|
+
return buffer_.size();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
uint8_t get_current_seq() const { return current_seq_id_; }
|
|
67
|
+
|
|
68
|
+
private:
|
|
69
|
+
void reset(uint8_t seq) {
|
|
70
|
+
buffer_.clear();
|
|
71
|
+
current_seq_id_ = seq;
|
|
72
|
+
expected_frag_ = 0;
|
|
73
|
+
active_ = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
uint8_t current_seq_id_;
|
|
77
|
+
uint16_t expected_frag_;
|
|
78
|
+
std::vector<uint8_t> buffer_;
|
|
79
|
+
bool active_;
|
|
80
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <vector>
|
|
3
|
+
#include <string>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
|
|
6
|
+
class ISerialPort {
|
|
7
|
+
public:
|
|
8
|
+
virtual ~ISerialPort() = default;
|
|
9
|
+
|
|
10
|
+
virtual bool open(const std::string& port_name, int baud_rate) = 0;
|
|
11
|
+
virtual void close() = 0;
|
|
12
|
+
|
|
13
|
+
virtual int write(const std::vector<uint8_t>& data) = 0;
|
|
14
|
+
virtual int read(uint8_t* buffer, size_t size) = 0;
|
|
15
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <memory>
|
|
3
|
+
#include <vector>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
#include <data_bridge/transport/iserial_port.hpp>
|
|
7
|
+
|
|
8
|
+
class SerialPort : public ISerialPort {
|
|
9
|
+
|
|
10
|
+
public:
|
|
11
|
+
SerialPort();
|
|
12
|
+
~SerialPort();
|
|
13
|
+
|
|
14
|
+
// Prevent copying (Serial ports are unique resources)
|
|
15
|
+
SerialPort(const SerialPort&) = delete;
|
|
16
|
+
SerialPort& operator=(const SerialPort&) = delete;
|
|
17
|
+
|
|
18
|
+
bool open(const std::string& port_name, int baud_rate) override;
|
|
19
|
+
void close() override;
|
|
20
|
+
|
|
21
|
+
int write(const std::vector<uint8_t>& data) override;
|
|
22
|
+
int read(uint8_t* buffer, size_t size) override;
|
|
23
|
+
|
|
24
|
+
private:
|
|
25
|
+
// The "Pimpl" - This struct is defined only in the .cpp files
|
|
26
|
+
struct Impl;
|
|
27
|
+
std::unique_ptr<Impl> pimpl;
|
|
28
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
add_library(data_bridge STATIC
|
|
2
|
+
protocol/crc16.cpp
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
# Platform specific sources
|
|
6
|
+
if(WIN32)
|
|
7
|
+
target_sources(data_bridge PRIVATE transport/platform/windows/windows_serial.cpp)
|
|
8
|
+
else()
|
|
9
|
+
target_sources(data_bridge PRIVATE transport/platform/linux/linux_serial.cpp)
|
|
10
|
+
endif()
|
|
11
|
+
|
|
12
|
+
target_include_directories(data_bridge PUBLIC
|
|
13
|
+
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
|
|
14
|
+
$<INSTALL_INTERFACE:include>
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Export the library name for other subdirectories
|
|
18
|
+
set(DATA_BRIDGE_LIB data_bridge PARENT_SCOPE)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#include <data_bridge/protocol/crc16.hpp>
|
|
2
|
+
|
|
3
|
+
uint16_t crc16_ccitt(const uint8_t* data, size_t len) {
|
|
4
|
+
uint16_t crc = 0xFFFF;
|
|
5
|
+
|
|
6
|
+
for (size_t i = 0; i < len; ++i) {
|
|
7
|
+
crc ^= static_cast<uint16_t>(data[i]) << 8;
|
|
8
|
+
|
|
9
|
+
for (int bit = 0; bit < 8; ++bit) {
|
|
10
|
+
if (crc & 0x8000)
|
|
11
|
+
crc = (crc << 1) ^ 0x1021;
|
|
12
|
+
else
|
|
13
|
+
crc <<= 1;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return crc;
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#include "protocol/packet.hpp"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#include <data_bridge/transport/serial_port.hpp>
|
|
2
|
+
#include <fcntl.h>
|
|
3
|
+
#include <termios.h>
|
|
4
|
+
#include <unistd.h>
|
|
5
|
+
|
|
6
|
+
struct SerialPort::Impl {
|
|
7
|
+
int fd = -1;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
SerialPort::SerialPort() : pimpl(std::make_unique<Impl>()) {}
|
|
11
|
+
SerialPort::~SerialPort() { close(); }
|
|
12
|
+
|
|
13
|
+
bool SerialPort::open(const std::string& port, int baud) {
|
|
14
|
+
pimpl->fd = ::open(port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
|
|
15
|
+
if (pimpl->fd == -1) return false;
|
|
16
|
+
|
|
17
|
+
// Clear O_NDELAY to make it blocking (uses VTIME)
|
|
18
|
+
fcntl(pimpl->fd, F_SETFL, 0);
|
|
19
|
+
|
|
20
|
+
struct termios tty;
|
|
21
|
+
if (tcgetattr(pimpl->fd, &tty) != 0) return false;
|
|
22
|
+
|
|
23
|
+
cfsetospeed(&tty, B115200);
|
|
24
|
+
cfsetispeed(&tty, B115200);
|
|
25
|
+
|
|
26
|
+
// RAW MODE - Essential for binary/JSON data
|
|
27
|
+
cfmakeraw(&tty);
|
|
28
|
+
tty.c_cflag |= (CLOCAL | CREAD);
|
|
29
|
+
|
|
30
|
+
// Explicitly disable flow control just in case cfmakeraw doesn't (it should)
|
|
31
|
+
tty.c_cflag &= ~CRTSCTS;
|
|
32
|
+
|
|
33
|
+
tty.c_cc[VMIN] = 0; // Non-blocking
|
|
34
|
+
tty.c_cc[VTIME] = 1; // 0.1 second timeout (faster polling)
|
|
35
|
+
|
|
36
|
+
tcflush(pimpl->fd, TCIOFLUSH); // Flush on open to clear old buffer garbage
|
|
37
|
+
return tcsetattr(pimpl->fd, TCSANOW, &tty) == 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
int SerialPort::write(const std::vector<uint8_t>& data) {
|
|
41
|
+
int written = ::write(pimpl->fd, data.data(), data.size());
|
|
42
|
+
// Note: tcdrain() removed - it blocks indefinitely on PTYs (used for testing)
|
|
43
|
+
// For real serial ports, the write() is sufficient as our protocol handles ACKs
|
|
44
|
+
return written;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
int SerialPort::read(uint8_t* buffer, size_t size) {
|
|
48
|
+
return ::read(pimpl->fd, buffer, size);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void SerialPort::close() {
|
|
52
|
+
if (pimpl->fd != -1) { ::close(pimpl->fd); pimpl->fd = -1; }
|
|
53
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#include <data_bridge/transport/serial_port.hpp>
|
|
2
|
+
#include <windows.h>
|
|
3
|
+
|
|
4
|
+
struct SerialPort::Impl {
|
|
5
|
+
HANDLE hSerial = INVALID_HANDLE_VALUE;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
SerialPort::SerialPort() : pimpl(std::make_unique<Impl>()) {}
|
|
9
|
+
SerialPort::~SerialPort() { close(); }
|
|
10
|
+
|
|
11
|
+
bool SerialPort::open(const std::string& port, int baud) {
|
|
12
|
+
pimpl->hSerial = CreateFileA(port.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
|
13
|
+
if (pimpl->hSerial == INVALID_HANDLE_VALUE) return false;
|
|
14
|
+
|
|
15
|
+
DCB dcb = {0};
|
|
16
|
+
dcb.DCBlength = sizeof(dcb);
|
|
17
|
+
GetCommState(pimpl->hSerial, &dcb);
|
|
18
|
+
dcb.BaudRate = CBR_115200;
|
|
19
|
+
dcb.ByteSize = 8;
|
|
20
|
+
dcb.StopBits = ONESTOPBIT;
|
|
21
|
+
dcb.Parity = NOPARITY;
|
|
22
|
+
SetCommState(pimpl->hSerial, &dcb);
|
|
23
|
+
|
|
24
|
+
// TIMEOUTS - Prevents the app from freezing
|
|
25
|
+
COMMTIMEOUTS timeouts = { 0 };
|
|
26
|
+
timeouts.ReadIntervalTimeout = 50;
|
|
27
|
+
timeouts.ReadTotalTimeoutConstant = 50;
|
|
28
|
+
timeouts.ReadTotalTimeoutMultiplier = 10;
|
|
29
|
+
SetCommTimeouts(pimpl->hSerial, &timeouts);
|
|
30
|
+
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
int SerialPort::write(const std::vector<uint8_t>& data) {
|
|
35
|
+
DWORD written;
|
|
36
|
+
WriteFile(pimpl->hSerial, data.data(), (DWORD)data.size(), &written, NULL);
|
|
37
|
+
return (int)written;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
int SerialPort::read(uint8_t* buffer, size_t size) {
|
|
41
|
+
DWORD read;
|
|
42
|
+
if (!ReadFile(pimpl->hSerial, buffer, (DWORD)size, &read, NULL)) return -1;
|
|
43
|
+
return (int)read;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
void SerialPort::close() {
|
|
47
|
+
if (pimpl->hSerial != INVALID_HANDLE_VALUE) {
|
|
48
|
+
CloseHandle(pimpl->hSerial);
|
|
49
|
+
pimpl->hSerial = INVALID_HANDLE_VALUE;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
|
|
3
|
+
interface DataBridgeOptions {
|
|
4
|
+
baudRate?: number;
|
|
5
|
+
maxRetries?: number;
|
|
6
|
+
ackTimeoutMs?: number;
|
|
7
|
+
fragmentSize?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* ReliableDataBridge
|
|
11
|
+
*
|
|
12
|
+
* Implements Stop-and-Wait ARQ with fragmentation/reassembly.
|
|
13
|
+
* Mirrors the Python bindings.wrapper.DataBridge logic.
|
|
14
|
+
*/
|
|
15
|
+
declare class ReliableDataBridge extends EventEmitter {
|
|
16
|
+
private serial;
|
|
17
|
+
private reassembler;
|
|
18
|
+
private isOpen_;
|
|
19
|
+
private options;
|
|
20
|
+
private seqId;
|
|
21
|
+
private rxBuffer;
|
|
22
|
+
private pendingAcks;
|
|
23
|
+
constructor(options?: DataBridgeOptions);
|
|
24
|
+
static open(port: string, baudOrOptions?: number | DataBridgeOptions, cb?: (data: Buffer) => void): Promise<ReliableDataBridge>;
|
|
25
|
+
open(port: string, baud?: number): Promise<boolean>;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
get isOpen(): boolean;
|
|
28
|
+
send(data: string | Buffer): Promise<void>;
|
|
29
|
+
private sendWithRetry;
|
|
30
|
+
private onData;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface SerialPort {
|
|
34
|
+
open(port: string, baud: number, callback: (data: Buffer) => void): boolean;
|
|
35
|
+
write(data: Buffer): number;
|
|
36
|
+
close(): boolean;
|
|
37
|
+
isOpen(): boolean;
|
|
38
|
+
}
|
|
39
|
+
declare const SerialPort: {
|
|
40
|
+
new (): SerialPort;
|
|
41
|
+
};
|
|
42
|
+
|
|
3
43
|
/**
|
|
4
44
|
* ResilientDataBridge - Connection-resilient wrapper
|
|
5
45
|
*
|
|
@@ -74,75 +114,4 @@ declare class ResilientDataBridge extends EventEmitter {
|
|
|
74
114
|
emit<K extends keyof ResilientEvents>(event: K, ...args: Parameters<ResilientEvents[K]>): boolean;
|
|
75
115
|
}
|
|
76
116
|
|
|
77
|
-
|
|
78
|
-
* Data Bridge - Guaranteed Reliable Serial Communication
|
|
79
|
-
*
|
|
80
|
-
* TypeScript wrapper for the native Node-API addon.
|
|
81
|
-
* Provides a clean, async-friendly API for Electron applications.
|
|
82
|
-
*/
|
|
83
|
-
|
|
84
|
-
interface DataBridgeOptions {
|
|
85
|
-
baudRate?: number;
|
|
86
|
-
}
|
|
87
|
-
interface DataBridgeEvents {
|
|
88
|
-
data: (data: Buffer) => void;
|
|
89
|
-
error: (error: Error) => void;
|
|
90
|
-
close: () => void;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* DataBridge provides guaranteed reliable serial communication.
|
|
94
|
-
*
|
|
95
|
-
* Features:
|
|
96
|
-
* - Automatic retransmission on packet loss
|
|
97
|
-
* - CRC32 integrity checking
|
|
98
|
-
* - COBS framing for robust delimitation
|
|
99
|
-
* - Fragmentation for large messages
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* ```typescript
|
|
103
|
-
* const bridge = await DataBridge.open('/dev/ttyUSB0');
|
|
104
|
-
*
|
|
105
|
-
* bridge.on('data', (data) => {
|
|
106
|
-
* console.log('Received:', data.toString());
|
|
107
|
-
* });
|
|
108
|
-
*
|
|
109
|
-
* await bridge.send('Hello, World!');
|
|
110
|
-
* await bridge.close();
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
declare class DataBridge extends EventEmitter {
|
|
114
|
-
private native;
|
|
115
|
-
private _isOpen;
|
|
116
|
-
private constructor();
|
|
117
|
-
/**
|
|
118
|
-
* Open a serial port with guaranteed reliable communication.
|
|
119
|
-
*
|
|
120
|
-
* @param port - Serial port path (e.g., '/dev/ttyUSB0' or 'COM3')
|
|
121
|
-
* @param options - Configuration options
|
|
122
|
-
* @returns Promise resolving to a DataBridge instance
|
|
123
|
-
*/
|
|
124
|
-
static open(port: string, options?: DataBridgeOptions): Promise<DataBridge>;
|
|
125
|
-
/**
|
|
126
|
-
* Send data with guaranteed delivery.
|
|
127
|
-
*
|
|
128
|
-
* The data will be fragmented if necessary, checksummed, and
|
|
129
|
-
* retransmitted until acknowledged by the receiver.
|
|
130
|
-
*
|
|
131
|
-
* @param data - Data to send (Buffer or string)
|
|
132
|
-
* @returns Promise resolving when data is acknowledged
|
|
133
|
-
*/
|
|
134
|
-
send(data: Buffer | string): Promise<void>;
|
|
135
|
-
/**
|
|
136
|
-
* Close the serial port.
|
|
137
|
-
*/
|
|
138
|
-
close(): Promise<void>;
|
|
139
|
-
/**
|
|
140
|
-
* Check if the port is currently open.
|
|
141
|
-
*/
|
|
142
|
-
get isOpen(): boolean;
|
|
143
|
-
on<K extends keyof DataBridgeEvents>(event: K, listener: DataBridgeEvents[K]): this;
|
|
144
|
-
once<K extends keyof DataBridgeEvents>(event: K, listener: DataBridgeEvents[K]): this;
|
|
145
|
-
emit<K extends keyof DataBridgeEvents>(event: K, ...args: Parameters<DataBridgeEvents[K]>): boolean;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export { DataBridge, type DataBridgeEvents, type DataBridgeOptions, ResilientDataBridge, type ResilientEvents, type ResilientOptions, DataBridge as default };
|
|
117
|
+
export { ReliableDataBridge as DataBridge, type DataBridgeOptions, SerialPort as RawSerialPort, ResilientDataBridge, type ResilientEvents, type ResilientOptions };
|