use std::pin::Pin;
use std::task::{Context, Poll};
use std::thread;
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio_serial::{SerialPort, SerialPortBuilderExt, SerialStream};
use crate::config::{DataBits, FlowControl, ModemStatus, Parity, SerialConfig, StopBits};
use crate::error::Result;
pub trait SerialDevice: AsyncRead + AsyncWrite + Send + Unpin {
fn set_baud_rate(&mut self, baud: u32) -> Result<()>;
fn set_data_bits(&mut self, bits: DataBits) -> Result<()>;
fn set_stop_bits(&mut self, bits: StopBits) -> Result<()>;
fn set_parity(&mut self, parity: Parity) -> Result<()>;
fn set_flow_control(&mut self, flow: FlowControl) -> Result<()>;
fn set_dtr(&mut self, level: bool) -> Result<()>;
fn set_rts(&mut self, level: bool) -> Result<()>;
fn send_break(&mut self, duration: Duration) -> Result<()>;
fn modem_status(&mut self) -> Result<ModemStatus>;
fn config(&self) -> &SerialConfig;
}
pub struct SerialPortDevice {
stream: SerialStream,
config: SerialConfig,
}
impl SerialPortDevice {
pub fn open(path: &str, config: SerialConfig) -> Result<Self> {
config.validate()?;
let stream = tokio_serial::new(path, config.baud_rate)
.data_bits(to_sp_data_bits(config.data_bits))
.stop_bits(to_sp_stop_bits(config.stop_bits))
.parity(to_sp_parity(config.parity))
.flow_control(to_sp_flow(config.flow_control))
.timeout(config.read_timeout)
.open_native_async()?;
Ok(Self { stream, config })
}
#[cfg(unix)]
pub fn pair() -> Result<(Self, Self)> {
let (a, b) = SerialStream::pair()?;
let config = SerialConfig::default();
Ok((Self { stream: a, config }, Self { stream: b, config }))
}
}
impl AsyncRead for SerialPortDevice {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.stream).poll_read(cx, buf)
}
}
impl AsyncWrite for SerialPortDevice {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<std::io::Result<usize>> {
Pin::new(&mut self.stream).poll_write(cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.stream).poll_flush(cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.stream).poll_shutdown(cx)
}
}
impl SerialDevice for SerialPortDevice {
fn set_baud_rate(&mut self, baud: u32) -> Result<()> {
if baud == 0 {
return Err(crate::Error::InvalidConfig(
"baud_rate must be non-zero".into(),
));
}
self.stream.set_baud_rate(baud)?;
self.config.baud_rate = baud;
Ok(())
}
fn set_data_bits(&mut self, bits: DataBits) -> Result<()> {
self.stream.set_data_bits(to_sp_data_bits(bits))?;
self.config.data_bits = bits;
Ok(())
}
fn set_stop_bits(&mut self, bits: StopBits) -> Result<()> {
self.stream.set_stop_bits(to_sp_stop_bits(bits))?;
self.config.stop_bits = bits;
Ok(())
}
fn set_parity(&mut self, parity: Parity) -> Result<()> {
self.stream.set_parity(to_sp_parity(parity))?;
self.config.parity = parity;
Ok(())
}
fn set_flow_control(&mut self, flow: FlowControl) -> Result<()> {
self.stream.set_flow_control(to_sp_flow(flow))?;
self.config.flow_control = flow;
Ok(())
}
fn set_dtr(&mut self, level: bool) -> Result<()> {
self.stream.write_data_terminal_ready(level)?;
Ok(())
}
fn set_rts(&mut self, level: bool) -> Result<()> {
self.stream.write_request_to_send(level)?;
Ok(())
}
fn send_break(&mut self, duration: Duration) -> Result<()> {
self.stream.set_break()?;
thread::sleep(duration);
self.stream.clear_break()?;
Ok(())
}
fn modem_status(&mut self) -> Result<ModemStatus> {
Ok(ModemStatus {
cts: self.stream.read_clear_to_send()?,
dsr: self.stream.read_data_set_ready()?,
ri: self.stream.read_ring_indicator()?,
cd: self.stream.read_carrier_detect()?,
})
}
fn config(&self) -> &SerialConfig {
&self.config
}
}
const fn to_sp_data_bits(b: DataBits) -> serialport::DataBits {
match b {
DataBits::Five => serialport::DataBits::Five,
DataBits::Six => serialport::DataBits::Six,
DataBits::Seven => serialport::DataBits::Seven,
DataBits::Eight => serialport::DataBits::Eight,
}
}
const fn to_sp_stop_bits(b: StopBits) -> serialport::StopBits {
match b {
StopBits::One => serialport::StopBits::One,
StopBits::Two => serialport::StopBits::Two,
}
}
const fn to_sp_parity(p: Parity) -> serialport::Parity {
match p {
Parity::Even => serialport::Parity::Even,
Parity::Odd => serialport::Parity::Odd,
Parity::None | Parity::Mark | Parity::Space => serialport::Parity::None,
}
}
const fn to_sp_flow(f: FlowControl) -> serialport::FlowControl {
match f {
FlowControl::None => serialport::FlowControl::None,
FlowControl::Hardware => serialport::FlowControl::Hardware,
FlowControl::Software => serialport::FlowControl::Software,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn data_bits_round_trip() {
assert_eq!(
to_sp_data_bits(DataBits::Eight),
serialport::DataBits::Eight
);
assert_eq!(to_sp_data_bits(DataBits::Five), serialport::DataBits::Five);
}
#[test]
fn stop_bits_round_trip() {
assert_eq!(to_sp_stop_bits(StopBits::One), serialport::StopBits::One);
assert_eq!(to_sp_stop_bits(StopBits::Two), serialport::StopBits::Two);
}
#[test]
fn parity_round_trip() {
assert_eq!(to_sp_parity(Parity::Even), serialport::Parity::Even);
assert_eq!(to_sp_parity(Parity::Odd), serialport::Parity::Odd);
assert_eq!(to_sp_parity(Parity::None), serialport::Parity::None);
}
#[test]
fn flow_round_trip() {
assert_eq!(to_sp_flow(FlowControl::None), serialport::FlowControl::None);
assert_eq!(
to_sp_flow(FlowControl::Hardware),
serialport::FlowControl::Hardware
);
assert_eq!(
to_sp_flow(FlowControl::Software),
serialport::FlowControl::Software
);
}
#[cfg(unix)]
#[tokio::test]
async fn pair_returns_default_config() {
let (a, b) = SerialPortDevice::pair().expect("pty pair");
assert_eq!(a.config(), &SerialConfig::default());
assert_eq!(b.config(), &SerialConfig::default());
}
}