mbus_async/client/command.rs
1//! Client command types for the async task channel.
2//!
3//! This module defines the two enums used to communicate with the background
4//! `tokio` task that owns the transport:
5//!
6//! - [`ClientRequest`] — the user-facing operation parameters, one variant per
7//! Modbus function code, carrying only the values the caller supplies. The
8//! task assigns the transaction id internally.
9//! - `TaskCommand` — the full envelope sent over the `mpsc` channel, which
10//! wraps a [`ClientRequest`] together with the oneshot reply sender, or
11//! represents a connection request.
12//!
13//! Shutdown is signalled implicitly by dropping the `mpsc::Sender` end of the
14//! channel; no explicit `Shutdown` variant is needed.
15
16use tokio::sync::oneshot;
17
18use mbus_core::errors::MbusError;
19use mbus_core::transport::UnitIdOrSlaveAddr;
20
21#[cfg(feature = "diagnostics")]
22use mbus_core::function_codes::public::{DiagnosticSubFunction, EncapsulatedInterfaceType};
23#[cfg(feature = "coils")]
24use mbus_core::models::coil::Coils;
25#[cfg(feature = "diagnostics")]
26use mbus_core::models::diagnostic::{ObjectId, ReadDeviceIdCode};
27#[cfg(feature = "file-record")]
28use mbus_core::models::file_record::SubRequest;
29
30use crate::client::response::ClientResponse;
31
32/// Reply channel used to deliver a single response back to the async caller.
33pub(crate) type ResponseSender = oneshot::Sender<Result<ClientResponse, MbusError>>;
34
35// ─── ClientRequest ────────────────────────────────────────────────────────────
36
37/// User-supplied parameters for a single Modbus request.
38///
39/// Each variant corresponds to one function code (or a closely related group).
40/// The task assigns the transaction id; callers never supply it here.
41#[allow(clippy::large_enum_variant)]
42#[derive(Debug)]
43pub enum ClientRequest {
44 // ── Coils (FC 01 / 05 / 0F) ───────────────────────────────────────────
45 /// Read multiple coils (FC 01).
46 #[cfg(feature = "coils")]
47 ReadMultipleCoils {
48 /// Unit or slave address of the Modbus device.
49 unit: UnitIdOrSlaveAddr,
50 /// Starting address.
51 address: u16,
52 /// Number of coils to read.
53 quantity: u16,
54 },
55 /// Write a single coil (FC 05).
56 #[cfg(feature = "coils")]
57 WriteSingleCoil {
58 /// Unit or slave address of the Modbus device.
59 unit: UnitIdOrSlaveAddr,
60 /// Address to write.
61 address: u16,
62 /// Value to write.
63 value: bool,
64 },
65 /// Write multiple coils (FC 15 / 0F).
66 #[cfg(feature = "coils")]
67 WriteMultipleCoils {
68 /// Unit or slave address of the Modbus device.
69 unit: UnitIdOrSlaveAddr,
70 /// Starting address.
71 address: u16,
72 /// Coils values to write.
73 coils: Coils,
74 },
75
76 // ── Registers (FC 03 / 04 / 06 / 10 / 16 / 17) ────────────────────────
77 /// Read holding registers (FC 03).
78 #[cfg(feature = "holding-registers")]
79 ReadHoldingRegisters {
80 /// Unit or slave address of the Modbus device.
81 unit: UnitIdOrSlaveAddr,
82 /// Starting address.
83 address: u16,
84 /// Number of registers to read.
85 quantity: u16,
86 },
87 /// Read input registers (FC 04).
88 #[cfg(feature = "input-registers")]
89 ReadInputRegisters {
90 /// Unit or slave address of the Modbus device.
91 unit: UnitIdOrSlaveAddr,
92 /// Starting address.
93 address: u16,
94 /// Number of registers to read.
95 quantity: u16,
96 },
97 /// Write a single register (FC 06).
98 #[cfg(feature = "holding-registers")]
99 WriteSingleRegister {
100 /// Unit or slave address of the Modbus device.
101 unit: UnitIdOrSlaveAddr,
102 /// Address to write.
103 address: u16,
104 /// Value to write.
105 value: u16,
106 },
107 /// Write multiple registers (FC 16 / 10).
108 #[cfg(feature = "holding-registers")]
109 WriteMultipleRegisters {
110 /// Unit or slave address of the Modbus device.
111 unit: UnitIdOrSlaveAddr,
112 /// Starting address.
113 address: u16,
114 /// Register values to write.
115 values: heapless::Vec<u16, { mbus_core::data_unit::common::MAX_PDU_DATA_LEN }>,
116 },
117 /// Read and write multiple registers (FC 23 / 17).
118 #[cfg(feature = "holding-registers")]
119 ReadWriteMultipleRegisters {
120 /// Unit or slave address of the Modbus device.
121 unit: UnitIdOrSlaveAddr,
122 /// Address to read from.
123 read_address: u16,
124 /// Number of registers to read.
125 read_quantity: u16,
126 /// Address to write to.
127 write_address: u16,
128 /// Register values to write.
129 write_values: heapless::Vec<u16, { mbus_core::data_unit::common::MAX_PDU_DATA_LEN }>,
130 },
131 /// Mask write register (FC 22 / 16).
132 #[cfg(feature = "holding-registers")]
133 MaskWriteRegister {
134 /// Unit or slave address of the Modbus device.
135 unit: UnitIdOrSlaveAddr,
136 /// Address to write.
137 address: u16,
138 /// AND mask to apply.
139 and_mask: u16,
140 /// OR mask to apply.
141 or_mask: u16,
142 },
143
144 // ── Discrete inputs (FC 02) ────────────────────────────────────────────
145 /// Read discrete inputs (FC 02).
146 #[cfg(feature = "discrete-inputs")]
147 ReadDiscreteInputs {
148 /// Unit or slave address of the Modbus device.
149 unit: UnitIdOrSlaveAddr,
150 /// Starting address.
151 address: u16,
152 /// Number of inputs to read.
153 quantity: u16,
154 },
155
156 // ── FIFO queue (FC 18) ─────────────────────────────────────────────────
157 /// Read FIFO queue (FC 18).
158 #[cfg(feature = "fifo")]
159 ReadFifoQueue {
160 /// Unit or slave address of the Modbus device.
161 unit: UnitIdOrSlaveAddr,
162 /// FIFO queue address.
163 address: u16,
164 },
165
166 // ── File record (FC 14 / 15) ───────────────────────────────────────────
167 /// Read file record (FC 14).
168 #[cfg(feature = "file-record")]
169 ReadFileRecord {
170 /// Unit or slave address of the Modbus device.
171 unit: UnitIdOrSlaveAddr,
172 /// Sub request details.
173 sub_request: SubRequest,
174 },
175 /// Write file record (FC 15).
176 #[cfg(feature = "file-record")]
177 WriteFileRecord {
178 /// Unit or slave address of the Modbus device.
179 unit: UnitIdOrSlaveAddr,
180 /// Sub request details.
181 sub_request: SubRequest,
182 },
183
184 // ── Diagnostics (FC 07 / 08 / 0B / 0C / 11 / 2B) ─────────────────────
185 /// Read device identification (FC 43/14).
186 #[cfg(feature = "diagnostics")]
187 ReadDeviceIdentification {
188 /// Unit or slave address of the Modbus device.
189 unit: UnitIdOrSlaveAddr,
190 /// Read Device ID Code.
191 read_device_id_code: ReadDeviceIdCode,
192 /// Object ID to request.
193 object_id: ObjectId,
194 },
195 /// Encapsulated interface transport (FC 43).
196 #[cfg(feature = "diagnostics")]
197 EncapsulatedInterfaceTransport {
198 /// Unit or slave address of the Modbus device.
199 unit: UnitIdOrSlaveAddr,
200 /// MEI Type.
201 mei_type: EncapsulatedInterfaceType,
202 /// MEI request data.
203 data: heapless::Vec<u8, { mbus_core::data_unit::common::MAX_PDU_DATA_LEN }>,
204 },
205 /// Read exception status (FC 07).
206 #[cfg(feature = "diagnostics")]
207 ReadExceptionStatus {
208 /// Unit or slave address of the Modbus device.
209 unit: UnitIdOrSlaveAddr,
210 },
211 /// Diagnostics sub-function execution (FC 08).
212 #[cfg(feature = "diagnostics")]
213 Diagnostics {
214 /// Unit or slave address of the Modbus device.
215 unit: UnitIdOrSlaveAddr,
216 /// Diagnostic sub-function code.
217 sub_function: DiagnosticSubFunction,
218 /// Sub-function data.
219 data: heapless::Vec<u16, { mbus_core::data_unit::common::MAX_PDU_DATA_LEN }>,
220 },
221 /// Get communication event counter (FC 11 / 0B).
222 #[cfg(feature = "diagnostics")]
223 GetCommEventCounter {
224 /// Unit or slave address of the Modbus device.
225 unit: UnitIdOrSlaveAddr,
226 },
227 /// Get communication event log (FC 12 / 0C).
228 #[cfg(feature = "diagnostics")]
229 GetCommEventLog {
230 /// Unit or slave address of the Modbus device.
231 unit: UnitIdOrSlaveAddr,
232 },
233 /// Report server ID (FC 17 / 11).
234 #[cfg(feature = "diagnostics")]
235 ReportServerId {
236 /// Unit or slave address of the Modbus device.
237 unit: UnitIdOrSlaveAddr,
238 },
239}
240
241impl ClientRequest {
242 /// Returns the target unit id or slave address for this request.
243 #[allow(dead_code)]
244 pub(crate) fn unit(&self) -> UnitIdOrSlaveAddr {
245 match self {
246 #[cfg(feature = "coils")]
247 Self::ReadMultipleCoils { unit, .. }
248 | Self::WriteSingleCoil { unit, .. }
249 | Self::WriteMultipleCoils { unit, .. } => *unit,
250
251 #[cfg(feature = "holding-registers")]
252 Self::ReadHoldingRegisters { unit, .. }
253 | Self::WriteSingleRegister { unit, .. }
254 | Self::WriteMultipleRegisters { unit, .. }
255 | Self::ReadWriteMultipleRegisters { unit, .. }
256 | Self::MaskWriteRegister { unit, .. } => *unit,
257
258 #[cfg(feature = "input-registers")]
259 Self::ReadInputRegisters { unit, .. } => *unit,
260
261 #[cfg(feature = "discrete-inputs")]
262 Self::ReadDiscreteInputs { unit, .. } => *unit,
263
264 #[cfg(feature = "fifo")]
265 Self::ReadFifoQueue { unit, .. } => *unit,
266
267 #[cfg(feature = "file-record")]
268 Self::ReadFileRecord { unit, .. } | Self::WriteFileRecord { unit, .. } => *unit,
269
270 #[cfg(feature = "diagnostics")]
271 Self::ReadDeviceIdentification { unit, .. }
272 | Self::EncapsulatedInterfaceTransport { unit, .. }
273 | Self::ReadExceptionStatus { unit, .. }
274 | Self::Diagnostics { unit, .. }
275 | Self::GetCommEventCounter { unit, .. }
276 | Self::GetCommEventLog { unit, .. }
277 | Self::ReportServerId { unit, .. } => *unit,
278
279 #[allow(unreachable_patterns)]
280 _ => unreachable!(),
281 }
282 }
283}
284
285// ─── TaskCommand ──────────────────────────────────────────────────────────────
286
287/// Command envelope sent from [`AsyncClientCore`] to the background task.
288///
289/// [`AsyncClientCore`]: crate::client::client_core::AsyncClientCore
290// `ClientRequest` is 18 KiB on the stack; the enum is transient (sent once over
291// the mpsc channel and immediately consumed), so boxing is unnecessary overhead.
292#[allow(clippy::large_enum_variant)]
293pub(crate) enum TaskCommand {
294 /// Establish the transport connection.
295 Connect {
296 resp_tx: oneshot::Sender<Result<(), MbusError>>,
297 },
298 /// Execute a Modbus function-code request.
299 Request {
300 params: ClientRequest,
301 resp_tx: ResponseSender,
302 },
303 /// Drain all in-flight and queued requests with `ConnectionClosed` and
304 /// close the transport. Issued automatically after a per-request timeout
305 /// so the pipeline self-heals without caller intervention.
306 Disconnect,
307}