@technoculture/safeserial 0.1.0 → 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.
@@ -0,0 +1,21 @@
1
+ add_library(safeserial STATIC
2
+ safeserial.cpp
3
+ resilient_bridge.cpp
4
+ )
5
+
6
+ safeserial_apply_sanitizers(safeserial)
7
+
8
+ # Platform specific sources
9
+ if(WIN32)
10
+ target_sources(safeserial PRIVATE transport/platform/windows/windows_serial.cpp)
11
+ else()
12
+ target_sources(safeserial PRIVATE transport/platform/linux/linux_serial.cpp)
13
+ endif()
14
+
15
+ target_include_directories(safeserial PUBLIC
16
+ $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
17
+ $<INSTALL_INTERFACE:include>
18
+ )
19
+
20
+ # Export the library name for other subdirectories
21
+ set(SAFESERIAL_LIB safeserial PARENT_SCOPE)
@@ -0,0 +1 @@
1
+ #include "protocol/packet.hpp"
@@ -0,0 +1,338 @@
1
+ #include <safeserial/resilient_bridge.hpp>
2
+
3
+ #include <algorithm>
4
+ #include <chrono>
5
+ #include <cmath>
6
+ #include <stdexcept>
7
+
8
+ ResilientDataBridge::ResilientDataBridge()
9
+ : options_(Options::Defaults()) {}
10
+
11
+ ResilientDataBridge::ResilientDataBridge(const Options& options)
12
+ : options_(options) {}
13
+
14
+ ResilientDataBridge::ResilientDataBridge(
15
+ const Options& options,
16
+ std::function<std::shared_ptr<ISerialPort>()> serial_factory)
17
+ : options_(options),
18
+ serial_factory_(std::move(serial_factory)) {}
19
+
20
+ ResilientDataBridge::~ResilientDataBridge() {
21
+ close();
22
+ }
23
+
24
+ bool ResilientDataBridge::open(const std::string& port) {
25
+ std::lock_guard<std::mutex> lock(state_mutex_);
26
+ if (connected_) {
27
+ return true;
28
+ }
29
+
30
+ port_ = port;
31
+ stop_ = false;
32
+ bool ok = connect();
33
+ if (!ok && options_.reconnect) {
34
+ start_reconnect_thread();
35
+ }
36
+ return ok;
37
+ }
38
+
39
+ void ResilientDataBridge::close() {
40
+ stop_ = true;
41
+ if (reconnect_thread_.joinable()) {
42
+ reconnect_thread_.join();
43
+ }
44
+ reconnect_thread_running_ = false;
45
+
46
+ {
47
+ std::lock_guard<std::mutex> lock(state_mutex_);
48
+ if (bridge_) {
49
+ bridge_->close();
50
+ bridge_.reset();
51
+ }
52
+ connected_ = false;
53
+ }
54
+
55
+ {
56
+ std::lock_guard<std::mutex> lock(queue_mutex_);
57
+ for (auto& msg : queue_) {
58
+ std::lock_guard<std::mutex> msg_lock(msg->mutex);
59
+ msg->failed = true;
60
+ msg->error = "Connection closed";
61
+ msg->done = true;
62
+ msg->cv.notify_all();
63
+ }
64
+ queue_.clear();
65
+ }
66
+
67
+ std::function<void()> cb;
68
+ {
69
+ std::lock_guard<std::mutex> lock(callback_mutex_);
70
+ cb = on_close_;
71
+ }
72
+ if (cb) {
73
+ cb();
74
+ }
75
+ }
76
+
77
+ bool ResilientDataBridge::connect() {
78
+ if (port_.empty()) {
79
+ notify_error("Port not set");
80
+ return false;
81
+ }
82
+
83
+ if (serial_factory_) {
84
+ auto serial = serial_factory_();
85
+ bridge_ = std::make_unique<DataBridge>(serial, options_.bridge);
86
+ } else {
87
+ bridge_ = std::make_unique<DataBridge>(options_.bridge);
88
+ }
89
+ bridge_->set_on_data([this](const std::vector<uint8_t>& data) {
90
+ std::function<void(const std::vector<uint8_t>&)> cb;
91
+ {
92
+ std::lock_guard<std::mutex> lock(callback_mutex_);
93
+ cb = on_data_;
94
+ }
95
+ if (cb) {
96
+ cb(data);
97
+ }
98
+ });
99
+
100
+ if (!bridge_->open(port_, options_.bridge.baud_rate)) {
101
+ bridge_.reset();
102
+ return false;
103
+ }
104
+
105
+ connected_ = true;
106
+ reconnect_attempt_ = 0;
107
+
108
+ std::function<void()> cb;
109
+ {
110
+ std::lock_guard<std::mutex> lock(callback_mutex_);
111
+ cb = on_reconnected_;
112
+ }
113
+ if (cb && reconnect_thread_running_) {
114
+ cb();
115
+ }
116
+
117
+ flush_queue();
118
+ return true;
119
+ }
120
+
121
+ void ResilientDataBridge::handle_disconnect(const std::string& reason) {
122
+ {
123
+ std::lock_guard<std::mutex> lock(state_mutex_);
124
+ if (!connected_) {
125
+ return;
126
+ }
127
+ connected_ = false;
128
+ if (bridge_) {
129
+ bridge_->close();
130
+ bridge_.reset();
131
+ }
132
+ }
133
+
134
+ std::function<void()> cb;
135
+ {
136
+ std::lock_guard<std::mutex> lock(callback_mutex_);
137
+ cb = on_disconnect_;
138
+ }
139
+ if (cb) {
140
+ cb();
141
+ }
142
+
143
+ notify_error(reason);
144
+
145
+ if (options_.reconnect && !stop_) {
146
+ start_reconnect_thread();
147
+ }
148
+ }
149
+
150
+ void ResilientDataBridge::start_reconnect_thread() {
151
+ if (reconnect_thread_running_) {
152
+ return;
153
+ }
154
+
155
+ reconnect_thread_running_ = true;
156
+ reconnect_thread_ = std::thread(&ResilientDataBridge::reconnect_loop, this);
157
+ }
158
+
159
+ void ResilientDataBridge::reconnect_loop() {
160
+ while (!stop_ && options_.reconnect) {
161
+ if (connected_) {
162
+ break;
163
+ }
164
+
165
+ reconnect_attempt_++;
166
+ uint32_t base_delay = options_.reconnect_delay_ms;
167
+ uint32_t max_delay = options_.max_reconnect_delay_ms;
168
+ uint32_t backoff = base_delay * static_cast<uint32_t>(std::pow(2, reconnect_attempt_ - 1));
169
+ uint32_t jitter = static_cast<uint32_t>(rng_() % 1000);
170
+ uint32_t delay = std::min(max_delay, backoff + jitter);
171
+
172
+ std::function<void(uint32_t, uint32_t)> cb;
173
+ {
174
+ std::lock_guard<std::mutex> lock(callback_mutex_);
175
+ cb = on_reconnecting_;
176
+ }
177
+ if (cb) {
178
+ cb(reconnect_attempt_, delay);
179
+ }
180
+
181
+ std::this_thread::sleep_for(std::chrono::milliseconds(delay));
182
+ if (stop_) {
183
+ break;
184
+ }
185
+
186
+ bool ok = false;
187
+ if (!connected_) {
188
+ ok = connect();
189
+ } else {
190
+ ok = true;
191
+ }
192
+ if (ok) {
193
+ break;
194
+ }
195
+ }
196
+
197
+ reconnect_thread_running_ = false;
198
+ }
199
+
200
+ void ResilientDataBridge::flush_queue() {
201
+ while (!stop_ && connected_) {
202
+ std::shared_ptr<QueuedMessage> msg;
203
+ {
204
+ std::lock_guard<std::mutex> lock(queue_mutex_);
205
+ if (queue_.empty()) {
206
+ break;
207
+ }
208
+ msg = queue_.front();
209
+ queue_.erase(queue_.begin());
210
+ }
211
+
212
+ try {
213
+ int written = bridge_->send(
214
+ msg->data,
215
+ options_.bridge.ack_timeout_ms,
216
+ options_.bridge.max_retries,
217
+ options_.bridge.fragment_size);
218
+
219
+ {
220
+ std::lock_guard<std::mutex> msg_lock(msg->mutex);
221
+ msg->bytes_written = written;
222
+ msg->done = true;
223
+ msg->cv.notify_all();
224
+ }
225
+ } catch (const std::exception& ex) {
226
+ {
227
+ std::lock_guard<std::mutex> lock(queue_mutex_);
228
+ queue_.insert(queue_.begin(), msg);
229
+ }
230
+ handle_disconnect(ex.what());
231
+ break;
232
+ }
233
+ }
234
+ }
235
+
236
+ int ResilientDataBridge::send(const std::vector<uint8_t>& data) {
237
+ if (stop_) {
238
+ throw std::runtime_error("Connection closed");
239
+ }
240
+
241
+ if (connected_ && bridge_) {
242
+ try {
243
+ return bridge_->send(
244
+ data,
245
+ options_.bridge.ack_timeout_ms,
246
+ options_.bridge.max_retries,
247
+ options_.bridge.fragment_size);
248
+ } catch (const std::exception& ex) {
249
+ if (!options_.reconnect) {
250
+ throw;
251
+ }
252
+ handle_disconnect(ex.what());
253
+ }
254
+ } else if (!options_.reconnect) {
255
+ throw std::runtime_error("Not connected");
256
+ }
257
+
258
+ auto msg = std::make_shared<QueuedMessage>(data);
259
+ {
260
+ std::lock_guard<std::mutex> lock(queue_mutex_);
261
+ while (queue_.size() >= options_.max_queue_size) {
262
+ auto dropped = queue_.front();
263
+ queue_.erase(queue_.begin());
264
+ {
265
+ std::lock_guard<std::mutex> dropped_lock(dropped->mutex);
266
+ dropped->failed = true;
267
+ dropped->error = "Message dropped: queue overflow";
268
+ dropped->done = true;
269
+ dropped->cv.notify_all();
270
+ }
271
+ }
272
+ queue_.push_back(msg);
273
+ }
274
+
275
+ if (options_.reconnect && !reconnect_thread_running_) {
276
+ start_reconnect_thread();
277
+ }
278
+
279
+ std::unique_lock<std::mutex> lock(msg->mutex);
280
+ msg->cv.wait(lock, [&]() { return msg->done || stop_; });
281
+ if (stop_) {
282
+ throw std::runtime_error("Connection closed");
283
+ }
284
+ if (msg->failed) {
285
+ throw std::runtime_error(msg->error);
286
+ }
287
+ return msg->bytes_written;
288
+ }
289
+
290
+ bool ResilientDataBridge::is_connected() const {
291
+ return connected_.load();
292
+ }
293
+
294
+ size_t ResilientDataBridge::queue_length() const {
295
+ std::lock_guard<std::mutex> lock(queue_mutex_);
296
+ return queue_.size();
297
+ }
298
+
299
+ void ResilientDataBridge::set_on_data(std::function<void(const std::vector<uint8_t>&)> callback) {
300
+ std::lock_guard<std::mutex> lock(callback_mutex_);
301
+ on_data_ = std::move(callback);
302
+ }
303
+
304
+ void ResilientDataBridge::set_on_error(std::function<void(const std::string&)> callback) {
305
+ std::lock_guard<std::mutex> lock(callback_mutex_);
306
+ on_error_ = std::move(callback);
307
+ }
308
+
309
+ void ResilientDataBridge::set_on_disconnect(std::function<void()> callback) {
310
+ std::lock_guard<std::mutex> lock(callback_mutex_);
311
+ on_disconnect_ = std::move(callback);
312
+ }
313
+
314
+ void ResilientDataBridge::set_on_reconnecting(std::function<void(uint32_t, uint32_t)> callback) {
315
+ std::lock_guard<std::mutex> lock(callback_mutex_);
316
+ on_reconnecting_ = std::move(callback);
317
+ }
318
+
319
+ void ResilientDataBridge::set_on_reconnected(std::function<void()> callback) {
320
+ std::lock_guard<std::mutex> lock(callback_mutex_);
321
+ on_reconnected_ = std::move(callback);
322
+ }
323
+
324
+ void ResilientDataBridge::set_on_close(std::function<void()> callback) {
325
+ std::lock_guard<std::mutex> lock(callback_mutex_);
326
+ on_close_ = std::move(callback);
327
+ }
328
+
329
+ void ResilientDataBridge::notify_error(const std::string& message) {
330
+ std::function<void(const std::string&)> cb;
331
+ {
332
+ std::lock_guard<std::mutex> lock(callback_mutex_);
333
+ cb = on_error_;
334
+ }
335
+ if (cb) {
336
+ cb(message);
337
+ }
338
+ }
@@ -0,0 +1,246 @@
1
+ #include <safeserial/safeserial.hpp>
2
+
3
+ #include <algorithm>
4
+ #include <chrono>
5
+ #include <stdexcept>
6
+
7
+ namespace {
8
+ std::vector<uint8_t> slice_payload(const std::vector<uint8_t>& data, size_t offset, size_t len) {
9
+ auto start = data.begin() + static_cast<std::vector<uint8_t>::difference_type>(offset);
10
+ auto end = data.begin() + static_cast<std::vector<uint8_t>::difference_type>(offset + len);
11
+ return std::vector<uint8_t>(start, end);
12
+ }
13
+ } // namespace
14
+
15
+ DataBridge::DataBridge()
16
+ : serial_(std::make_shared<SerialPort>()),
17
+ options_(Options::Defaults()) {}
18
+
19
+ DataBridge::DataBridge(const Options& options)
20
+ : serial_(std::make_shared<SerialPort>()),
21
+ options_(options) {}
22
+
23
+ DataBridge::DataBridge(std::shared_ptr<ISerialPort> serial, const Options& options)
24
+ : serial_(std::move(serial)),
25
+ options_(options) {
26
+ if (!serial_) {
27
+ serial_ = std::make_shared<SerialPort>();
28
+ }
29
+ }
30
+
31
+ DataBridge::~DataBridge() {
32
+ close();
33
+ }
34
+
35
+ bool DataBridge::open(const std::string& port, int baud_rate_override) {
36
+ if (is_open_) {
37
+ return true;
38
+ }
39
+
40
+ int baud_rate = baud_rate_override > 0 ? baud_rate_override : options_.baud_rate;
41
+ if (!serial_->open(port, baud_rate)) {
42
+ return false;
43
+ }
44
+
45
+ stop_ = false;
46
+ is_open_ = true;
47
+ receive_thread_ = std::thread(&DataBridge::receive_loop, this);
48
+ return true;
49
+ }
50
+
51
+ void DataBridge::close() {
52
+ if (!is_open_) {
53
+ return;
54
+ }
55
+
56
+ stop_ = true;
57
+ {
58
+ std::lock_guard<std::mutex> lock(ack_mutex_);
59
+ waiting_for_ack_ = false;
60
+ acked_ = true;
61
+ }
62
+ ack_cv_.notify_all();
63
+
64
+ if (receive_thread_.joinable()) {
65
+ receive_thread_.join();
66
+ }
67
+
68
+ serial_->close();
69
+ is_open_ = false;
70
+ }
71
+
72
+ bool DataBridge::is_open() const {
73
+ return is_open_.load();
74
+ }
75
+
76
+ int DataBridge::send(const std::vector<uint8_t>& data,
77
+ uint16_t ack_timeout_ms_override,
78
+ uint8_t max_retries_override,
79
+ uint16_t fragment_size_override) {
80
+ if (!is_open_) {
81
+ throw std::runtime_error("Port not open");
82
+ }
83
+
84
+ std::lock_guard<std::mutex> send_lock(send_mutex_);
85
+
86
+ uint16_t fragment_size = fragment_size_override > 0 ? fragment_size_override : options_.fragment_size;
87
+ uint16_t ack_timeout_ms = ack_timeout_ms_override > 0 ? ack_timeout_ms_override : options_.ack_timeout_ms;
88
+ uint8_t max_retries = max_retries_override > 0 ? max_retries_override : options_.max_retries;
89
+
90
+ if (fragment_size == 0) {
91
+ throw std::runtime_error("Invalid fragment size");
92
+ }
93
+
94
+ size_t total_frags = (data.empty() ? 1 : (data.size() + fragment_size - 1) / fragment_size);
95
+ uint8_t seq = 0;
96
+ {
97
+ std::lock_guard<std::mutex> lock(seq_mutex_);
98
+ seq = next_seq_;
99
+ next_seq_ = static_cast<uint8_t>((next_seq_ + 1) % 256);
100
+ }
101
+
102
+ int total_written = 0;
103
+ for (size_t frag_id = 0; frag_id < total_frags; ++frag_id) {
104
+ size_t offset = frag_id * fragment_size;
105
+ size_t len = data.empty() ? 0 : std::min<size_t>(fragment_size, data.size() - offset);
106
+ std::vector<uint8_t> fragment = slice_payload(data, offset, len);
107
+
108
+ std::string payload;
109
+ if (!fragment.empty()) {
110
+ payload.assign(reinterpret_cast<const char*>(fragment.data()), fragment.size());
111
+ }
112
+ std::vector<uint8_t> packet = Packet::serialize(
113
+ Packet::TYPE_DATA,
114
+ seq,
115
+ payload,
116
+ static_cast<uint16_t>(frag_id),
117
+ static_cast<uint16_t>(total_frags));
118
+
119
+ uint8_t retries = 0;
120
+ bool acked = false;
121
+ int bytes_written = 0;
122
+ while (retries <= max_retries && !acked) {
123
+ {
124
+ std::lock_guard<std::mutex> lock(ack_mutex_);
125
+ waiting_for_ack_ = true;
126
+ acked_ = false;
127
+ waiting_seq_ = seq;
128
+ waiting_frag_ = static_cast<uint16_t>(frag_id);
129
+ }
130
+
131
+ {
132
+ std::lock_guard<std::mutex> lock(write_mutex_);
133
+ bytes_written = serial_->write(packet);
134
+ }
135
+
136
+ std::unique_lock<std::mutex> lock(ack_mutex_);
137
+ bool notified = ack_cv_.wait_for(
138
+ lock,
139
+ std::chrono::milliseconds(ack_timeout_ms),
140
+ [this]() { return acked_ || stop_; });
141
+
142
+ if (stop_) {
143
+ throw std::runtime_error("Connection closed");
144
+ }
145
+
146
+ if (notified && acked_) {
147
+ acked = true;
148
+ waiting_for_ack_ = false;
149
+ total_written += bytes_written;
150
+ } else {
151
+ retries++;
152
+ }
153
+ }
154
+
155
+ if (!acked) {
156
+ throw std::runtime_error("Send failed after retries");
157
+ }
158
+ }
159
+
160
+ return total_written;
161
+ }
162
+
163
+ void DataBridge::set_on_data(std::function<void(const std::vector<uint8_t>&)> callback) {
164
+ std::lock_guard<std::mutex> lock(callback_mutex_);
165
+ on_data_ = std::move(callback);
166
+ }
167
+
168
+ size_t DataBridge::get_buffered_size() const {
169
+ return reassembler_.get_buffered_size();
170
+ }
171
+
172
+ void DataBridge::receive_loop() {
173
+ uint8_t buffer[1024];
174
+ while (!stop_) {
175
+ int n = serial_->read(buffer, sizeof(buffer));
176
+ if (n > 0) {
177
+ rx_buffer_.insert(rx_buffer_.end(), buffer, buffer + n);
178
+
179
+ while (true) {
180
+ size_t before = rx_buffer_.size();
181
+ Packet::Frame frame = Packet::deserialize(rx_buffer_);
182
+ if (!frame.valid) {
183
+ if (rx_buffer_.size() == before) {
184
+ break;
185
+ }
186
+ continue;
187
+ }
188
+ handle_frame(frame);
189
+ }
190
+ }
191
+
192
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
193
+ }
194
+ }
195
+
196
+ void DataBridge::handle_frame(const Packet::Frame& frame) {
197
+ if (frame.header.type == Packet::TYPE_ACK) {
198
+ std::lock_guard<std::mutex> lock(ack_mutex_);
199
+ if (waiting_for_ack_ &&
200
+ frame.header.seq_id == waiting_seq_ &&
201
+ frame.header.fragment_id == waiting_frag_) {
202
+ acked_ = true;
203
+ waiting_for_ack_ = false;
204
+ ack_cv_.notify_all();
205
+ }
206
+ return;
207
+ }
208
+
209
+ if (frame.header.type != Packet::TYPE_DATA) {
210
+ return;
211
+ }
212
+
213
+ bool should_ack = false;
214
+ if (reassembler_.process_fragment(frame)) {
215
+ should_ack = true;
216
+ if (reassembler_.is_complete(frame)) {
217
+ auto data = reassembler_.get_data();
218
+ std::function<void(const std::vector<uint8_t>&)> cb;
219
+ {
220
+ std::lock_guard<std::mutex> lock(callback_mutex_);
221
+ cb = on_data_;
222
+ }
223
+ if (cb) {
224
+ cb(data);
225
+ }
226
+ }
227
+ } else if (reassembler_.is_duplicate(frame)) {
228
+ should_ack = true;
229
+ }
230
+
231
+ if (should_ack) {
232
+ send_ack(frame);
233
+ }
234
+ }
235
+
236
+ void DataBridge::send_ack(const Packet::Frame& frame) {
237
+ std::vector<uint8_t> ack = Packet::serialize(
238
+ Packet::TYPE_ACK,
239
+ frame.header.seq_id,
240
+ "",
241
+ frame.header.fragment_id,
242
+ frame.header.total_frags);
243
+
244
+ std::lock_guard<std::mutex> lock(write_mutex_);
245
+ serial_->write(ack);
246
+ }
@@ -0,0 +1,53 @@
1
+ #include <safeserial/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 <safeserial/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
+ }