use std::io::Write;
use crate::frame::{DEFAULT_MAX_FRAME_SIZE, expected_frame_count, read_frames};
use crate::{PrintError, Printer, PrinterConfig, StatusQuery};
const DEFAULT_BAUD: u32 = 9600;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SerialSettings {
pub data_bits: SerialDataBits,
pub parity: SerialParity,
pub stop_bits: SerialStopBits,
pub flow_control: SerialFlowControl,
}
impl Default for SerialSettings {
fn default() -> Self {
Self {
data_bits: SerialDataBits::Eight,
parity: SerialParity::None,
stop_bits: SerialStopBits::One,
flow_control: SerialFlowControl::Software,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SerialDataBits {
Seven,
Eight,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SerialParity {
None,
Even,
Odd,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SerialStopBits {
One,
Two,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SerialFlowControl {
None,
Software,
Hardware,
}
pub struct SerialPrinter {
port: Box<dyn serialport::SerialPort>,
config: PrinterConfig,
}
impl SerialPrinter {
pub fn open(path: &str, baud: u32, config: PrinterConfig) -> Result<Self, PrintError> {
Self::open_with_settings(path, baud, SerialSettings::default(), config)
}
pub fn open_with_settings(
path: &str,
baud: u32,
settings: SerialSettings,
config: PrinterConfig,
) -> Result<Self, PrintError> {
let timeout = config.timeouts.read.max(config.timeouts.write);
let port = serialport::new(path, baud)
.data_bits(map_data_bits(settings.data_bits))
.parity(map_parity(settings.parity))
.stop_bits(map_stop_bits(settings.stop_bits))
.flow_control(map_flow_control(settings.flow_control))
.timeout(timeout)
.open()
.map_err(|e| PrintError::SerialError(e.to_string()))?;
Ok(Self { port, config })
}
pub fn open_default(path: &str, config: PrinterConfig) -> Result<Self, PrintError> {
Self::open(path, DEFAULT_BAUD, config)
}
pub fn list_ports() -> Vec<String> {
serialport::available_ports()
.unwrap_or_default()
.into_iter()
.map(|p| p.port_name)
.collect()
}
}
impl Printer for SerialPrinter {
fn send_raw(&mut self, data: &[u8]) -> Result<(), PrintError> {
if self.config.trace_io {
trace_bytes("serial tx", data);
}
self.port.write_all(data).map_err(PrintError::WriteFailed)?;
self.port.flush().map_err(PrintError::WriteFailed)?;
Ok(())
}
}
impl StatusQuery for SerialPrinter {
fn query_raw(&mut self, cmd: &[u8]) -> Result<Vec<Vec<u8>>, PrintError> {
self.send_raw(cmd)?;
let expected_frames = expected_frame_count(cmd);
let timeout = self.config.timeouts.read;
let frames = read_frames(
&mut self.port,
expected_frames,
timeout,
DEFAULT_MAX_FRAME_SIZE,
)?;
if self.config.trace_io {
for frame in &frames {
trace_bytes("serial rx", frame);
}
}
Ok(frames)
}
}
fn map_data_bits(bits: SerialDataBits) -> serialport::DataBits {
match bits {
SerialDataBits::Seven => serialport::DataBits::Seven,
SerialDataBits::Eight => serialport::DataBits::Eight,
}
}
fn map_parity(parity: SerialParity) -> serialport::Parity {
match parity {
SerialParity::None => serialport::Parity::None,
SerialParity::Even => serialport::Parity::Even,
SerialParity::Odd => serialport::Parity::Odd,
}
}
fn map_stop_bits(bits: SerialStopBits) -> serialport::StopBits {
match bits {
SerialStopBits::One => serialport::StopBits::One,
SerialStopBits::Two => serialport::StopBits::Two,
}
}
fn map_flow_control(flow: SerialFlowControl) -> serialport::FlowControl {
match flow {
SerialFlowControl::None => serialport::FlowControl::None,
SerialFlowControl::Software => serialport::FlowControl::Software,
SerialFlowControl::Hardware => serialport::FlowControl::Hardware,
}
}
fn trace_bytes(label: &str, bytes: &[u8]) {
let hex = bytes
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(" ");
let ascii = bytes
.iter()
.map(|b| {
if b.is_ascii_graphic() || *b == b' ' {
char::from(*b)
} else {
'.'
}
})
.collect::<String>();
eprintln!(
"[trace-io] {label} len={} hex=[{}] ascii='{}'",
bytes.len(),
hex,
ascii
);
}