rtcom_core/config.rs
1//! Serial line configuration and modem-status types.
2//!
3//! These are the framing and flow parameters every [`SerialDevice`] needs to
4//! expose. They intentionally mirror the classic `termios` model (data bits /
5//! stop bits / parity / flow control) so behaviour lines up with user
6//! expectations inherited from `picocom` and `tio`.
7//!
8//! [`SerialDevice`]: crate::SerialDevice
9
10use std::time::Duration;
11
12/// Default read poll timeout used by blocking backends.
13///
14/// The async backend does not gate reads on this value, but it is stored so
15/// [`SerialConfig`] can be printed verbatim and so future blocking fallbacks
16/// behave consistently.
17pub const DEFAULT_READ_TIMEOUT: Duration = Duration::from_millis(100);
18
19/// Number of data bits per frame.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum DataBits {
22 /// 5 data bits per frame.
23 Five,
24 /// 6 data bits per frame.
25 Six,
26 /// 7 data bits per frame.
27 Seven,
28 /// 8 data bits per frame (the default and the only mode most USB-serial
29 /// bridges support).
30 Eight,
31}
32
33impl DataBits {
34 /// Returns the numeric width in bits.
35 #[must_use]
36 pub const fn bits(self) -> u8 {
37 match self {
38 Self::Five => 5,
39 Self::Six => 6,
40 Self::Seven => 7,
41 Self::Eight => 8,
42 }
43 }
44}
45
46/// Number of stop bits appended after the data / parity bits.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum StopBits {
49 /// One stop bit (default).
50 One,
51 /// Two stop bits.
52 Two,
53}
54
55/// Parity mode.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub enum Parity {
58 /// No parity bit (default).
59 None,
60 /// Even parity.
61 Even,
62 /// Odd parity.
63 Odd,
64 /// Mark parity (parity bit always 1). Rare; not supported on all
65 /// platforms.
66 Mark,
67 /// Space parity (parity bit always 0). Rare; not supported on all
68 /// platforms.
69 Space,
70}
71
72/// Flow-control mode.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74pub enum FlowControl {
75 /// No flow control (default).
76 None,
77 /// Hardware flow control using the RTS/CTS lines.
78 Hardware,
79 /// Software flow control using XON/XOFF bytes (0x11 / 0x13).
80 Software,
81}
82
83/// Snapshot of the input-side modem control lines.
84///
85/// Returned by [`SerialDevice::modem_status`](crate::SerialDevice::modem_status).
86/// Each field is `true` when the corresponding line is asserted. The struct
87/// is deliberately a flat record of four booleans — it mirrors the hardware
88/// register one-to-one — so the `struct_excessive_bools` lint does not apply.
89#[allow(clippy::struct_excessive_bools)]
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
91pub struct ModemStatus {
92 /// Clear to Send.
93 pub cts: bool,
94 /// Data Set Ready.
95 pub dsr: bool,
96 /// Ring Indicator.
97 pub ri: bool,
98 /// Carrier Detect.
99 pub cd: bool,
100}
101
102/// Full serial-link configuration.
103///
104/// `SerialConfig` is what the CLI builds from command-line flags (see
105/// `rtcom-cli` Issue #3) and what the session orchestrator hands to a
106/// [`SerialDevice`](crate::SerialDevice) at open time. It is also what
107/// [`SerialDevice::config`](crate::SerialDevice::config) returns so runtime
108/// code can display or serialize the current link parameters.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct SerialConfig {
111 /// Baud rate in bits per second.
112 pub baud_rate: u32,
113 /// Data bits per frame.
114 pub data_bits: DataBits,
115 /// Stop bits per frame.
116 pub stop_bits: StopBits,
117 /// Parity mode.
118 pub parity: Parity,
119 /// Flow-control mode.
120 pub flow_control: FlowControl,
121 /// Timeout used by blocking reads (unused on the async path, but kept so
122 /// `config()` remains a faithful record of the requested settings).
123 pub read_timeout: Duration,
124}
125
126impl Default for SerialConfig {
127 /// Returns the tio/picocom-compatible default: `115200 8N1`, no flow control.
128 fn default() -> Self {
129 Self {
130 baud_rate: 115_200,
131 data_bits: DataBits::Eight,
132 stop_bits: StopBits::One,
133 parity: Parity::None,
134 flow_control: FlowControl::None,
135 read_timeout: DEFAULT_READ_TIMEOUT,
136 }
137 }
138}
139
140impl SerialConfig {
141 /// Validates that the configuration is internally consistent.
142 ///
143 /// Currently only rejects a zero baud rate; more checks (e.g. disallowing
144 /// `Mark`/`Space` on platforms that don't implement them) may be added in
145 /// the future.
146 ///
147 /// # Errors
148 ///
149 /// Returns [`Error::InvalidConfig`](crate::Error::InvalidConfig) if the
150 /// configuration cannot be used to open a device.
151 pub fn validate(&self) -> crate::Result<()> {
152 if self.baud_rate == 0 {
153 return Err(crate::Error::InvalidConfig(
154 "baud_rate must be non-zero".into(),
155 ));
156 }
157 Ok(())
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn default_is_115200_8n1_no_flow() {
167 let cfg = SerialConfig::default();
168 assert_eq!(cfg.baud_rate, 115_200);
169 assert_eq!(cfg.data_bits, DataBits::Eight);
170 assert_eq!(cfg.stop_bits, StopBits::One);
171 assert_eq!(cfg.parity, Parity::None);
172 assert_eq!(cfg.flow_control, FlowControl::None);
173 }
174
175 #[test]
176 fn data_bits_width_matches_enum() {
177 assert_eq!(DataBits::Five.bits(), 5);
178 assert_eq!(DataBits::Six.bits(), 6);
179 assert_eq!(DataBits::Seven.bits(), 7);
180 assert_eq!(DataBits::Eight.bits(), 8);
181 }
182
183 #[test]
184 fn validate_rejects_zero_baud() {
185 let cfg = SerialConfig {
186 baud_rate: 0,
187 ..SerialConfig::default()
188 };
189 assert!(cfg.validate().is_err());
190 }
191
192 #[test]
193 fn validate_accepts_default() {
194 assert!(SerialConfig::default().validate().is_ok());
195 }
196}