use anyhow::Result;
use chrono::Utc;
use serialport::SerialPort;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::{Mutex, mpsc};
#[derive(Debug, Clone)]
pub enum SerialData {
Received(String),
}
pub struct SerialReader {
port: Arc<Mutex<Box<dyn SerialPort + Send>>>,
running: Arc<AtomicBool>,
sender: mpsc::UnboundedSender<SerialData>,
hex_mode: bool,
log_ts: bool,
rx_log_writer: Option<Arc<std::sync::Mutex<std::io::BufWriter<std::fs::File>>>>,
buffer: Vec<u8>, }
impl SerialReader {
pub fn new(
port: Arc<Mutex<Box<dyn SerialPort + Send>>>,
running: Arc<AtomicBool>,
sender: mpsc::UnboundedSender<SerialData>,
hex_mode: bool,
log_ts: bool,
rx_log_writer: Option<Arc<std::sync::Mutex<std::io::BufWriter<std::fs::File>>>>,
) -> Self {
Self {
port,
running,
sender,
hex_mode,
log_ts,
rx_log_writer,
buffer: vec![0u8; 4096], }
}
pub async fn run(mut self) {
while self.running.load(Ordering::SeqCst) {
let n = {
let mut guard = self.port.lock().await;
match guard.read(&mut self.buffer) {
Ok(n) => n,
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => 0,
Err(_) => break,
}
};
if n > 0 {
let bytes = self.buffer[..n].to_vec();
self.process_received_data(&bytes).await;
} else {
tokio::task::yield_now().await;
}
}
}
async fn process_received_data(&mut self, bytes: &[u8]) {
let display_text = if self.hex_mode {
self.format_hex_data(bytes)
} else {
self.format_text_data(bytes)
};
let _ = self.sender.send(SerialData::Received(display_text));
self.write_to_log(bytes).await;
}
fn format_hex_data(&mut self, bytes: &[u8]) -> String {
let capacity = if self.log_ts { 32 } else { 0 } + bytes.len() * 3; let mut hex_str = String::with_capacity(capacity);
if self.log_ts {
hex_str.push('[');
hex_str.push_str(&Utc::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string());
hex_str.push_str("] ");
}
for (i, b) in bytes.iter().enumerate() {
if i > 0 {
hex_str.push(' ');
}
hex_str.push_str(&format!("{:02X}", b));
}
hex_str
}
fn format_text_data(&mut self, bytes: &[u8]) -> String {
let capacity = if self.log_ts { 32 } else { 0 } + bytes.len();
let mut text = String::with_capacity(capacity);
if self.log_ts {
text.push('[');
text.push_str(&Utc::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string());
text.push_str("] ");
}
text.push_str(&String::from_utf8_lossy(bytes));
text
}
async fn write_to_log(&mut self, bytes: &[u8]) {
if let Some(w) = &self.rx_log_writer
&& let Ok(mut lw) = w.lock()
{
use std::io::Write;
if self.log_ts {
let _ = write!(lw, "[{}] ", Utc::now().format("%Y-%m-%d %H:%M:%S%.3f"));
}
if self.hex_mode {
for (i, b) in bytes.iter().enumerate() {
let separator = if i + 1 == bytes.len() { "" } else { " " };
let _ = write!(lw, "{:02X}{}", b, separator);
}
let _ = writeln!(lw);
} else {
let _ = lw.write_all(bytes);
}
let _ = lw.flush();
}
}
}
pub async fn write_bytes_async(
port: &Arc<Mutex<Box<dyn SerialPort + Send>>>,
bytes: &[u8],
) -> Result<()> {
let mut guard = port.lock().await;
guard.write_all(bytes)?;
guard.flush()?;
Ok(())
}