#[cfg(feature = "native")]
pub mod native;
#[cfg(feature = "wasm")]
pub mod wasm;
use {
crate::error::Result,
std::{
io::{Read, Write},
time::Duration,
},
};
#[derive(Debug, Clone)]
pub struct SerialConfig {
pub port_name: String,
pub baud_rate: u32,
pub timeout: Duration,
pub data_bits: DataBits,
pub parity: Parity,
pub stop_bits: StopBits,
pub flow_control: FlowControl,
}
impl Default for SerialConfig {
fn default() -> Self {
Self {
port_name: String::new(),
baud_rate: 115200,
timeout: Duration::from_millis(1000),
data_bits: DataBits::Eight,
parity: Parity::None,
stop_bits: StopBits::One,
flow_control: FlowControl::None,
}
}
}
impl SerialConfig {
pub fn new(port_name: impl Into<String>, baud_rate: u32) -> Self {
Self {
port_name: port_name.into(),
baud_rate,
..Default::default()
}
}
#[must_use]
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DataBits {
Five,
Six,
Seven,
#[default]
Eight,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Parity {
#[default]
None,
Odd,
Even,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum StopBits {
#[default]
One,
Two,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FlowControl {
#[default]
None,
Hardware,
Software,
}
#[derive(Debug, Clone)]
pub struct PortInfo {
pub name: String,
pub vid: Option<u16>,
pub pid: Option<u16>,
pub manufacturer: Option<String>,
pub product: Option<String>,
pub serial_number: Option<String>,
}
pub trait Port: Read + Write + Send {
fn set_timeout(&mut self, timeout: Duration) -> Result<()>;
fn timeout(&self) -> Duration;
fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()>;
fn baud_rate(&self) -> u32;
fn clear_buffers(&mut self) -> Result<()>;
fn name(&self) -> &str;
fn set_dtr(&mut self, level: bool) -> Result<()>;
fn set_rts(&mut self, level: bool) -> Result<()>;
fn read_cts(&mut self) -> Result<bool>;
fn read_dsr(&mut self) -> Result<bool>;
fn close(&mut self) -> Result<()>;
fn write_all_bytes(&mut self, buf: &[u8]) -> Result<()> {
std::io::Write::write_all(self, buf)?;
std::io::Write::flush(self)?;
Ok(())
}
}
pub trait PortEnumerator {
fn list_ports() -> Result<Vec<PortInfo>>;
fn find_by_vid_pid(vid: u16, pid: u16) -> Result<Vec<PortInfo>> {
let ports = Self::list_ports()?;
Ok(ports
.into_iter()
.filter(|p| p.vid == Some(vid) && p.pid == Some(pid))
.collect())
}
}
#[cfg(feature = "native")]
pub use native::{NativePort, NativePortEnumerator};
#[cfg(feature = "wasm")]
pub use wasm::{WebSerialPort, WebSerialPortEnumerator};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serial_config_new() {
let config = SerialConfig::new("/dev/ttyUSB0", 921600);
assert_eq!(config.port_name, "/dev/ttyUSB0");
assert_eq!(config.baud_rate, 921600);
assert_eq!(config.data_bits, DataBits::Eight);
assert_eq!(config.parity, Parity::None);
assert_eq!(config.stop_bits, StopBits::One);
assert_eq!(config.flow_control, FlowControl::None);
}
#[test]
fn test_serial_config_default() {
let config = SerialConfig::default();
assert!(
config
.port_name
.is_empty()
);
assert_eq!(config.baud_rate, 115200);
assert_eq!(config.timeout, Duration::from_millis(1000));
}
#[test]
fn test_serial_config_with_timeout() {
let config = SerialConfig::new("COM3", 9600).with_timeout(Duration::from_secs(5));
assert_eq!(config.timeout, Duration::from_secs(5));
assert_eq!(config.baud_rate, 9600);
}
#[test]
fn test_serial_config_from_string() {
let config = SerialConfig::new(String::from("/dev/ttyACM0"), 115200);
assert_eq!(config.port_name, "/dev/ttyACM0");
}
#[test]
fn test_data_bits_default() {
assert_eq!(DataBits::default(), DataBits::Eight);
}
#[test]
fn test_parity_default() {
assert_eq!(Parity::default(), Parity::None);
}
#[test]
fn test_stop_bits_default() {
assert_eq!(StopBits::default(), StopBits::One);
}
#[test]
fn test_flow_control_default() {
assert_eq!(FlowControl::default(), FlowControl::None);
}
#[test]
fn test_port_info_fields() {
let info = PortInfo {
name: "/dev/ttyUSB0".to_string(),
vid: Some(0x1A86),
pid: Some(0x7523),
manufacturer: Some("QinHeng".to_string()),
product: Some("CH340".to_string()),
serial_number: None,
};
assert_eq!(info.name, "/dev/ttyUSB0");
assert_eq!(info.vid, Some(0x1A86));
assert_eq!(info.pid, Some(0x7523));
assert!(
info.serial_number
.is_none()
);
}
#[test]
fn test_port_info_clone() {
let info = PortInfo {
name: "COM3".to_string(),
vid: None,
pid: None,
manufacturer: None,
product: None,
serial_number: None,
};
let cloned = info.clone();
assert_eq!(info.name, cloned.name);
}
#[test]
fn test_enums_are_copy() {
let db = DataBits::Eight;
let db2 = db; assert_eq!(db, db2);
let p = Parity::Even;
let p2 = p;
assert_eq!(p, p2);
let sb = StopBits::Two;
let sb2 = sb;
assert_eq!(sb, sb2);
let fc = FlowControl::Hardware;
let fc2 = fc;
assert_eq!(fc, fc2);
}
}