Skip to main content

modbus_bridge/
client.rs

1//! [`Client`] — owns the RTU serial port and creates [`ClientSession`]s.
2//!
3//! In client mode the roles of RTU and TCP are reversed relative to [`Bridge`](crate::Bridge):
4//! the serial bus acts as the *request source* (an RTU master talks to this device) and
5//! the TCP stream connects to an *upstream Modbus TCP server*.
6
7use crate::{
8    client_builder::ClientBuilder, client_session::ClientSession, rtu::ModbusRtu, NoDelay,
9};
10use embedded_hal::digital::OutputPin;
11
12/// Modbus RTU→TCP client.
13///
14/// Owns the serial port (`S`) and RS-485 TX-enable pin (`TX`). Connect to an
15/// upstream Modbus TCP server by calling [`connect`](Client::connect) with a TCP stream.
16///
17/// The optional third parameter `D` is a delay provider for I/O timeouts.
18/// It defaults to [`NoDelay`](crate::NoDelay).
19///
20/// # Examples
21///
22/// ```rust,ignore
23/// use modbus_bridge::{Client, BridgeError, BridgeEvent};
24///
25/// let mut client = Client::builder()
26///     .rtu(uart, tx_en_pin)
27///     .build();
28///
29/// // tcp_stream connects to the upstream Modbus TCP server
30/// let mut session = client.connect(tcp_stream);
31/// loop {
32///     match session.next().await {
33///         Ok(BridgeEvent::Transaction(t)) => log::info!("modbus: {t}"),
34///         Ok(BridgeEvent::Warning(w))     => log::warn!("modbus: {w}"),
35///         Err(BridgeError::RtuClosed)     => break,  // RTU master disconnected
36///         Err(e)                          => { log::error!("{e}"); break; }
37///     }
38/// }
39/// let tcp_stream = session.into_stream();
40/// ```
41pub struct Client<S, TX, D = NoDelay> {
42    pub(crate) rtu: ModbusRtu<S, TX>,
43    pub(crate) rtu_timeout_ms: Option<u32>,
44    pub(crate) tcp_timeout_ms: Option<u32>,
45    pub(crate) delay: D,
46}
47
48impl<S, TX, D> Client<S, TX, D> {
49    /// Returns a [`ClientBuilder`](crate::ClientBuilder) for constructing a `Client`.
50    pub fn builder() -> ClientBuilder<(), (), NoDelay> {
51        ClientBuilder::new()
52    }
53
54    pub(crate) fn from_parts(
55        serial: S,
56        tx_en: TX,
57        delay: D,
58        rtu_timeout_ms: Option<u32>,
59        tcp_timeout_ms: Option<u32>,
60    ) -> Self {
61        Self {
62            rtu: ModbusRtu::new(serial, tx_en),
63            rtu_timeout_ms,
64            tcp_timeout_ms,
65            delay,
66        }
67    }
68
69    /// Consumes the client and returns the inner serial port, TX-enable pin, and delay provider.
70    pub fn into_inner(self) -> (S, TX, D) {
71        let (s, tx) = self.rtu.into_inner();
72        (s, tx, self.delay)
73    }
74}
75
76#[cfg(feature = "async")]
77impl<S, TX, D> Client<S, TX, D>
78where
79    S: embedded_io_async::Read + embedded_io_async::Write,
80    TX: OutputPin,
81{
82    /// Creates a [`ClientSession`](crate::ClientSession) connected to an upstream TCP server.
83    ///
84    /// Takes ownership of `stream` and mutably borrows the client for the lifetime
85    /// of the returned [`ClientSession`](crate::ClientSession).
86    ///
87    /// # Examples
88    ///
89    /// ```rust,ignore
90    /// let mut session = client.connect(tcp_stream);
91    /// loop {
92    ///     match session.next().await {
93    ///         Ok(event) => { /* handle event */ }
94    ///         Err(_) => break,
95    ///     }
96    /// }
97    /// let tcp_stream = session.into_stream();
98    /// ```
99    pub fn connect<TS>(&mut self, stream: TS) -> ClientSession<'_, S, TX, TS, D>
100    where
101        TS: embedded_io_async::Read + embedded_io_async::Write,
102    {
103        ClientSession::new(self, stream)
104    }
105}
106
107#[cfg(feature = "sync")]
108impl<S, TX, D> Client<S, TX, D>
109where
110    S: embedded_io::Read + embedded_io::Write,
111    TX: OutputPin,
112{
113    /// Creates a [`ClientSession`](crate::ClientSession) connected to an upstream TCP server.
114    pub fn connect<TS>(&mut self, stream: TS) -> ClientSession<'_, S, TX, TS, D>
115    where
116        TS: embedded_io::Read + embedded_io::Write,
117    {
118        ClientSession::new(self, stream)
119    }
120}