Skip to main content

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}