1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use std::borrow::Cow;
use std::cell::RefCell;


pub mod discover;
mod blocking;
mod r#async;

mod methods;
pub use methods::*;


#[cfg(not(feature = "tokio-serial"))]
type Port = Box<dyn serialport::SerialPort>;
#[cfg(feature = "tokio-serial")]
type Port = Box<tokio_serial::SerialStream>;

pub struct Interface {
	info: serialport::SerialPortInfo,
	port: Option<RefCell<Port>>,
}


impl std::fmt::Display for Interface {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		use serialport::SerialPort;

		let port_name = &self.info.port_name;
		let name = self.port.as_ref().and_then(|p| {
			                             p.try_borrow()
			                              .ok()
			                              .and_then(|p| p.name().filter(|s| s != port_name))
		                             });

		write!(f, "serial:{}", name.as_deref().unwrap_or(port_name))
	}
}

impl std::fmt::Debug for Interface {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		f.debug_struct("Interface")
		 .field("name", &self.info.port_name)
		 .field("opened", &self.port.is_some())
		 .finish()
	}
}


impl Interface {
	#[cfg_attr(feature = "tracing", tracing::instrument)]
	pub fn new(info: serialport::SerialPortInfo) -> Self { Self { info, port: None } }

	#[cfg_attr(feature = "tracing", tracing::instrument)]
	pub fn new_with(port: Port, name: Option<String>) -> Self {
		use serialport::{SerialPort, SerialPortType, SerialPortInfo};

		let name = port.name().or(name).map(Cow::from).unwrap_or_default();
		let info = SerialPortInfo { port_name: name.to_string(),
		                            port_type: SerialPortType::Unknown };

		let mut result = Self::new(info);
		result.set_port(port);
		result
	}

	pub fn info(&self) -> &serialport::SerialPortInfo { &self.info }
	pub fn is_open(&self) -> bool { self.port.is_some() }

	#[cfg_attr(feature = "tracing", tracing::instrument)]
	pub fn set_port(&mut self, port: Port) { self.port = Some(RefCell::new(port)); }

	#[cfg_attr(feature = "tracing", tracing::instrument)]
	pub fn open(&mut self) -> Result<(), serialport::Error> {
		if self.port.is_some() {
			Ok(())
		} else {
			let port = open(&self.info.port_name).map(RefCell::new)?;
			self.port = Some(port);
			Ok(())
		}
	}


	#[cfg_attr(feature = "tracing", tracing::instrument)]
	pub fn close(&mut self) { self.port.take(); }
}


#[cfg_attr(feature = "tracing", tracing::instrument)]
pub fn open<'a, S>(port_name: S) -> Result<Port, serialport::Error>
	where S: Into<std::borrow::Cow<'a, str>> + std::fmt::Debug {
	trace!("opening port {port_name:?}");
	let builder = port_builder(port_name);

	let port;
	#[cfg(not(feature = "tokio-serial"))]
	{
		port = builder.open()?;
	}
	#[cfg(feature = "tokio-serial")]
	{
		use tokio_serial::SerialPortBuilderExt;
		port = builder.open_native_async().map(Box::new)?;
	}

	{
		use serialport::SerialPort;
		let name = port.as_ref().name();
		let name = name.as_deref().unwrap_or("n/a");
		trace!("opened port: {name}");
	}
	Ok(port)
}

fn port_builder<'a>(port_name: impl Into<std::borrow::Cow<'a, str>>) -> serialport::SerialPortBuilder {
	serialport::new(port_name, 115200).data_bits(serialport::DataBits::Eight)
}


/* NOTE: This can be safely sent between thread, but not inner port,
			but that's okay because it's boxen under `RefCell`.
			Probably should be pinned, but not sure yet.
*/
unsafe impl Send for Interface {}
unsafe impl Sync for Interface {}