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
126
127
128
129
130
131
use std::borrow::Cow;
use std::cell::RefCell;

use crate::error::Error;


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()
		               .map(|p| {
			               p.try_borrow()
			                .ok()
			                .map(|p| p.name().filter(|s| s != port_name))
			                .flatten()
		               })
		               .flatten();

		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<(), 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: Into<std::borrow::Cow<'a, str>>>(port_name: S) -> Result<Port, Error>
	where S: 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 {}