use super::Port;
#[cfg(any(test, doc, feature = "mock"))]
use crate::backend::Mock;
use crate::{
ascii::command::MaxPacketSize,
backend::{Backend, Serial},
error::AsciiError,
};
use serialport as sp;
use std::{
io,
net::{TcpStream, ToSocketAddrs},
time::Duration,
};
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3);
const DEFAULT_GENERATE_ID: bool = true;
const DEFAULT_GENERATE_CHECKSUM: bool = true;
macro_rules! define_option {
(
$(#[$attr:meta])*
pub struct $name:ident {
$(
$member:ident: $type:ty,
)*
...
}
) => {
$(#[$attr])*
#[derive(Debug)]
pub struct $name {
$(
$member: $type,
)*
/// Whether commands should include message IDs or not.
generate_id: bool,
generate_checksum: bool,
max_packet_size: MaxPacketSize,
}
$(#[$attr])*
impl Default for $name {
fn default() -> Self {
Self::new()
}
}
};
}
macro_rules! impl_builder_methods {
($($token:ident),+) => {
$(
impl_builder_methods!{@ $token}
)+
};
(@ timeout) => {
pub fn timeout(&mut self, duration: Option<Duration>) -> &mut Self {
self.timeout = duration;
self
}
};
(@ baud_rate) => {
pub fn baud_rate(&mut self, baud_rate: u32) -> &mut Self {
self.baud_rate = baud_rate;
self
}
};
(@ common) => {
pub fn checksums(&mut self, checksum: bool) -> &mut Self {
self.generate_checksum = checksum;
self
}
pub fn message_ids(&mut self, id: bool) -> &mut Self {
self.generate_id = id;
self
}
pub fn max_packet_size(&mut self, max_packet_size: MaxPacketSize) -> &mut Self {
self.max_packet_size = max_packet_size;
self
}
fn with_backend<'a, B: Backend>(&self, backend: B) -> Port<'a, B> {
Port::from_backend(
backend,
self.generate_id,
self.generate_checksum,
self.max_packet_size,
)
}
};
}
define_option! {
pub struct OpenSerialOptions {
timeout: Option<Duration>,
baud_rate: u32,
...
}
}
impl OpenSerialOptions {
pub const DEFAULT_BAUD_RATE: u32 = 115_200;
pub fn new() -> Self {
Self {
generate_id: DEFAULT_GENERATE_ID,
generate_checksum: DEFAULT_GENERATE_CHECKSUM,
max_packet_size: MaxPacketSize::default(),
timeout: Some(DEFAULT_TIMEOUT),
baud_rate: Self::DEFAULT_BAUD_RATE,
}
}
fn open_serial_port(&self, path: &str) -> Result<Serial, AsciiError> {
sp::new(path, Self::DEFAULT_BAUD_RATE)
.data_bits(sp::DataBits::Eight)
.parity(sp::Parity::None)
.flow_control(sp::FlowControl::None)
.stop_bits(sp::StopBits::One)
.timeout(self.timeout.unwrap_or(Duration::MAX))
.baud_rate(self.baud_rate)
.open_native()
.map(Serial)
.map_err(Into::into)
}
pub fn open<'a>(&self, path: &str) -> Result<Port<'a, Serial>, AsciiError> {
Ok(self.with_backend(self.open_serial_port(path)?))
}
pub fn open_dyn<'a>(
&self,
path: &str,
) -> Result<Port<'a, Box<dyn Backend + Send>>, AsciiError> {
Ok(self.with_backend(Box::new(self.open_serial_port(path)?)))
}
impl_builder_methods! {common, baud_rate, timeout}
}
define_option! {
pub struct OpenTcpOptions {
timeout: Option<Duration>,
...
}
}
impl OpenTcpOptions {
pub fn new() -> Self {
Self {
generate_id: DEFAULT_GENERATE_ID,
generate_checksum: DEFAULT_GENERATE_CHECKSUM,
max_packet_size: MaxPacketSize::default(),
timeout: Some(DEFAULT_TIMEOUT),
}
}
fn open_tcp_stream<A: ToSocketAddrs>(&self, address: A) -> io::Result<TcpStream> {
let stream = TcpStream::connect(address)?;
stream.set_read_timeout(self.timeout)?;
Ok(stream)
}
pub fn open<'a, A: ToSocketAddrs>(&self, address: A) -> io::Result<Port<'a, TcpStream>> {
Ok(self.with_backend(self.open_tcp_stream(address)?))
}
pub fn open_dyn<'a, A: ToSocketAddrs>(
&self,
address: A,
) -> io::Result<Port<'a, Box<dyn Backend + Send>>> {
Ok(self.with_backend(Box::new(self.open_tcp_stream(address)?)))
}
impl_builder_methods! {common, timeout}
}
define_option! {
pub struct OpenGeneralOptions {
...
}
}
impl OpenGeneralOptions {
pub fn new() -> Self {
Self {
generate_id: DEFAULT_GENERATE_ID,
generate_checksum: DEFAULT_GENERATE_CHECKSUM,
max_packet_size: MaxPacketSize::default(),
}
}
pub fn open<'a, B: Backend>(&self, backend: B) -> Port<'a, B> {
self.with_backend(backend)
}
impl_builder_methods! {common}
}
define_option! {
#[cfg(any(test, doc, feature = "mock"))]
#[cfg_attr(all(doc, feature = "doc_cfg"), doc(cfg(feature = "mock")))]
pub struct OpenMockOptions {
...
}
}
#[cfg(any(test, doc, feature = "mock"))]
#[cfg_attr(all(doc, feature = "doc_cfg"), doc(cfg(feature = "mock")))]
impl OpenMockOptions {
pub fn new() -> Self {
Self {
generate_id: false,
generate_checksum: false,
max_packet_size: MaxPacketSize::default(),
}
}
pub fn open<'a>(&self) -> Port<'a, Mock> {
self.with_backend(Mock::new())
}
impl_builder_methods! {common}
}