Skip to main content

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}