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}