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}