Branch data Line data Source code
1 : : /*
2 : : * Copyright 2025 Jinwoo Sung
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License");
5 : : * you may not use this file except in compliance with the License.
6 : : * You may obtain a copy of the License at
7 : : *
8 : : * http://www.apache.org/licenses/LICENSE-2.0
9 : : *
10 : : * Unless required by applicable law or agreed to in writing, software
11 : : * distributed under the License is distributed on an "AS IS" BASIS,
12 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : : * See the License for the specific language governing permissions and
14 : : * limitations under the License.
15 : : */
16 : :
17 : : #pragma once
18 : :
19 : : #include <optional>
20 : : #include <string>
21 : :
22 : : #include "wirestead/base/constants.hpp"
23 : : #include "wirestead/util/input_validator.hpp"
24 : :
25 : : namespace wirestead {
26 : : namespace config {
27 : :
28 : : struct SerialConfig {
29 : : #ifdef _WIN32
30 : : std::string device = "COM1";
31 : : #else
32 : : std::string device = "/dev/ttyUSB0";
33 : : #endif
34 : : unsigned baud_rate = 115200;
35 : : unsigned char_size = 8; // 5,6,7,8
36 : : enum class Parity { None, Even, Odd } parity = Parity::None;
37 : : unsigned stop_bits = 1; // 1 or 2
38 : : enum class Flow { None, Software, Hardware } flow = Flow::None;
39 : :
40 : : size_t read_chunk = base::constants::DEFAULT_READ_BUFFER_SIZE;
41 : :
42 : : // Ask the kernel driver to hand bytes up as soon as they arrive instead of
43 : : // waiting out its buffering timer. On Linux this is ASYNC_LOW_LATENCY, and
44 : : // it matters most on USB serial adapters: an FTDI defaults to a 16 ms
45 : : // latency timer, so a 1 ms packet at 115200 baud can still reach the
46 : : // callback 16 ms late — enough to break a control loop on its own.
47 : : //
48 : : // Best effort by design. Drivers that have no such timer (CDC-ACM, most
49 : : // native UARTs) reject the request, and that is not an error: the port opens
50 : : // and runs normally either way. Set false to leave the driver's default
51 : : // alone, which trades latency for fewer wakeups.
52 : : bool low_latency = true;
53 : :
54 : : // Half-duplex RS-485, the wiring behind Dynamixel servos, Modbus RTU and a
55 : : // good deal of industrial sensing. The driver has to drive the transceiver's
56 : : // direction pin around each write, which is not something a caller can do
57 : : // from userspace at the right instant - hence a setting rather than advice.
58 : : //
59 : : // Linux only (TIOCSRS485). Adapters that switch direction in hardware need
60 : : // none of this and will refuse it; that refusal is not an error.
61 : : struct Rs485 {
62 : : bool enabled = false;
63 : : // Logical level RTS takes while transmitting. The usual wiring wants it
64 : : // high to send, but the opposite exists and produces a link that looks
65 : : // dead in one direction only.
66 : : bool rts_on_send = true;
67 : : // Whether the receiver stays on during transmit. Off for true half duplex,
68 : : // which is what stops a device hearing its own echo.
69 : : bool rx_during_tx = false;
70 : : // Milliseconds the driver holds the direction pin either side of the
71 : : // frame. Both default to 0 because most transceivers need nothing; slow
72 : : // ones need a millisecond or two, and no model can guess which - this is
73 : : // the knob for the hardware in front of you.
74 : : unsigned delay_rts_before_send_ms = 0;
75 : : unsigned delay_rts_after_send_ms = 0;
76 : : } rs485;
77 : :
78 : : // Modem control lines, applied once the port is open and only when set.
79 : : // std::nullopt means "leave the driver's default alone", which is a different
80 : : // request from "drive it low". Neither prevents a board that resets on DTR,
81 : : // such as an Arduino, from rebooting at open: Linux asserts DTR while opening
82 : : // the tty, before these apply (#710).
83 : : std::optional<bool> dtr;
84 : : std::optional<bool> rts;
85 : :
86 : : bool reopen_on_error = true; // Attempt to reopen on device disconnection/error
87 : :
88 : : // Reopen the port when no data has been *received* for this long. 0 disables
89 : : // it, which is the default.
90 : : //
91 : : // The failure this catches is a device that stops streaming without ever
92 : : // reporting an error - a wedged USB adapter, a sensor that stopped talking -
93 : : // where every other mechanism here sees a perfectly healthy open port and
94 : : // waits forever.
95 : : //
96 : : // Receive-only on purpose, unlike the TCP idle timeout, which any traffic in
97 : : // either direction resets. A driver polling a mute device writes on schedule
98 : : // and would keep a bidirectional timer alive forever, which is exactly the
99 : : // case worth catching.
100 : : //
101 : : // Expiry runs the same path as a read error, so reopen_on_error decides
102 : : // whether the port is reopened or the link goes to Error.
103 : : unsigned rx_idle_timeout_ms = 0;
104 : : size_t backpressure_threshold = base::constants::DEFAULT_BACKPRESSURE_THRESHOLD;
105 : : base::constants::BackpressureStrategy backpressure_strategy = base::constants::BackpressureStrategy::Reliable;
106 : : bool enable_memory_pool = true;
107 : : // Receive callback exceptions request quiet stop when true; false logs and continues receiving.
108 : : bool stop_on_callback_exception = false;
109 : :
110 : : unsigned retry_interval_ms = base::constants::DEFAULT_RETRY_INTERVAL_MS;
111 : : int max_retries = base::constants::DEFAULT_MAX_RETRIES;
112 : :
113 : : // Opt into the shared IoContextManager singleton instead of a dedicated
114 : : // io_context + thread (the default since #440). Only meaningful for
115 : : // deliberately trading per-instance parallelism for reduced thread/memory
116 : : // overhead across many instances in one process.
117 : : bool use_shared_context = false;
118 : :
119 : : // Linux opt-in: assert DTR and clear HUPCL after open so later closes and
120 : : // reopens can retain DTR. Cannot prevent a reset at the first open or USB
121 : : // disconnect. The tty setting persists after stop; false leaves it untouched.
122 : : // Combining this with dtr=false is invalid. Unsupported drivers fail to connect.
123 : : bool keep_dtr_on_close = false;
124 : :
125 : : // Validation methods
126 : 348 : bool is_valid() const {
127 [ + + + + ]: 348 : return (!keep_dtr_on_close || dtr != false) && read_chunk >= base::constants::MIN_READ_BUFFER_SIZE &&
128 [ + - + - : 346 : read_chunk <= base::constants::MAX_READ_BUFFER_SIZE && util::InputValidator::is_valid_device_path(device) &&
+ + ]
129 [ + + + + ]: 344 : baud_rate >= base::constants::MIN_BAUD_RATE && baud_rate <= base::constants::MAX_BAUD_RATE &&
130 [ + + + - : 341 : char_size >= 5 && char_size <= 8 && (stop_bits == 1 || stop_bits == 2) &&
+ + + + ]
131 [ + + ]: 338 : retry_interval_ms >= base::constants::MIN_RETRY_INTERVAL_MS &&
132 [ + + ]: 337 : retry_interval_ms <= base::constants::MAX_RETRY_INTERVAL_MS &&
133 [ + + ]: 336 : backpressure_threshold >= base::constants::MIN_BACKPRESSURE_THRESHOLD &&
134 [ + + ]: 335 : backpressure_threshold <= base::constants::MAX_BACKPRESSURE_THRESHOLD &&
135 [ + + + - ]: 334 : (rx_idle_timeout_ms == 0 || (rx_idle_timeout_ms >= base::constants::MIN_IDLE_TIMEOUT_MS &&
136 [ + + + - ]: 699 : rx_idle_timeout_ms <= base::constants::MAX_IDLE_TIMEOUT_MS)) &&
137 [ + + + + : 682 : (max_retries == -1 || (max_retries >= 0 && max_retries <= base::constants::MAX_RETRIES_LIMIT));
- + ]
138 : : }
139 : :
140 : : // Apply validation and clamp values to valid ranges
141 : 7 : void validate_and_clamp() {
142 : : // Same bounds the TCP and UDS configs apply to read_buffer_size: this is
143 : : // the per-connection userspace read buffer under a different name, and an
144 : : // unclamped 0 reaches the transport as rx_.resize(0).
145 [ + + ]: 7 : if (read_chunk < base::constants::MIN_READ_BUFFER_SIZE) {
146 : 1 : read_chunk = base::constants::MIN_READ_BUFFER_SIZE;
147 [ + + ]: 6 : } else if (read_chunk > base::constants::MAX_READ_BUFFER_SIZE) {
148 : 1 : read_chunk = base::constants::MAX_READ_BUFFER_SIZE;
149 : : }
150 : :
151 [ + + ]: 7 : if (baud_rate < base::constants::MIN_BAUD_RATE) {
152 : 1 : baud_rate = base::constants::MIN_BAUD_RATE;
153 [ + + ]: 6 : } else if (baud_rate > base::constants::MAX_BAUD_RATE) {
154 : 1 : baud_rate = base::constants::MAX_BAUD_RATE;
155 : : }
156 : :
157 [ + + ]: 7 : if (char_size < 5)
158 : 1 : char_size = 5;
159 [ + + ]: 6 : else if (char_size > 8)
160 : 1 : char_size = 8;
161 : :
162 [ + + + - ]: 7 : if (stop_bits != 1 && stop_bits != 2) stop_bits = 1;
163 : :
164 [ + + ]: 7 : if (retry_interval_ms < base::constants::MIN_RETRY_INTERVAL_MS) {
165 : 1 : retry_interval_ms = base::constants::MIN_RETRY_INTERVAL_MS;
166 [ + + ]: 6 : } else if (retry_interval_ms > base::constants::MAX_RETRY_INTERVAL_MS) {
167 : 1 : retry_interval_ms = base::constants::MAX_RETRY_INTERVAL_MS;
168 : : }
169 : :
170 [ + + ]: 7 : if (backpressure_threshold < base::constants::MIN_BACKPRESSURE_THRESHOLD) {
171 : 1 : backpressure_threshold = base::constants::MIN_BACKPRESSURE_THRESHOLD;
172 [ + + ]: 6 : } else if (backpressure_threshold > base::constants::MAX_BACKPRESSURE_THRESHOLD) {
173 : 1 : backpressure_threshold = base::constants::MAX_BACKPRESSURE_THRESHOLD;
174 : : }
175 : :
176 [ - + - - ]: 7 : if (rx_idle_timeout_ms != 0 && rx_idle_timeout_ms > base::constants::MAX_IDLE_TIMEOUT_MS) {
177 : 0 : rx_idle_timeout_ms = base::constants::MAX_IDLE_TIMEOUT_MS;
178 : : }
179 : :
180 [ + + + - ]: 7 : if (max_retries != -1 && max_retries > base::constants::MAX_RETRIES_LIMIT) {
181 : 1 : max_retries = base::constants::MAX_RETRIES_LIMIT;
182 : : }
183 : 7 : }
184 : : };
185 : :
186 : : } // namespace config
187 : : } // namespace wirestead
|