use anyhow::Result;
use std::io::{Read, Write};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
pub mod baud {
pub const B9600: u32 = 9600;
pub const B19200: u32 = 19200;
pub const B38400: u32 = 38400;
pub const B57600: u32 = 57600;
pub const B115200: u32 = 115200;
pub const B230400: u32 = 230400;
pub const B460800: u32 = 460800;
pub const B921600: u32 = 921600;
}
#[derive(Clone, Debug)]
pub struct SerialConfig {
pub port: String,
pub baud_rate: u32,
pub data_bits: DataBits,
pub parity: Parity,
pub stop_bits: StopBits,
}
impl SerialConfig {
pub fn new(port: &str, baud_rate: u32) -> Self {
Self {
port: port.to_string(),
baud_rate,
data_bits: DataBits::Eight,
parity: Parity::None,
stop_bits: StopBits::One,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum DataBits {
Five,
Six,
Seven,
#[default]
Eight,
}
impl From<DataBits> for serialport::DataBits {
fn from(db: DataBits) -> Self {
match db {
DataBits::Five => serialport::DataBits::Five,
DataBits::Six => serialport::DataBits::Six,
DataBits::Seven => serialport::DataBits::Seven,
DataBits::Eight => serialport::DataBits::Eight,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum Parity {
#[default]
None,
Odd,
Even,
}
impl From<Parity> for serialport::Parity {
fn from(p: Parity) -> Self {
match p {
Parity::None => serialport::Parity::None,
Parity::Odd => serialport::Parity::Odd,
Parity::Even => serialport::Parity::Even,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum StopBits {
#[default]
One,
Two,
}
impl From<StopBits> for serialport::StopBits {
fn from(sb: StopBits) -> Self {
match sb {
StopBits::One => serialport::StopBits::One,
StopBits::Two => serialport::StopBits::Two,
}
}
}
pub struct SerialPort {
port: Box<dyn serialport::SerialPort>,
}
impl SerialPort {
pub fn open(config: &SerialConfig) -> Result<Self> {
let port = serialport::new(&config.port, config.baud_rate)
.data_bits(config.data_bits.into())
.parity(config.parity.into())
.stop_bits(config.stop_bits.into())
.timeout(Duration::from_millis(100)) .open()?;
Ok(Self { port })
}
pub fn open_simple(port: &str, baud_rate: u32) -> Result<Self> {
let config = SerialConfig::new(port, baud_rate);
Self::open(&config)
}
pub fn split(self) -> (SerialReader, SerialWriter) {
let reader_port = self.port.try_clone().expect("Failed to clone serial port");
let writer_port = self.port;
let (read_tx, read_rx) = mpsc::channel::<ReadResult>();
let (read_cmd_tx, read_cmd_rx) = mpsc::channel::<ReadCommand>();
let (write_tx, write_rx) = mpsc::channel::<WriteCommand>();
let (write_result_tx, write_result_rx) = mpsc::channel::<WriteResult>();
thread::spawn(move || {
let mut port = reader_port;
while let Ok(cmd) = read_cmd_rx.recv() {
match cmd {
ReadCommand::Read(size) => {
let mut buf = vec![0u8; size];
match port.read(&mut buf) {
Ok(n) => {
buf.truncate(n);
if read_tx.send(ReadResult::Data(buf)).is_err() {
break;
}
}
Err(e) if e.kind() == std::io::ErrorKind::TimedOut => {
if read_tx.send(ReadResult::Data(vec![])).is_err() {
break;
}
}
Err(e) => {
if read_tx.send(ReadResult::Error(e.to_string())).is_err() {
break;
}
}
}
}
ReadCommand::Stop => break,
}
}
});
thread::spawn(move || {
let mut port = writer_port;
while let Ok(cmd) = write_rx.recv() {
match cmd {
WriteCommand::Write(data) => {
let result = port.write_all(&data).and_then(|_| port.flush());
let _ = write_result_tx.send(match result {
Ok(()) => WriteResult::Ok,
Err(e) => WriteResult::Error(e.to_string()),
});
}
WriteCommand::Stop => break,
}
}
});
(
SerialReader {
read_rx,
read_cmd_tx,
},
SerialWriter {
write_tx,
write_result_rx,
},
)
}
}
enum ReadCommand {
Read(usize),
Stop,
}
enum ReadResult {
Data(Vec<u8>),
Error(String),
}
enum WriteCommand {
Write(Vec<u8>),
Stop,
}
enum WriteResult {
Ok,
Error(String),
}
pub struct SerialReader {
read_rx: mpsc::Receiver<ReadResult>,
read_cmd_tx: mpsc::Sender<ReadCommand>,
}
impl SerialReader {
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.read_cmd_tx
.send(ReadCommand::Read(buf.len()))
.map_err(|_| anyhow::anyhow!("Serial reader thread died"))?;
let rx = unsafe {
std::ptr::read(&self.read_rx)
};
let (result, rx) = tokio::task::spawn_blocking(move || {
let result = rx.recv();
(result, rx)
})
.await?;
unsafe {
std::ptr::write(&mut self.read_rx, rx);
}
match result {
Ok(ReadResult::Data(data)) => {
let n = std::cmp::min(data.len(), buf.len());
buf[..n].copy_from_slice(&data[..n]);
Ok(n)
}
Ok(ReadResult::Error(e)) => Err(anyhow::anyhow!("Serial read error: {}", e)),
Err(_) => Err(anyhow::anyhow!("Serial reader thread died")),
}
}
}
impl Drop for SerialReader {
fn drop(&mut self) {
let _ = self.read_cmd_tx.send(ReadCommand::Stop);
}
}
pub struct SerialWriter {
write_tx: mpsc::Sender<WriteCommand>,
write_result_rx: mpsc::Receiver<WriteResult>,
}
impl SerialWriter {
pub async fn write(&mut self, data: &[u8]) -> Result<usize> {
self.write_all(data).await?;
Ok(data.len())
}
pub async fn write_all(&mut self, data: &[u8]) -> Result<()> {
self.write_tx
.send(WriteCommand::Write(data.to_vec()))
.map_err(|_| anyhow::anyhow!("Serial writer thread died"))?;
let rx = unsafe { std::ptr::read(&self.write_result_rx) };
let (result, rx) = tokio::task::spawn_blocking(move || {
let result = rx.recv();
(result, rx)
})
.await?;
unsafe {
std::ptr::write(&mut self.write_result_rx, rx);
}
match result {
Ok(WriteResult::Ok) => Ok(()),
Ok(WriteResult::Error(e)) => Err(anyhow::anyhow!("Serial write error: {}", e)),
Err(_) => Err(anyhow::anyhow!("Serial writer thread died")),
}
}
pub async fn write_str(&mut self, data: &str) -> Result<()> {
self.write_all(data.as_bytes()).await
}
pub async fn flush(&mut self) -> Result<()> {
Ok(())
}
}
impl Drop for SerialWriter {
fn drop(&mut self) {
let _ = self.write_tx.send(WriteCommand::Stop);
}
}
pub fn list_ports() -> Result<Vec<SerialPortInfo>> {
let ports = serialport::available_ports()?;
Ok(ports
.into_iter()
.map(|p| SerialPortInfo {
name: p.port_name,
port_type: match p.port_type {
serialport::SerialPortType::UsbPort(info) => PortType::Usb {
vid: info.vid,
pid: info.pid,
manufacturer: info.manufacturer,
product: info.product,
},
serialport::SerialPortType::PciPort => PortType::Pci,
serialport::SerialPortType::BluetoothPort => PortType::Bluetooth,
serialport::SerialPortType::Unknown => PortType::Unknown,
},
})
.collect())
}
#[derive(Clone, Debug)]
pub struct SerialPortInfo {
pub name: String,
pub port_type: PortType,
}
#[derive(Clone, Debug)]
pub enum PortType {
Usb {
vid: u16,
pid: u16,
manufacturer: Option<String>,
product: Option<String>,
},
Pci,
Bluetooth,
Unknown,
}