-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserial.hpp
More file actions
512 lines (459 loc) · 14.3 KB
/
serial.hpp
File metadata and controls
512 lines (459 loc) · 14.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
// @ Copyright 2022-2025 Nestor Neto
#ifndef INCLUDE_LIBSERIAL_SERIAL_HPP_
#define INCLUDE_LIBSERIAL_SERIAL_HPP_
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <asm/ioctls.h>
#include <asm/termbits.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <chrono>
#include <iostream>
#include <memory>
#include <functional>
#include <string>
#include <thread>
#include <vector>
#include "libserial/serial_exception.hpp"
#include "libserial/serial_types.hpp"
/**
* @brief Serial Interface Library libserial namespace
* @author Nestor Pereira Neto
*
*/
namespace libserial {
/**
* @brief A C++ wrapper class for serial port communication
*
* The Serial class provides a high-level interface for serial port operations
* on Linux systems. It encapsulates low-level POSIX system calls and provides
* exception-based error handling for serial communication.
*
* @author Nestor Pereira Neto
* @version 0.0.0
*/
class Serial {
public:
/**
* @brief Default constructor
*
* Creates a Serial object without opening any port.
* Use open() method to establish connection.
* Initializes with default baud rate of 9600.
*/
Serial() = default;
Serial(const Serial&) = delete;
Serial& operator=(const Serial&) = delete;
/**
* @brief Constructor with automatic port opening
*
* Creates a Serial object and immediately opens the specified port.
*
* @param port The device path (e.g., "/dev/ttyUSB0", "/dev/ttyS0")
* @throws SerialException if the port cannot be opened
*/
explicit Serial(const std::string& port);
/**
* @brief Destructor
*
* Automatically closes the serial port if it's open, ensuring
* proper resource cleanup.
*/
~Serial() noexcept;
/**
* @brief Opens a serial port for communication
*
* @param port The device path to open (e.g., "/dev/ttyUSB0")
* @throws SerialException if the port doesn't exist or cannot be accessed
* @throws PermissionDeniedException if insufficient permissions
*/
void open(const std::string& port);
/**
* @brief Closes the currently open serial port
*
* @throws SerialException if an error occurs during closing
*/
void close();
/**
* @brief Writes data to the serial port
*
* Sends the provided string data to the serial port. A carriage return
* character ('\\r') is automatically appended to the data.
*
* @param data Shared pointer to the string data to write
* @throws SerialException if write operation fails
* @throws std::invalid_argument if data pointer is null
*
* @note The original string is not modified; a copy is made with the
* terminator appended.
*/
void write(std::shared_ptr<std::string> data);
/**
* @brief Reads data from serial port into a shared pointer buffer
*
* Reads up to max_length bytes from the serial port and stores them
* in the provided shared string buffer. This version provides better
* memory management and avoids unnecessary string copies. Just works
* in canonical mode.
*
* @param buffer Shared pointer to string where data will be stored
* @return Number of bytes actually read
* @throws SerialException if read operation fails
* @throws SerialException if buffer is null
*
* @note The buffer will be resized to contain exactly the read data
*/
size_t read(std::shared_ptr<std::string> buffer);
/**
* @brief Reads a specific number of bytes from the serial port
*
* Reads exactly num_bytes from the serial port and stores them
* in the provided shared string buffer. Just works in non-canonical mode.
*
* @param buffer Shared pointer to string where data will be stored
* @param num_bytes Number of bytes to read
* @return Number of bytes actually read
* @throws SerialException if read operation fails
* @throws SerialException if buffer is null
* @throws SerialException if num_bytes is zero
*
* @note The buffer will be resized to contain exactly the read data
*/
size_t readBytes(std::shared_ptr<std::string> buffer, size_t num_bytes);
/**
* @brief Reads data until a specific terminator character is found
*
* Continues reading byte by byte until the specified terminator
* character is encountered. The terminator is included in the result.
* Works in both canonical and non-canonical modes.
*
* @param terminator The character to stop reading at
* @return String containing all read data including the terminator
* @throws SerialException if read operation fails
*
* @warning This method reads one byte at a time and may be slower
* for large amounts of data
*/
size_t readUntil(std::shared_ptr<std::string> buffer, char terminator);
/**
* @brief Flushes the input buffer
*
* Discards any data that has been received but not yet read.
* Useful for clearing stale data before starting fresh communication.
*
* @throws SerialException if flush operation fails
*/
void flushInputBuffer();
/**
* @brief Sets the baud rate for serial communication
*
* Configures the speed of serial communication. Both input and output
* speeds are set to the same value.
*
* @param baud_rate The desired baud rate (e.g., 9600, 115200)
* @throws SerialException if baud rate cannot be set
*
* @note The port must be opened before calling this method
*/
void setBaudRate(unsigned int baud_rate);
/**
* @brief Gets the number of bytes available for reading
*
* Returns the number of bytes currently available in the input buffer
* without actually reading them. Useful for non-blocking operations.
*
* @return Number of bytes available for reading
* @throws SerialException if operation fails
*/
int getAvailableData() const;
/**
* @brief Sets the read timeout in milliseconds
*
* Configures the maximum time to wait for read operations before
* timing out. A value of 0 means no timeout (blocking).
*
* @param timeout Timeout in milliseconds
*
* @note The system timeout is set in deciseconds (100ms units), so the value
* will be rounded down to the nearest multiple of 100ms. For example,
* 1549ms will be set as 1500ms.
*/
void setReadTimeout(std::chrono::milliseconds timeout);
/**
* @brief Sets the write timeout in milliseconds
*
* Configures the maximum time to wait for write operations before
* timing out. A value of 0 means no timeout (blocking).
*
* @param timeout Timeout in milliseconds
* @throws SerialException if setting cannot be applied
*/
void setWriteTimeout(std::chrono::milliseconds timeout);
/**
* @brief Sets the number of data bits per byte
*
* Configures the number of data bits used in each byte of serial
* communication.
*
* @param nbits The desired number of data bits (5, 6, 7, or 8)
* @throws SerialException if number of bits cannot be set
*/
void setDataLength(DataLength nbits);
/**
* @brief Sets the parity configuration
*
* Configures the parity checking mechanism for serial communication.
*
* @param parity The desired parity setting (ENABLE or DISABLE)
* @throws SerialException if parity cannot be set
*/
void setParity(Parity parity);
/**
* @brief Sets the stop bits configuration
*
* Configures the number of stop bits used in serial communication.
*
* @param stop_bits The desired stop bits setting (ONE, ONE_AND_HALF, or TWO)
* @throws SerialException if stop bits cannot be set
*/
void setStopBits(StopBits stop_bits);
/**
* @brief Sets the flow control configuration
*
* Configures the flow control mechanism for serial communication.
*
* @param flow_control The desired flow control setting (HARDWARE or SOFTWARE)
* @throws SerialException if flow control cannot be set
*/
void setFlowControl([[maybe_unused]] FlowControl flow_control);
/**
* @brief Sets canonical mode for input processing
*
* Configures whether the input is processed in canonical (line-based)
* mode or non-canonical (raw) mode.
*
* @param canonical_mode The desired canonical mode setting (ENABLE or DISABLE)
* @throws SerialException if canonical mode cannot be set
*/
void setCanonicalMode(CanonicalMode mode);
/**
* @brief Sets the terminator character for readUntil operations
*
* Configures the character that signals the end of a readUntil operation.
*
* @param term The desired terminator character
* @throws SerialException if terminator cannot be set
*/
void setTerminator(Terminator term);
/**
* @brief Sets the read timeout in deciseconds
*
* @param time Timeout in deciseconds
* @throws SerialException if setting cannot be applied
*/
void setTimeOut(uint16_t time);
/**
* @brief Sets the minimum number of characters to read
*
* @param num Minimum number of characters to read
* @throws SerialException if setting cannot be applied
*/
void setMinNumberCharRead(uint16_t);
/**
* @brief Set the baud rate using BaudRate enum
*
* This overloaded version accepts a BaudRate enum value, providing
* type safety and preventing invalid baud rate values.
*
* @param baud_rate The baud rate from BaudRate enum
* @throws SerialException if baud rate cannot be set
*
* @note The port must be opened before calling this method
*/
void setBaudRate(BaudRate baud_rate);
/**
* @brief Sets the maximum safe read size
*
* Configures the maximum number of bytes that can be read
* in a single read operation to prevent excessive memory usage.
*
* @param size The desired maximum safe read size in bytes
*/
void setMaxSafeReadSize(size_t size);
/**
* @brief Gets the maximum safe read size
*
* Retrieves the maximum number of bytes that can be read
* in a single read operation to prevent excessive memory usage.
*
* @return The maximum safe read size in bytes
*/
size_t getMaxSafeReadSize() const;
/**
* @brief Gets the current baud rate
*
* @return The current baud rate
* @throws SerialException if unable to retrieve baud rate
*/
int getBaudRate() const;
/**
* @brief Gets the current data length setting
*
* Retrieves the number of data bits configured for serial communication.
*
* @return The current data length (number of data bits)
*/
DataLength getDataLength() const;
/**
* @brief Gets the current read timeout setting
*
* @return The current read timeout in milliseconds
*/
std::chrono::milliseconds getReadTimeout() const;
/**
* @brief Gets the current minimum number of characters to read setting
*
* @return The current minimum number of characters to read
*/
uint16_t getMinNumberCharRead() const;
#ifdef BUILD_TESTING_ON
// WARNING: Test helper only! This function bypasses normal initialization
// and may leave the Serial object in an inconsistent state. It is intended
// strictly for unit tests to inject a file descriptor and should NEVER be
// used in production code.
void setFdForTest(int fd) {
fd_serial_port_ = fd;
}
// WARNING: Test helper only! This function allows injection of custom
// system call functions for testing error handling. It should NEVER be
// used in production code.
void setPollSystemFunction(
std::function<int(struct pollfd*, nfds_t, int)> poll_func) {
poll_ = [poll_func](struct pollfd* f, nfds_t n, int t) {
return poll_func(f, n, t);
};
}
void setReadSystemFunction(
std::function<ssize_t(int, void*, size_t)> read_func) {
read_ = [read_func](int fd, void* buf, size_t sz) {
return read_func(fd, buf, sz);
};
}
/* *INDENT-OFF* */
void setIoctlSystemFunction(
std::function<int(int, unsigned long, void*)> ioctl_func) { // NOLINT
ioctl_ = [ioctl_func](int fd, unsigned long request, void* arg) { // NOLINT
return ioctl_func(fd, request, arg);
};
}
/* *INDENT-ON* */
#endif
private:
/**
* @brief Ioctl system call function wrapper
*
* Allows injection of custom ioctl function for testing.
*/
/* *INDENT-OFF* */
std::function<int(int, unsigned long, void*)> ioctl_ = // NOLINT
[](int fd, unsigned long request, void* arg) { // NOLINT
return ::ioctl(fd, request, arg);
};
/* *INDENT-ON* */
/**
* @brief Poll system call function wrapper
*
* Allows injection of custom poll function for testing.
*/
std::function<int(struct pollfd*, nfds_t, int)> poll_ =
[](struct pollfd* f, nfds_t n, int t) {
return ::poll(f, n, t);
};
/**
* @brief Read system call function wrapper
*
* Allows injection of custom read function for testing.
*/
std::function<ssize_t(int, void*, size_t)> read_ =
[](int fd, void* buf, size_t sz) {
return ::read(fd, buf, sz);
};
/**
* @brief Applies terminal settings to the port
*
* Internal method to write the termios2 configuration
* to the serial port file descriptor.
*
* @throws SerialException if ioctl operation fails
*/
void setTermios2();
/**
* @brief Retrieves current terminal settings
*
* Internal method to read the current termios2 configuration
* from the serial port file descriptor.
*
* @throws SerialException if ioctl operation fails
*/
void getTermios2() const;
/**
* @brief Terminal configuration structure
*
* Holds the current serial port configuration including
* baud rate, data bits, parity, stop bits, and other settings.
*/
mutable struct termios2 options_;
/**
* @brief File descriptor for the serial port
*
* The low-level file descriptor used for all read/write operations
* on the serial port. Set to -1 when no port is open.
*/
int fd_serial_port_{-1};
/**
* @brief Read timeout in milliseconds
*
* Specifies the maximum time to wait for read operations
* before timing out. Default is 1000ms.
*/
std::chrono::milliseconds read_timeout_ms_{1000}; ///< Read timeout in milliseconds (default 1000ms)
/**
* @brief Write timeout in milliseconds
*
* Specifies the maximum time to wait for write operations
* before timing out. Default is 1000ms.
*/
std::chrono::milliseconds write_timeout_ms_{1000}; ///< Write timeout in milliseconds (default 1000ms)
/**
* @brief Maximum safe read size
*
* Defines the maximum number of bytes that can be read
* in a single read operation to prevent excessive memory usage.
* Default is 2048 bytes (2KB).
*/
size_t max_safe_read_size_{2048}; // 2KB limit
/**
* @brief Minimum number of characters to read
*
* Specifies the minimum number of characters required for a read operation to return (default 0).
*/
uint16_t min_number_char_read_{0};
/**
* @brief Canonical mode setting
*
* Determines whether canonical (line-based) input processing is enabled (default ENABLE).
*/
CanonicalMode canonical_mode_{CanonicalMode::ENABLE};
/**
* @brief Line terminator character
*
* Specifies the character used to terminate lines (default LF).
*/
Terminator terminator_{Terminator::LF};
};
} // namespace libserial
#endif // INCLUDE_LIBSERIAL_SERIAL_HPP_