modbus_rtu/master/sync.rs
1//! Blocking Modbus RTU master backed by the `serialport` crate.
2
3use crate::{Request, Response};
4
5
6/// Blocking Modbus RTU master that enforces Modbus idle timing rules between frames.
7#[derive(Debug)]
8pub struct Master {
9 /// Serial port handle used for request/response traffic.
10 port: Box<dyn serialport::SerialPort>,
11
12 /// Timestamp of the last transmitted frame, used to honor the 3.5-char gap.
13 last_tx: std::time::Instant,
14
15 /// Cached baud rate so higher-level code can inspect the active speed.
16 baud_rate: u32,
17}
18
19
20impl Master {
21 /// Builds a master configured for an RS-485 style setup (8N1, blocking I/O).
22 ///
23 /// The port timeout is pinned to the Modbus RTU silent interval (T3.5) for
24 /// the supplied baud rate so that the reader can detect frame boundaries.
25 ///
26 /// ---
27 /// # Examples
28 /// ```ignore
29 /// use modbus_rtu::Master;
30 ///
31 /// # fn demo() -> serialport::Result<()> {
32 /// let master = Master::new_rs485("/dev/ttyUSB0", 9_600)?;
33 /// assert_eq!(master.baud_rate(), 9_600);
34 /// # Ok(())
35 /// # }
36 /// ```
37 ///
38 pub fn new_rs485(path: &str, baud_rate: u32) -> serialport::Result<Self> {
39 let port = serialport::new(path, baud_rate)
40 .data_bits(serialport::DataBits::Eight)
41 .parity(serialport::Parity::None)
42 .stop_bits(serialport::StopBits::One)
43 .timeout(Self::idle_time_rs485(baud_rate))
44 .open()?;
45 Ok(Self { port, last_tx: (std::time::Instant::now() - Self::idle_time_rs485(baud_rate)), baud_rate })
46 }
47
48 /// Returns the baud rate currently configured on the serial link.
49 ///
50 /// ---
51 /// # Examples
52 /// ```ignore
53 /// use modbus_rtu::Master;
54 ///
55 /// # fn demo() -> serialport::Result<()> {
56 /// let master = Master::new_rs485("/dev/ttyUSB0", 38_400)?;
57 /// assert_eq!(master.baud_rate(), 38_400);
58 /// # Ok(())
59 /// # }
60 /// ```
61 ///
62 pub fn baud_rate(&self) -> u32 {
63 self.baud_rate
64 }
65
66 /// Updates the serial baud rate and matching Modbus idle timeout.
67 ///
68 /// ---
69 /// # Examples
70 /// ```ignore
71 /// use modbus_rtu::Master;
72 ///
73 /// # fn demo() -> Result<(), Box<dyn std::error::Error>> {
74 /// let mut master = Master::new_rs485("/dev/ttyUSB0", 9_600)?;
75 /// master.set_baudrate(19_200)?;
76 /// assert_eq!(master.baud_rate(), 19_200);
77 /// # Ok(())
78 /// # }
79 /// ```
80 ///
81 pub fn set_baudrate(&mut self, baud_rate: u32) -> serialport::Result<()> {
82 self.port.set_baud_rate(baud_rate)?;
83 self.port.set_timeout(Self::idle_time_rs485(baud_rate))?;
84 self.baud_rate = baud_rate;
85 self.last_tx = std::time::Instant::now();
86 Ok(())
87 }
88
89 /// Sends a Modbus RTU request and waits for the corresponding response.
90 ///
91 /// Broadcast requests return immediately after the frame is flushed because
92 /// the Modbus RTU spec forbids responses to slave id 0.
93 ///
94 /// ---
95 /// # Examples
96 /// ```ignore
97 /// use modbus_rtu::{Function, Master, Request};
98 ///
99 /// # fn demo() -> Result<(), Box<dyn std::error::Error>> {
100 /// let mut master = Master::new_rs485("/dev/ttyUSB0", 19_200)?;
101 /// let func = Function::ReadHoldingRegisters { starting_address: 0x0000, quantity: 2 };
102 /// let request = Request::new(0x01, &func, std::time::Duration::from_millis(200));
103 /// let response = master.send(&request)?;
104 /// assert!(response.is_success());
105 /// # Ok(())
106 /// # }
107 /// ```
108 ///
109 pub fn send(&mut self, req: &Request) -> Result<Response, crate::error::Error> {
110 while self.last_tx.elapsed() <= Self::idle_time_rs485(self.baud_rate) {
111 std::hint::spin_loop();
112 }
113 let frame = req.to_bytes().map_err(|e| crate::error::Error::Request(e))?;
114 self.port.clear(serialport::ClearBuffer::Output).map_err(|e| crate::error::Error::IO(e.into()))?;
115 self.write(&frame)?;
116 if req.is_broadcasting() {
117 return Ok(Response::Success);
118 }
119 let post_tx_idle = Self::idle_time_rs485(self.baud_rate);
120 let wait_start = std::time::Instant::now();
121 while wait_start.elapsed() <= post_tx_idle {
122 std::hint::spin_loop();
123 }
124 let mut buf: [u8; 256] = [0; 256];
125 let len = self.read(&mut buf, req.timeout(), req.function().expected_len())?;
126 if len == 0 {
127 return Err(crate::error::Error::IO(std::io::ErrorKind::TimedOut.into()));
128 }
129 Response::from_bytes(req, &buf[0..len]).map_err(|e| crate::error::Error::Response(e))
130 }
131
132 /// Writes a Modbus frame to the serial port and records the transmit instant.
133 fn write(&mut self, frame: &[u8]) -> Result<(), crate::error::Error> {
134 // println!("will write {}bytes ({:?})", frame.len(), frame);
135 self.port.write_all(frame)
136 .map_err(|e| crate::error::Error::IO(e.into()))?;
137 self.last_tx = std::time::Instant::now();
138 Ok(())
139 }
140
141 /// Reads bytes until the slave stops responding or `buf` fills up.
142 fn read(&mut self, buf: &mut [u8], timeout: core::time::Duration, expected_len: usize) -> Result<usize, crate::error::Error> {
143 let start = std::time::Instant::now();
144 let mut len: usize = 0;
145 while start.elapsed() <= timeout {
146 let n = match self.port.read(&mut buf[len..]) {
147 Ok(n) => {
148 // println!("received {} bytes: {:?}", n, &buf[len..len + n]);
149 n
150 },
151 Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => if len == 0 { continue } else {
152 if len >= 5
153 && buf[1] & 0x80 != 0 {
154 // println!("idle detected (exception length)");
155 break;
156 }
157 if len < expected_len {
158 continue;
159 }
160 // println!("idle detected");
161 break
162 },
163 Err(e) => return Err(crate::error::Error::IO(e.into())),
164 };
165 len += n;
166 if len >= buf.len() {
167 // println!("buffer full");
168 break;
169 }
170 }
171 if start.elapsed() > timeout {
172 // println!("timeout detected");
173 }
174 // println!("final: {}bytes {:?}", len, &buf[0..len]);
175 Ok(len)
176 }
177
178 /// Computes the Modbus RTU T3.5 idle time for a link running 8N1 encoding.
179 fn idle_time_rs485(baud_rate: u32) -> core::time::Duration {
180 const BITS_PER_CHAR: f64 = 10.0;
181 let seconds = 3.5 * BITS_PER_CHAR / baud_rate as f64;
182 core::time::Duration::from_secs_f64(seconds)
183 }
184}