Skip to main content

rustmod_client/
sync.rs

1use crate::{
2    ClientConfig, ClientError, ModbusClient, ReadDeviceIdentificationResponse,
3    ReportServerIdResponse, UnitId,
4};
5use rustmod_datalink::{DataLinkError, ModbusTcpTransport};
6use thiserror::Error;
7use tokio::runtime::Runtime;
8
9/// Errors that can occur when using [`SyncModbusTcpClient`].
10#[derive(Debug, Error)]
11#[non_exhaustive]
12pub enum SyncClientError {
13    /// Failed to initialise the Tokio runtime.
14    #[error("runtime init error: {0}")]
15    RuntimeInit(std::io::Error),
16    /// Transport-level error.
17    #[error("datalink error: {0}")]
18    DataLink(#[from] DataLinkError),
19    /// Error from the underlying async [`ModbusClient`].
20    #[error("client error: {0}")]
21    Client(#[from] ClientError),
22}
23
24/// Blocking Modbus TCP client for use outside an async runtime.
25///
26/// Internally creates a single-threaded Tokio runtime and delegates to
27/// [`ModbusClient`]. All methods block the calling thread until the response
28/// arrives or the timeout expires.
29pub struct SyncModbusTcpClient {
30    runtime: Runtime,
31    client: ModbusClient<ModbusTcpTransport>,
32}
33
34impl SyncModbusTcpClient {
35    /// Connect to a Modbus TCP device at `addr` (e.g. `"192.168.1.10:502"`) with default config.
36    pub fn connect(addr: &str) -> Result<Self, SyncClientError> {
37        Self::connect_with_config(addr, ClientConfig::default())
38    }
39
40    /// Connect to a Modbus TCP device with a custom [`ClientConfig`].
41    pub fn connect_with_config(addr: &str, config: ClientConfig) -> Result<Self, SyncClientError> {
42        let runtime = tokio::runtime::Builder::new_current_thread()
43            .enable_all()
44            .build()
45            .map_err(SyncClientError::RuntimeInit)?;
46        let link = runtime.block_on(ModbusTcpTransport::connect(addr))?;
47        let client = ModbusClient::with_config(link, config);
48        Ok(Self { runtime, client })
49    }
50
51    /// Return the current client configuration.
52    pub fn config(&self) -> ClientConfig {
53        self.client.config()
54    }
55
56    /// Read coils (FC01). See [`ModbusClient::read_coils`](crate::ModbusClient::read_coils).
57    pub fn read_coils(
58        &self,
59        unit_id: UnitId,
60        start: u16,
61        quantity: u16,
62    ) -> Result<Vec<bool>, SyncClientError> {
63        self.runtime
64            .block_on(self.client.read_coils(unit_id, start, quantity))
65            .map_err(SyncClientError::Client)
66    }
67
68    /// Read discrete inputs (FC02). See [`ModbusClient::read_discrete_inputs`](crate::ModbusClient::read_discrete_inputs).
69    pub fn read_discrete_inputs(
70        &self,
71        unit_id: UnitId,
72        start: u16,
73        quantity: u16,
74    ) -> Result<Vec<bool>, SyncClientError> {
75        self.runtime
76            .block_on(self.client.read_discrete_inputs(unit_id, start, quantity))
77            .map_err(SyncClientError::Client)
78    }
79
80    /// Read holding registers (FC03). See [`ModbusClient::read_holding_registers`](crate::ModbusClient::read_holding_registers).
81    pub fn read_holding_registers(
82        &self,
83        unit_id: UnitId,
84        start: u16,
85        quantity: u16,
86    ) -> Result<Vec<u16>, SyncClientError> {
87        self.runtime
88            .block_on(self.client.read_holding_registers(unit_id, start, quantity))
89            .map_err(SyncClientError::Client)
90    }
91
92    /// Read input registers (FC04). See [`ModbusClient::read_input_registers`](crate::ModbusClient::read_input_registers).
93    pub fn read_input_registers(
94        &self,
95        unit_id: UnitId,
96        start: u16,
97        quantity: u16,
98    ) -> Result<Vec<u16>, SyncClientError> {
99        self.runtime
100            .block_on(self.client.read_input_registers(unit_id, start, quantity))
101            .map_err(SyncClientError::Client)
102    }
103
104    /// Write a single coil (FC05). See [`ModbusClient::write_single_coil`](crate::ModbusClient::write_single_coil).
105    pub fn write_single_coil(
106        &self,
107        unit_id: UnitId,
108        address: u16,
109        value: bool,
110    ) -> Result<(), SyncClientError> {
111        self.runtime
112            .block_on(self.client.write_single_coil(unit_id, address, value))
113            .map_err(SyncClientError::Client)
114    }
115
116    /// Write a single register (FC06). See [`ModbusClient::write_single_register`](crate::ModbusClient::write_single_register).
117    pub fn write_single_register(
118        &self,
119        unit_id: UnitId,
120        address: u16,
121        value: u16,
122    ) -> Result<(), SyncClientError> {
123        self.runtime
124            .block_on(self.client.write_single_register(unit_id, address, value))
125            .map_err(SyncClientError::Client)
126    }
127
128    /// Mask write register (FC22). See [`ModbusClient::mask_write_register`](crate::ModbusClient::mask_write_register).
129    pub fn mask_write_register(
130        &self,
131        unit_id: UnitId,
132        address: u16,
133        and_mask: u16,
134        or_mask: u16,
135    ) -> Result<(), SyncClientError> {
136        self.runtime
137            .block_on(
138                self.client
139                    .mask_write_register(unit_id, address, and_mask, or_mask),
140            )
141            .map_err(SyncClientError::Client)
142    }
143
144    /// Write multiple coils (FC15). See [`ModbusClient::write_multiple_coils`](crate::ModbusClient::write_multiple_coils).
145    pub fn write_multiple_coils(
146        &self,
147        unit_id: UnitId,
148        start: u16,
149        values: &[bool],
150    ) -> Result<(), SyncClientError> {
151        self.runtime
152            .block_on(self.client.write_multiple_coils(unit_id, start, values))
153            .map_err(SyncClientError::Client)
154    }
155
156    /// Write multiple registers (FC16). See [`ModbusClient::write_multiple_registers`](crate::ModbusClient::write_multiple_registers).
157    pub fn write_multiple_registers(
158        &self,
159        unit_id: UnitId,
160        start: u16,
161        values: &[u16],
162    ) -> Result<(), SyncClientError> {
163        self.runtime
164            .block_on(self.client.write_multiple_registers(unit_id, start, values))
165            .map_err(SyncClientError::Client)
166    }
167
168    /// Read and write multiple registers (FC23). See [`ModbusClient::read_write_multiple_registers`](crate::ModbusClient::read_write_multiple_registers).
169    pub fn read_write_multiple_registers(
170        &self,
171        unit_id: UnitId,
172        read_start: u16,
173        read_quantity: u16,
174        write_start: u16,
175        write_values: &[u16],
176    ) -> Result<Vec<u16>, SyncClientError> {
177        self.runtime
178            .block_on(self.client.read_write_multiple_registers(
179                unit_id,
180                read_start,
181                read_quantity,
182                write_start,
183                write_values,
184            ))
185            .map_err(SyncClientError::Client)
186    }
187
188    /// Send a custom function code request. See [`ModbusClient::custom_request`](crate::ModbusClient::custom_request).
189    pub fn custom_request(
190        &self,
191        unit_id: UnitId,
192        function_code: u8,
193        payload: &[u8],
194    ) -> Result<Vec<u8>, SyncClientError> {
195        self.runtime
196            .block_on(self.client.custom_request(unit_id, function_code, payload))
197            .map_err(SyncClientError::Client)
198    }
199
200    /// Report Server ID (FC17). See [`ModbusClient::report_server_id`](crate::ModbusClient::report_server_id).
201    pub fn report_server_id(&self, unit_id: UnitId) -> Result<ReportServerIdResponse, SyncClientError> {
202        self.runtime
203            .block_on(self.client.report_server_id(unit_id))
204            .map_err(SyncClientError::Client)
205    }
206
207    /// Read Device Identification (FC43/0x0E). See [`ModbusClient::read_device_identification`](crate::ModbusClient::read_device_identification).
208    pub fn read_device_identification(
209        &self,
210        unit_id: UnitId,
211        read_device_id_code: u8,
212        object_id: u8,
213    ) -> Result<ReadDeviceIdentificationResponse, SyncClientError> {
214        self.runtime
215            .block_on(self.client.read_device_identification(
216                unit_id,
217                read_device_id_code,
218                object_id,
219            ))
220            .map_err(SyncClientError::Client)
221    }
222
223    /// Read coils as raw packed bytes (FC01). See [`ModbusClient::read_coils_raw`](crate::ModbusClient::read_coils_raw).
224    pub fn read_coils_raw(
225        &self,
226        unit_id: UnitId,
227        start: u16,
228        quantity: u16,
229    ) -> Result<(Vec<u8>, u16), SyncClientError> {
230        self.runtime
231            .block_on(self.client.read_coils_raw(unit_id, start, quantity))
232            .map_err(SyncClientError::Client)
233    }
234
235    /// Read discrete inputs as raw packed bytes (FC02). See [`ModbusClient::read_discrete_inputs_raw`](crate::ModbusClient::read_discrete_inputs_raw).
236    pub fn read_discrete_inputs_raw(
237        &self,
238        unit_id: UnitId,
239        start: u16,
240        quantity: u16,
241    ) -> Result<(Vec<u8>, u16), SyncClientError> {
242        self.runtime
243            .block_on(self.client.read_discrete_inputs_raw(unit_id, start, quantity))
244            .map_err(SyncClientError::Client)
245    }
246
247    /// Read Exception Status (FC07). See [`ModbusClient::read_exception_status`](crate::ModbusClient::read_exception_status).
248    pub fn read_exception_status(&self, unit_id: UnitId) -> Result<u8, SyncClientError> {
249        self.runtime
250            .block_on(self.client.read_exception_status(unit_id))
251            .map_err(SyncClientError::Client)
252    }
253
254    /// Diagnostics (FC08). See [`ModbusClient::diagnostics`](crate::ModbusClient::diagnostics).
255    pub fn diagnostics(
256        &self,
257        unit_id: UnitId,
258        sub_function: u16,
259        data: u16,
260    ) -> Result<(u16, u16), SyncClientError> {
261        self.runtime
262            .block_on(self.client.diagnostics(unit_id, sub_function, data))
263            .map_err(SyncClientError::Client)
264    }
265
266    /// Read FIFO Queue (FC24). See [`ModbusClient::read_fifo_queue`](crate::ModbusClient::read_fifo_queue).
267    pub fn read_fifo_queue(
268        &self,
269        unit_id: UnitId,
270        address: u16,
271    ) -> Result<Vec<u16>, SyncClientError> {
272        self.runtime
273            .block_on(self.client.read_fifo_queue(unit_id, address))
274            .map_err(SyncClientError::Client)
275    }
276}