mbus_serial/management/
std_serial.rs1use std::io::{self, Read, Write};
2use std::time::Duration;
3
4use heapless::Vec;
5use mbus_core::data_unit::common::MAX_ADU_FRAME_LEN;
6use mbus_core::transport::{
7 BaudRate, DataBits as ConfigDataBits, ModbusConfig, Parity, SerialMode, Transport,
8 TransportError, TransportType,
9};
10use serialport::{ClearBuffer, DataBits as SerialPortDataBits, FlowControl, SerialPort, StopBits};
11
12#[cfg(feature = "logging")]
13macro_rules! serial_log_error {
14 ($($arg:tt)*) => {
15 log::error!($($arg)*)
16 };
17}
18
19#[cfg(not(feature = "logging"))]
20macro_rules! serial_log_error {
21 ($($arg:tt)*) => {{
22 let _ = core::format_args!($($arg)*);
23 }};
24}
25
26#[cfg(feature = "logging")]
27macro_rules! serial_log_warn {
28 ($($arg:tt)*) => {
29 log::warn!($($arg)*)
30 };
31}
32
33#[cfg(not(feature = "logging"))]
34macro_rules! serial_log_warn {
35 ($($arg:tt)*) => {{
36 let _ = core::format_args!($($arg)*);
37 }};
38}
39
40#[derive(Debug)]
43pub struct StdSerialTransport {
44 port: Option<Box<dyn SerialPort>>,
45 mode: SerialMode, timeout: Duration,
48 baud_rate: u32,
50}
51
52impl StdSerialTransport {
53 pub fn new(mode: SerialMode) -> Self {
55 Self {
56 port: None,
57 mode,
58 timeout: Duration::from_secs(1), baud_rate: 9600, }
61 }
62
63 pub fn available_ports() -> Result<std::vec::Vec<serialport::SerialPortInfo>, serialport::Error>
66 {
67 serialport::available_ports()
68 }
69
70 fn map_io_error(err: io::Error) -> TransportError {
74 match err.kind() {
75 io::ErrorKind::TimedOut => TransportError::Timeout,
76 io::ErrorKind::BrokenPipe
77 | io::ErrorKind::ConnectionReset
78 | io::ErrorKind::UnexpectedEof => TransportError::ConnectionClosed,
79 _ => TransportError::IoError,
80 }
81 }
82}
83
84impl Transport for StdSerialTransport {
85 type Error = TransportError;
86
87 fn connect(&mut self, config: &ModbusConfig) -> Result<(), Self::Error> {
96 let serial_config = match config {
97 ModbusConfig::Serial(c) => c,
98 _ => return Err(TransportError::InvalidConfiguration),
99 };
100
101 if serial_config.mode != self.mode {
103 return Err(TransportError::InvalidConfiguration);
104 }
105
106 self.baud_rate = match serial_config.baud_rate {
107 BaudRate::Baud9600 => 9600,
108 BaudRate::Baud19200 => 19200,
109 BaudRate::Custom(rate) => rate,
110 };
111
112 let parity = match serial_config.parity {
113 Parity::None => serialport::Parity::None,
114 Parity::Even => serialport::Parity::Even,
115 Parity::Odd => serialport::Parity::Odd,
116 };
117
118 let data_bits = match serial_config.data_bits {
119 ConfigDataBits::Five => SerialPortDataBits::Five,
120 ConfigDataBits::Six => SerialPortDataBits::Six,
121 ConfigDataBits::Seven => SerialPortDataBits::Seven,
122 ConfigDataBits::Eight => SerialPortDataBits::Eight,
123 };
124
125 let stop_bits = match serial_config.stop_bits {
127 1 => StopBits::One,
128 2 => StopBits::Two,
129 _ => return Err(TransportError::InvalidConfiguration),
130 };
131
132 self.timeout = Duration::from_millis(serial_config.response_timeout_ms as u64);
133
134 let builder = serialport::new(serial_config.port_path.as_str(), self.baud_rate)
136 .parity(parity)
137 .data_bits(data_bits)
138 .stop_bits(stop_bits) .flow_control(FlowControl::None)
140 .timeout(self.timeout);
141
142 match builder.open() {
144 Ok(port) => {
145 if let Err(e) = port.clear(ClearBuffer::All) {
146 serial_log_warn!("Failed to clear serial buffers on connect: {}", e);
147 }
148 self.port = Some(port);
149 Ok(())
150 }
151 Err(e) => {
152 serial_log_error!(
153 "Failed to open serial port '{}': {}",
154 serial_config.port_path.as_str(),
155 e
156 );
157 #[cfg(windows)]
159 {
160 let error_string = e.to_string().to_lowercase();
161 if error_string.contains("access is denied") {
162 serial_log_error!(
163 "Hint: 'Access is denied' on Windows usually means the port is already in use by another application."
164 );
165 }
166 if error_string.contains("the system cannot find the file specified") {
167 serial_log_error!(
168 "Hint: 'The system cannot find the file specified' on Windows means the port does not exist. Check available ports."
169 );
170 }
171 }
172 if e.to_string().contains("Not a typewriter") {
173 serial_log_error!(
174 "Hint: This error often occurs on macOS when using a pseudo-terminal (pty) created by tools like socat."
175 );
176 serial_log_error!(
177 "PTYs may not support setting serial parameters like baud rate. Consider using a physical serial port or a different virtual setup."
178 );
179 }
180 Err(TransportError::ConnectionFailed)
181 }
182 }
183 }
184
185 fn disconnect(&mut self) -> Result<(), Self::Error> {
189 self.port = None;
191 Ok(())
192 }
193
194 fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
202 let port = self.port.as_mut().ok_or(TransportError::ConnectionClosed)?;
203
204 if let Err(e) = port.clear(ClearBuffer::All) {
209 serial_log_warn!("Failed to clear serial buffers before send: {}", e);
210 }
212
213 port.write_all(adu).map_err(|e| {
214 serial_log_error!("Serial write_all failed: {}", e);
215 Self::map_io_error(e)
216 })?;
217
218 match port.flush() {
219 Ok(_) => Ok(()),
220 Err(e) => {
221 #[cfg(windows)]
224 if let Some(1) = e.raw_os_error() {
225 return Ok(());
227 }
228 serial_log_error!("Serial flush failed: {}", e);
229 Err(Self::map_io_error(e))
230 }
231 }
232 }
233
234 fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
243 let port = self.port.as_mut().ok_or(TransportError::ConnectionClosed)?;
244
245 let bytes_to_read = port.bytes_to_read().map_err(|e| {
247 serial_log_error!("Failed to check available bytes: {}", e);
248 TransportError::IoError
249 })?;
250
251 let mut buffer = Vec::new();
252
253 if bytes_to_read == 0 {
254 return Err(TransportError::Timeout);
255 }
256
257 let limit = std::cmp::min(bytes_to_read as usize, buffer.capacity());
259
260 let mut temp_buf = [0u8; MAX_ADU_FRAME_LEN];
262 let read_count = port.read(&mut temp_buf[..limit]).map_err(|e| {
263 if e.kind() == io::ErrorKind::WouldBlock {
264 return TransportError::Timeout;
265 }
266 Self::map_io_error(e)
267 })?;
268
269 if read_count == 0 {
270 return Err(TransportError::Timeout);
271 }
272
273 if buffer.extend_from_slice(&temp_buf[..read_count]).is_err() {
275 return Err(TransportError::IoError); }
277
278 Ok(buffer)
279 }
280
281 fn is_connected(&self) -> bool {
283 self.port.is_some()
284 }
285
286 fn transport_type(&self) -> TransportType {
288 let mode = self.mode; TransportType::StdSerial(mode)
290 }
291}