Skip to main content

modbus_bridge/
bridge.rs

1//! The [`Bridge`] type — owns the RTU serial port and creates [`Connection`]s.
2
3use crate::{builder::BridgeBuilder, connection::Connection, rtu::ModbusRtu, NoDelay};
4use embedded_hal::digital::OutputPin;
5
6/// Modbus RTU/TCP bridge.
7///
8/// Owns the serial port (`S`) and RS-485 TX-enable pin (`TX`). TCP connections
9/// are supplied one at a time via [`accept`](Bridge::accept). Only one
10/// [`Connection`](crate::Connection) can be active at a time — the bridge is
11/// mutably borrowed for the connection's lifetime.
12///
13/// The optional third parameter `D` is a delay provider for I/O timeouts.
14/// It defaults to [`NoDelay`](crate::NoDelay); configure it via
15/// [`BridgeBuilder::delay`](crate::BridgeBuilder::delay).
16///
17/// # Examples
18///
19/// ```rust,ignore
20/// use modbus_bridge::{Bridge, BridgeError, BridgeEvent};
21///
22/// let mut bridge = Bridge::builder()
23///     .rtu(uart, tx_en_pin)
24///     .build();
25///
26/// loop {
27///     let socket = tcp_stack.listen(502).await.unwrap();
28///     let mut conn = bridge.accept(socket);
29///     loop {
30///         match conn.next().await {
31///             Ok(BridgeEvent::Transaction(t)) => log::info!("modbus: {t}"),
32///             Ok(BridgeEvent::Warning(w))     => log::warn!("modbus: {w}"),
33///             Err(BridgeError::TcpClosed)     => break,
34///             Err(e)                          => { log::error!("{e}"); break; }
35///         }
36///     }
37///     conn.into_stream().close();
38/// }
39/// ```
40pub struct Bridge<S, TX, D = NoDelay> {
41    pub(crate) rtu: ModbusRtu<S, TX>,
42    pub(crate) rtu_timeout_ms: Option<u32>,
43    pub(crate) tcp_timeout_ms: Option<u32>,
44    pub(crate) delay: D,
45}
46
47impl<S, TX, D> Bridge<S, TX, D> {
48    /// Returns a [`BridgeBuilder`](crate::BridgeBuilder) for constructing a `Bridge`.
49    ///
50    /// # Examples
51    ///
52    /// ```rust,ignore
53    /// use modbus_bridge::Bridge;
54    ///
55    /// let bridge = Bridge::builder()
56    ///     .rtu(uart, tx_en)
57    ///     .build();
58    /// ```
59    pub fn builder() -> BridgeBuilder<(), (), NoDelay> {
60        BridgeBuilder::new()
61    }
62
63    pub(crate) fn from_parts(
64        serial: S,
65        tx_en: TX,
66        delay: D,
67        rtu_timeout_ms: Option<u32>,
68        tcp_timeout_ms: Option<u32>,
69    ) -> Self {
70        Self {
71            rtu: ModbusRtu::new(serial, tx_en),
72            rtu_timeout_ms,
73            tcp_timeout_ms,
74            delay,
75        }
76    }
77
78    /// Consumes the bridge and returns the inner serial port, TX-enable pin, and delay provider.
79    ///
80    /// # Examples
81    ///
82    /// ```rust,ignore
83    /// let (uart, tx_en, _delay) = bridge.into_inner();
84    /// ```
85    pub fn into_inner(self) -> (S, TX, D) {
86        let (s, tx) = self.rtu.into_inner();
87        (s, tx, self.delay)
88    }
89}
90
91#[cfg(feature = "async")]
92impl<S, TX, D> Bridge<S, TX, D>
93where
94    S: embedded_io_async::Read + embedded_io_async::Write,
95    TX: OutputPin,
96{
97    /// Creates a [`Connection`](crate::Connection) for an incoming TCP client.
98    ///
99    /// Takes ownership of `stream` and mutably borrows the bridge for the
100    /// lifetime of the returned [`Connection`](crate::Connection).
101    ///
102    /// # Examples
103    ///
104    /// ```rust,ignore
105    /// let mut conn = bridge.accept(socket);
106    /// loop {
107    ///     match conn.next().await {
108    ///         Ok(event) => { /* handle event */ }
109    ///         Err(_)    => break,
110    ///     }
111    /// }
112    /// let socket = conn.into_stream();
113    /// socket.close();
114    /// ```
115    pub fn accept<TS>(&mut self, stream: TS) -> Connection<'_, S, TX, TS, D>
116    where
117        TS: embedded_io_async::Read + embedded_io_async::Write,
118    {
119        Connection::new(self, stream)
120    }
121}
122
123#[cfg(feature = "sync")]
124impl<S, TX, D> Bridge<S, TX, D>
125where
126    S: embedded_io::Read + embedded_io::Write,
127    TX: OutputPin,
128{
129    /// Creates a [`Connection`](crate::Connection) for an incoming TCP client.
130    pub fn accept<TS>(&mut self, stream: TS) -> Connection<'_, S, TX, TS, D>
131    where
132        TS: embedded_io::Read + embedded_io::Write,
133    {
134        Connection::new(self, stream)
135    }
136}