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
//! serial-rs is a cross-platform serial library
//! A lot of the code here is based on the [Pyserial project](https://github.com/pyserial/pyserial)

#![deny(
    missing_docs,
    missing_debug_implementations,
    missing_copy_implementations,
    dead_code,
    while_true
)]
use std::io::ErrorKind;

#[allow(unused)]
const XON: i8 = 17;
#[allow(unused)]
const XOFF: i8 = 19;
#[allow(unused)]
const CR: i8 = 13;
#[allow(unused)]
const LF: i8 = 10;

#[cfg(unix)]
pub mod posix;

#[cfg(windows)]
pub mod windows;

/// Serial port result type
pub type SerialResult<T> = std::result::Result<T, SerialError>;

/// Serial port error type
pub enum SerialError {
    /// IO Error
    IoError(std::io::Error),
    /// OS Specific error
    OsError {
        /// OS Error code
        code: u32,
        /// OS Error description
        desc: String,
    },
    /// Internal library error
    LibraryError(String)
}

impl std::fmt::Debug for SerialError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::IoError(arg0) => f.debug_tuple("IoError").field(arg0).finish(),
            Self::OsError { code, desc } => f
                .debug_struct("OsError")
                .field("code", code)
                .field("desc", desc)
                .finish(),
            SerialError::LibraryError(e) => f.debug_tuple("LibraryError").field(e).finish(),
        }
    }
}

impl std::fmt::Display for SerialError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SerialError::IoError(e) => {
                write!(f, "IoError {}", e)
            }
            SerialError::OsError { code, desc } => write!(f, "OsError {code} ({desc})"),
            SerialError::LibraryError(e) => write!(f, "Serial-RS Lib error '{e}'"),
        }
    }
}

impl std::error::Error for SerialError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        if let Self::IoError(e) = self {
            Some(e)
        } else {
            None
        }
    }
}

/// Serial port settings
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct SerialPortSettings {
    baud_rate: u32,
    byte_size: ByteSize,
    parity: Parity,
    stop_bits: StopBits,
    read_timeout: Option<u128>,
    flow_control: FlowControl,
    write_timeout: Option<u128>,
    inter_byte_timeout: Option<u128>
}

impl Default for SerialPortSettings {
    fn default() -> Self {
        Self {
            baud_rate: 9600,
            byte_size: ByteSize::Eight,
            parity: Parity::None,
            stop_bits: StopBits::One,
            read_timeout: None,
            write_timeout: None,
            flow_control: FlowControl::None,
            inter_byte_timeout: None,
        }
    }
}

#[allow(missing_docs)]
impl SerialPortSettings {
    /// Set baud rate
    pub fn baud(mut self, baud: u32) -> Self {
        self.baud_rate = baud;
        self
    }

    pub fn read_timeout(mut self, timeout: Option<u128>) -> Self {
        self.read_timeout = timeout;
        self
    }

    pub fn byte_size(mut self, byte_size: ByteSize) -> Self {
        self.byte_size = byte_size;
        self
    }

    pub fn write_timeout(mut self, timeout: Option<u128>) -> Self {
        self.write_timeout = timeout;
        self
    }

    pub fn parity(mut self, parity: Parity) -> Self {
        self.parity = parity;
        self
    }

    pub fn stop_bits(mut self, stop_bits: StopBits) -> Self {
        self.stop_bits = stop_bits;
        self
    }

    pub fn set_flow_control(mut self, method: FlowControl) -> Self {
        self.flow_control = method;
        self
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
/// Flow control method
pub enum FlowControl {
    /// No flow control
    None,
    /// DSR DTR flow control (Software)
    DsrDtr,
    /// XON XOFF flow control (Software)
    XonXoff,
    /// CTS RTS flow control (Hardware)
    RtsCts
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
/// Bytesize for serial port
pub enum ByteSize {
    /// 5 bits
    Five,
    /// 6 bits
    Six,
    /// 7 bits
    Seven,
    /// 8 bits
    Eight,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
/// Parity definitions
pub enum Parity {
    /// No parity
    None,
    /// Even parity
    Even,
    /// Odd parity
    Odd,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
/// Stop bits for serial port
pub enum StopBits {
    /// 1 stop bit
    One,
    /// 1.5 stop bits
    OnePointFive,
    /// 2 stop bits
    Two,
}

/// Information on a listed serial port
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct PortInfo {
    /// Name of the device
    port: String,
    /// Hardware-ID of the device
    hwid: String,
    /// Vendor ID
    vid: u16,
    /// Product ID
    pid: u16,
    /// Manufacturer
    manufacturer: String,
    /// Description of the device
    description: String,
}

impl PortInfo {
    /// Gets port name
    pub fn get_port(&self) -> &str { &self.port }
    /// Gets port system hardware-ID
    pub fn get_hwid(&self) -> &str { &self.hwid }
    /// Gets port devices' ProductID
    pub fn get_pid(&self) -> u16 { self.pid }
    /// Gets port devices' VendorID
    pub fn get_vid(&self) -> u16 { self.vid }
    /// Gets port devices' manufacturer
    pub fn get_manufacturer(&self) -> &str { &self.manufacturer }
    /// Gets port devices' description
    pub fn get_desc(&self) -> &str { &self.description }
}

/// Serial port trait
pub trait SerialPort: Send + std::io::Write + std::io::Read {
    /// Reconfigures an open port with the current settings
    fn reconfigure_port(&mut self) -> SerialResult<()>;
    /// Closes the port
    fn close(self) -> SerialResult<()>;
    /// Sets Tx and Rx buffer size. A sensible value for these is 4096 bytes
    fn set_buffer_size(&mut self, rx_size: usize, tx_size: usize) -> SerialResult<()>;
    /// Sets flow control state manually
    fn set_output_flow_control(&self, enable: bool) -> SerialResult<()>;
    /// Sets data terminal flag
    fn set_data_terminal_ready(&mut self, enable: bool) -> SerialResult<()>;
    /// Sets request to send flag
    fn set_request_to_send(&mut self, enable: bool) -> SerialResult<()>;
    /// Sets break state flag
    fn set_break_state(&mut self, enable: bool) -> SerialResult<()>;
    /// Reads clear to send flag
    fn read_clear_to_send(&self) -> SerialResult<bool>;
    /// Reads data set ready flag
    fn read_data_set_ready(&self) -> SerialResult<bool>;
    /// Reads ring indicator flag
    fn read_ring_indicator(&self) -> SerialResult<bool>;
    /// Reads carrier detect flag
    fn read_carrier_detect(&self) -> SerialResult<bool>;
    /// Returns number of bytes left to read in serial buffer
    fn bytes_to_read(&self) -> SerialResult<usize>;
    /// Returns number of bytes left to write in serial buffer
    fn bytes_to_write(&self) -> SerialResult<usize>;
    /// Gets the path of the port
    fn get_path(&self) -> String;
    /// Tries to clone the port.
    /// 
    /// # Note about cloning
    /// You must be careful when cloning a port as this can have interesting
    /// effects. For example, if one thread tries to close the port but another
    /// thread wants the port open
    fn try_clone(&mut self) -> SerialResult<Box<dyn SerialPort>>;
    /// Clears serial input buffer
    fn clear_input_buffer(&mut self) -> SerialResult<()>;
    /// Clears serial output buffer
    fn clear_output_buffer(&mut self) -> SerialResult<()>;
}

/// Scanner to list avaliable serial ports on a system
pub trait PortScanner {
    /// Lists avaliable serial ports on a system
    fn list_devices(&mut self) -> SerialResult<Vec<PortInfo>>;
}

impl From<SerialError> for std::io::Error {
    fn from(e: SerialError) -> Self {
        match e {
            SerialError::IoError(i) => i,
            SerialError::OsError { code: _ , desc } => std::io::Error::new(ErrorKind::Other, desc),
            SerialError::LibraryError(e) => std::io::Error::new(ErrorKind::Other, e),
        }
    }
}

/// Creates a new serial port from port info
pub fn new(info: PortInfo, settings: Option<SerialPortSettings>) -> SerialResult<Box<dyn SerialPort>> {
    #[cfg(unix)]
    {
        use posix::*;
        Ok(Box::new(TTYPort::new(info.port, settings)?))
    }
    #[cfg(windows)]
    {
        use windows::*;
        Ok(Box::new(COMPort::new(info.port, settings)?))
    }
}

/// Creates a new serial port from port path
pub fn new_from_path(path: &str, settings: Option<SerialPortSettings>) -> SerialResult<Box<dyn SerialPort>> {
    #[cfg(unix)]
    {
        use posix::*;
        Ok(Box::new(TTYPort::new(path.to_string(), settings)?))
    }
    #[cfg(windows)]
    {
        use windows::*;
        Ok(Box::new(COMPort::new(path.to_string(), settings)?))
    }
}

/// Lists all ports on the system
pub fn list_ports() -> SerialResult<Vec<PortInfo>> {
    #[cfg(unix)]
    {
        use posix::port_lister::TTYPortScanner;
        TTYPortScanner{}.list_devices()
    }
    #[cfg(windows)]
    {
        use windows::port_lister::COMPortLister;
        COMPortLister{}.list_devices()
    }
}