Skip to main content

mbus_async/runtime/
network_client.rs

1//! Async Modbus TCP client.
2//!
3//! [`AsyncTcpClient`] is a thin wrapper around [`AsyncClientCore`] that adds
4//! TCP-specific constructors.  All Modbus request methods are inherited
5//! transparently through the [`std::ops::Deref`] implementation that resolves
6//! to `AsyncClientCore`.
7
8use std::ops::Deref;
9use super::*;
10
11/// Async Modbus TCP client facade.
12///
13/// All Modbus request methods (`read_holding_registers`, `write_single_coil`,
14/// etc.) are available directly on this type via [`Deref`] to
15/// [`AsyncClientCore`].
16///
17/// The constant generic parameter `N` is the compile-time pipeline depth
18/// forwarded to `ClientServices<_, _, N>` (default `9`).
19pub struct AsyncTcpClient<const N: usize = 9> {
20	core: AsyncClientCore,
21}
22
23impl<const N: usize> Deref for AsyncTcpClient<N> {
24	type Target = AsyncClientCore;
25
26	fn deref(&self) -> &Self::Target {
27		&self.core
28	}
29}
30
31// ── Default-pipeline constructors (N = 9) ───────────────────────────────────
32
33impl AsyncTcpClient<9> {
34	/// Creates an async TCP client connected to `host`:`port`.
35	///
36	/// Uses the default pipeline depth of 9 and a 20 ms polling interval.
37	#[cfg(feature = "tcp")]
38	pub fn connect(host: &str, port: u16) -> Result<Self, AsyncError> {
39		Self::connect_with_pipeline(host, port)
40	}
41
42	/// Creates an async TCP client connected to `host`:`port` with a custom
43	/// `poll_interval`.
44	///
45	/// Uses the default pipeline depth of 9.
46	#[cfg(feature = "tcp")]
47	pub fn connect_with_poll_interval(
48		host: &str,
49		port: u16,
50		poll_interval: Duration,
51	) -> Result<Self, AsyncError> {
52		Self::connect_with_pipeline_and_poll_interval(host, port, poll_interval)
53	}
54}
55
56// ── Configurable-pipeline constructors ───────────────────────────────────────
57
58impl<const N: usize> AsyncTcpClient<N> {
59	/// Creates an async TCP client with compile-time pipeline depth `N`.
60	///
61	/// Uses a 20 ms polling interval.
62	#[cfg(feature = "tcp")]
63	pub fn connect_with_pipeline(host: &str, port: u16) -> Result<Self, AsyncError> {
64		let transport = StdTcpTransport::new();
65		let config = ModbusConfig::Tcp(ModbusTcpConfig::new(host, port)?);
66		Self::from_transport_config(transport, config, Duration::from_millis(20))
67	}
68
69	/// Creates an async TCP client with compile-time pipeline depth `N` and a
70	/// custom `poll_interval`.
71	#[cfg(feature = "tcp")]
72	pub fn connect_with_pipeline_and_poll_interval(
73		host: &str,
74		port: u16,
75		poll_interval: Duration,
76	) -> Result<Self, AsyncError> {
77		let transport = StdTcpTransport::new();
78		let config = ModbusConfig::Tcp(ModbusTcpConfig::new(host, port)?);
79		Self::from_transport_config(transport, config, poll_interval)
80	}
81
82	/// Internal constructor: wires `transport` + `config` into a
83	/// `ClientServices` instance, spawns the worker thread, and wraps the
84	/// resulting channel in an [`AsyncClientCore`].
85	#[cfg(feature = "tcp")]
86	fn from_transport_config(
87		transport: StdTcpTransport,
88		config: ModbusConfig,
89		poll_interval: Duration,
90	) -> Result<Self, AsyncError> {
91		let pending = Arc::new(Mutex::new(HashMap::new()));
92		let app = AsyncApp {
93			pending: pending.clone(),
94		};
95
96		let client = ClientServices::<_, _, N>::new(transport, app, config)?;
97		let (sender, receiver) = mpsc::channel();
98
99		thread::spawn(move || run_worker(client, pending, receiver, poll_interval));
100
101		Ok(Self {
102			core: AsyncClientCore::new(sender),
103		})
104	}
105}