Skip to main content

mbus_client/app/
app_trait.rs

1//! Application Layer Traits
2//!
3//! This module defines the core traits used to bridge the Modbus protocol stack with
4//! user-defined application logic. It follows a callback-based (observer) pattern
5//! where the stack notifies the application of successful responses or failures.
6//!
7//! Each trait corresponds to a functional group of Modbus services (Coils, Registers, etc.).
8//!
9//! ## Callback Contract (applies to all traits in this file)
10//!
11//! - Callbacks are dispatched from `ClientServices::poll()`. No callback is invoked unless
12//!   the application actively calls `poll()`.
13//! - A successful callback means the response was fully parsed and validated against the
14//!   queued request context (transaction id, unit/slave address, and operation metadata).
15//! - For a single request, either:
16//!   - one success callback is invoked from the corresponding response trait, or
17//!   - one failure callback is invoked via [`RequestErrorNotifier::request_failed`].
18//! - After either callback path runs, the request is removed from the internal queue.
19//! - Callback implementations should remain lightweight and non-blocking. If heavy work is
20//!   needed (database writes, UI updates, IPC), enqueue that work into your own task queue.
21//! - `txn_id` is always the original id supplied by the caller, including Serial modes where
22//!   transaction ids are not transmitted on the wire.
23
24use mbus_core::{
25    errors::MbusError,
26    function_codes::public::{DiagnosticSubFunction, EncapsulatedInterfaceType},
27    transport::UnitIdOrSlaveAddr,
28};
29
30#[cfg(feature = "coils")]
31use crate::services::coil::Coils;
32#[cfg(feature = "diagnostics")]
33use crate::services::diagnostic::DeviceIdentificationResponse;
34#[cfg(feature = "discrete-inputs")]
35use crate::services::discrete_input::DiscreteInputs;
36#[cfg(feature = "fifo")]
37use crate::services::fifo_queue::FifoQueue;
38#[cfg(feature = "file-record")]
39use crate::services::file_record::SubRequestParams;
40#[cfg(feature = "registers")]
41use crate::services::register::Registers;
42
43/// Trait for receiving notifications about failed Modbus requests.
44///
45/// This is used to handle timeouts, connection issues, or Modbus exception responses
46/// at the application level, allowing the implementor to gracefully recover or alert the user.
47pub trait RequestErrorNotifier {
48    /// Called by the client stack whenever a previously queued request cannot be completed.
49    ///
50    /// The `error` parameter identifies the exact failure cause. The following variants are
51    /// delivered by the stack's internal `poll()` and `handle_timeouts()` logic:
52    ///
53    /// - **`MbusError::ModbusException(code)`** — The remote device replied with a Modbus
54    ///   exception frame (`function code 0x80 + FC`). The server understood the request but
55    ///   refused to execute it (e.g. illegal data address, illegal function). Delivered
56    ///   immediately inside the `poll()` call that received the exception response, before
57    ///   any retry logic runs.
58    ///
59    /// - **`MbusError::NoRetriesLeft`** — The response timeout expired and every configured
60    ///   retry attempt was exhausted. `handle_timeouts()` waits `response_timeout_ms`
61    ///   milliseconds after each send, schedules each retry according to the configured
62    ///   `BackoffStrategy` and `JitterStrategy`, and fires this error only after the last
63    ///   retry attempt has itself timed out without a response. The request is permanently
64    ///   removed from the queue.
65    ///
66    /// - **`MbusError::SendFailed`** — A scheduled retry was due (its backoff timestamp was
67    ///   reached inside `handle_timeouts()`), but the call to `transport.send()` returned an
68    ///   error (e.g. the TCP connection or serial port was lost between the original send and
69    ///   the retry). The request is dropped immediately; remaining retries in the budget are
70    ///   not consumed.
71    ///
72    /// # Notes
73    /// - Each call corresponds to exactly one transaction. After this call the request is
74    ///   permanently removed from the internal expected-response queue and will not be retried
75    ///   again. No further callbacks will be issued for the same `txn_id`.
76    /// - The `txn_id` is always the value supplied when the request was originally enqueued,
77    ///   even for Serial transports that do not transmit a transaction ID on the wire.
78    ///
79    /// # Parameters
80    /// - `txn_id`: Transaction ID of the original request.
81    /// - `unit_id_slave_addr`: The target Modbus unit ID (TCP) or slave address (Serial).
82    /// - `error`: The specific [`MbusError`] variant describing the failure (see above).
83    fn request_failed(
84        &mut self,
85        txn_id: u16,
86        unit_id_slave_addr: UnitIdOrSlaveAddr,
87        error: MbusError,
88    );
89}
90
91#[cfg(feature = "traffic")]
92/// Direction of raw Modbus frame traffic observed by the client stack.
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum TrafficDirection {
95    /// Outgoing request ADU sent by the client.
96    Tx,
97    /// Incoming response ADU received by the client.
98    Rx,
99}
100
101#[cfg(feature = "traffic")]
102/// Optional raw-frame traffic notifications emitted by the client stack.
103///
104/// This trait is opt-in and enabled only with the `traffic` feature. A
105/// blanket no-op implementation is provided for all app types so applications
106/// are never forced to implement it.
107pub trait TrafficNotifier {
108    /// Called when a request frame is sent.
109    fn on_tx_frame(&mut self, _txn_id: u16, _unit_id_slave_addr: UnitIdOrSlaveAddr, _frame: &[u8]) {
110    }
111
112    /// Called when a response frame is received.
113    fn on_rx_frame(&mut self, _txn_id: u16, _unit_id_slave_addr: UnitIdOrSlaveAddr, _frame: &[u8]) {
114    }
115
116    /// Called when sending a request frame failed.
117    fn on_tx_error(
118        &mut self,
119        _txn_id: u16,
120        _unit_id_slave_addr: UnitIdOrSlaveAddr,
121        _error: MbusError,
122        _frame: &[u8],
123    ) {
124    }
125
126    /// Called when processing/receiving a response frame failed.
127    fn on_rx_error(
128        &mut self,
129        _txn_id: u16,
130        _unit_id_slave_addr: UnitIdOrSlaveAddr,
131        _error: MbusError,
132        _frame: &[u8],
133    ) {
134    }
135}
136
137/// Trait defining the expected response handling for coil-related Modbus operations.
138///
139/// Implementors of this trait to deliver the responses to the application layer,
140/// allowing application developers to process the coil data and update their application state accordingly.
141///
142/// ## When Each Callback Is Fired
143/// - `read_coils_response`: after a successful FC 0x01 response for a multi-coil read.
144/// - `read_single_coil_response`: convenience callback when quantity was 1.
145/// - `write_single_coil_response`: after a successful FC 0x05 echo/ack response.
146/// - `write_multiple_coils_response`: after a successful FC 0x0F response containing
147///   start address and quantity written by the server.
148///
149/// ## Data Semantics
150/// - Address values are Modbus data-model addresses exactly as acknowledged by the server.
151/// - Boolean coil values follow Modbus conventions: `true` = ON (`0xFF00` in FC 0x05 request),
152///   `false` = OFF (`0x0000`).
153#[cfg(feature = "coils")]
154pub trait CoilResponse {
155    /// Handles a Read Coils response by invoking the appropriate application callback with the coil states.
156    ///
157    /// # Parameters
158    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
159    ///   does not natively use transaction IDs, the stack preserves the ID provided in
160    ///   the request and returns it here to allow for asynchronous tracking.
161    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
162    ///   - `unit_id`: if transport is tcp
163    ///   - `slave_addr`: if transport is serial
164    /// - `coils`: A wrapper containing the bit-packed boolean statuses of the requested coils.
165    fn read_coils_response(
166        &mut self,
167        txn_id: u16,
168        unit_id_slave_addr: UnitIdOrSlaveAddr,
169        coils: &Coils,
170    );
171
172    /// Handles a Read Single Coil response.
173    ///
174    /// # Parameters
175    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
176    ///   does not natively use transaction IDs, the stack preserves the ID provided in
177    ///   the request and returns it here to allow for asynchronous tracking.
178    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
179    ///   - `unit_id`: if transport is tcp
180    ///   - `slave_addr`: if transport is serial
181    /// - `address`: The exact address of the single coil that was read.
182    /// - `value`: The boolean state of the coil (`true` = ON, `false` = OFF).
183    fn read_single_coil_response(
184        &mut self,
185        txn_id: u16,
186        unit_id_slave_addr: UnitIdOrSlaveAddr,
187        address: u16,
188        value: bool,
189    );
190
191    /// Handles a Write Single Coil response, confirming the state change.
192    ///
193    /// # Parameters
194    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
195    ///   does not natively use transaction IDs, the stack preserves the ID provided in
196    ///   the request and returns it here to allow for asynchronous tracking.
197    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
198    ///   - `unit_id`: if transport is tcp
199    ///   - `slave_addr`: if transport is serial
200    /// - `address`: The address of the coil that was successfully written.
201    /// - `value`: The boolean state applied to the coil (`true` = ON, `false` = OFF).
202    fn write_single_coil_response(
203        &mut self,
204        txn_id: u16,
205        unit_id_slave_addr: UnitIdOrSlaveAddr,
206        address: u16,
207        value: bool,
208    );
209
210    /// Handles a Write Multiple Coils response, confirming the bulk state change.
211    ///
212    /// # Parameters
213    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
214    ///   does not natively use transaction IDs, the stack preserves the ID provided in
215    ///   the request and returns it here to allow for asynchronous tracking.
216    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
217    ///   - `unit_id`: if transport is tcp
218    ///   - `slave_addr`: if transport is serial
219    /// - `address`: The starting address where the bulk write began.
220    /// - `quantity`: The total number of consecutive coils updated.
221    fn write_multiple_coils_response(
222        &mut self,
223        txn_id: u16,
224        unit_id_slave_addr: UnitIdOrSlaveAddr,
225        address: u16,
226        quantity: u16,
227    );
228}
229
230/// Trait defining the expected response handling for FIFO Queue Modbus operations.
231///
232/// ## When Callback Is Fired
233/// - `read_fifo_queue_response` is invoked after a successful FC 0x18 response.
234///
235/// ## Data Semantics
236/// - `fifo_queue` contains values in server-returned order.
237/// - Quantity in the payload may vary between calls depending on device state.
238///
239/// ## Implementation Guidance
240///   non-blocking because it runs in the `poll()` execution path.
241#[cfg(feature = "fifo")]
242pub trait FifoQueueResponse {
243    /// Handles a Read FIFO Queue response.
244    ///
245    /// # Parameters
246    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
247    ///   does not natively use transaction IDs, the stack preserves the ID provided in
248    ///   the request and returns it here to allow for asynchronous tracking.
249    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
250    ///   - `unit_id`: if transport is tcp
251    ///   - `slave_addr`: if transport is serial
252    /// - `fifo_queue`: A `FifoQueue` struct containing the values pulled from the queue.
253    fn read_fifo_queue_response(
254        &mut self,
255        txn_id: u16,
256        unit_id_slave_addr: UnitIdOrSlaveAddr,
257        fifo_queue: &FifoQueue,
258    );
259}
260
261/// Trait defining the expected response handling for File Record Modbus operations.
262///
263/// ## When Each Callback Is Fired
264/// - `read_file_record_response`: after successful FC 0x14 response parsing.
265/// - `write_file_record_response`: after successful FC 0x15 acknowledgement.
266///
267/// ## Data Semantics
268/// - For read responses, each `SubRequestParams` entry reflects one returned record chunk.
269/// - Per Modbus spec, the response does not echo `file_number` or `record_number`; those
270///   fields are therefore reported as `0` in callback data and should not be used as identity.
271#[cfg(feature = "file-record")]
272pub trait FileRecordResponse {
273    /// Handles a Read File Record response.
274    ///
275    /// # Parameters
276    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
277    ///   does not natively use transaction IDs, the stack preserves the ID provided in
278    ///   the request and returns it here to allow for asynchronous tracking.
279    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
280    ///   - `unit_id`: if transport is tcp
281    ///   - `slave_addr`: if transport is serial
282    /// - `data`: A slice containing the sub-request responses. Note that `file_number` and `record_number`
283    ///
284    /// are not returned by the server in the response PDU and will be set to 0 in the parameters.
285    fn read_file_record_response(
286        &mut self,
287        txn_id: u16,
288        unit_id_slave_addr: UnitIdOrSlaveAddr,
289        data: &[SubRequestParams],
290    );
291
292    /// Handles a Write File Record response, confirming the write was successful.
293    ///
294    /// # Parameters
295    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
296    ///   does not natively use transaction IDs, the stack preserves the ID provided in
297    ///   the request and returns it here to allow for asynchronous tracking.
298    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
299    ///   - `unit_id`: if transport is tcp
300    ///   - `slave_addr`: if transport is serial
301    fn write_file_record_response(&mut self, txn_id: u16, unit_id_slave_addr: UnitIdOrSlaveAddr);
302}
303
304/// Defines callbacks for handling responses to Modbus register-related requests.
305///
306/// Implementors of this trait can process the data received from a Modbus server
307/// and update their application state accordingly. Each method corresponds to a
308/// specific Modbus register operation response.
309///
310/// ## Callback Mapping
311/// - FC 0x03: `read_multiple_holding_registers_response`, `read_single_holding_register_response`
312/// - FC 0x04: `read_multiple_input_registers_response`, `read_single_input_register_response`
313/// - FC 0x06: `write_single_register_response`
314/// - FC 0x10: `write_multiple_registers_response`
315/// - FC 0x16: `mask_write_register_response`
316/// - FC 0x17: `read_write_multiple_registers_response`
317///
318/// ## Data Semantics
319/// - Register values are 16-bit words (`u16`) already decoded from Modbus big-endian byte pairs.
320/// - Address and quantity values are echoed/validated values corresponding to the original request.
321#[cfg(feature = "registers")]
322pub trait RegisterResponse {
323    /// Handles a response for a `Read Input Registers` (FC 0x04) request.
324    ///
325    /// # Parameters
326    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
327    ///   does not natively use transaction IDs, the stack preserves the ID provided in
328    ///   the request and returns it here to allow for asynchronous tracking.
329    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
330    ///   - `unit_id`: if transport is tcp
331    ///   - `slave_addr`: if transport is serial
332    /// - `registers`: A `Registers` struct containing the values of the read input registers.
333    fn read_multiple_input_registers_response(
334        &mut self,
335        txn_id: u16,
336        unit_id_slave_addr: UnitIdOrSlaveAddr,
337        registers: &Registers,
338    );
339
340    /// Handles a response for a `Read Single Input Register` (FC 0x04) request.
341    ///
342    /// # Parameters
343    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
344    ///   does not natively use transaction IDs, the stack preserves the ID provided in
345    ///   the request and returns it here to allow for asynchronous tracking.
346    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
347    ///   - `unit_id`: if transport is tcp
348    ///   - `slave_addr`: if transport is serial
349    /// - `address`: The address of the register that was read.
350    /// - `value`: The value of the read register.
351    fn read_single_input_register_response(
352        &mut self,
353        txn_id: u16,
354        unit_id_slave_addr: UnitIdOrSlaveAddr,
355        address: u16,
356        value: u16,
357    );
358
359    /// Handles a response for a `Read Holding Registers` (FC 0x03) request.
360    ///
361    /// # Parameters
362    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
363    ///   does not natively use transaction IDs, the stack preserves the ID provided in
364    ///   the request and returns it here to allow for asynchronous tracking.
365    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
366    ///   - `unit_id`: if transport is tcp
367    ///   - `slave_addr`: if transport is serial
368    /// - `registers`: A `Registers` struct containing the values of the read holding registers.
369    fn read_multiple_holding_registers_response(
370        &mut self,
371        txn_id: u16,
372        unit_id_slave_addr: UnitIdOrSlaveAddr,
373        registers: &Registers,
374    );
375
376    /// Handles a response for a `Write Single Register` (FC 0x06) request, confirming a successful write.
377    ///
378    /// # Parameters
379    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
380    ///   does not natively use transaction IDs, the stack preserves the ID provided in
381    ///   the request and returns it here to allow for asynchronous tracking.
382    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
383    ///   - `unit_id`: if transport is tcp
384    ///   - `slave_addr`: if transport is serial
385    /// - `address`: The address of the register that was written.
386    /// - `value`: The value that was written to the register.
387    fn write_single_register_response(
388        &mut self,
389        txn_id: u16,
390        unit_id_slave_addr: UnitIdOrSlaveAddr,
391        address: u16,
392        value: u16,
393    );
394
395    /// Handles a response for a `Write Multiple Registers` (FC 0x10) request, confirming a successful write.
396    ///
397    /// # Parameters
398    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
399    ///   does not natively use transaction IDs, the stack preserves the ID provided in
400    ///   the request and returns it here to allow for asynchronous tracking.
401    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
402    ///   - `unit_id`: if transport is tcp
403    ///   - `slave_addr`: if transport is serial
404    /// - `starting_address`: The starting address of the registers that were written.
405    /// - `quantity`: The number of registers that were written.
406    fn write_multiple_registers_response(
407        &mut self,
408        txn_id: u16,
409        unit_id_slave_addr: UnitIdOrSlaveAddr,
410        starting_address: u16,
411        quantity: u16,
412    );
413
414    /// Handles a response for a `Read/Write Multiple Registers` (FC 0x17) request.
415    ///
416    /// # Parameters
417    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
418    ///   does not natively use transaction IDs, the stack preserves the ID provided in
419    ///   the request and returns it here to allow for asynchronous tracking.
420    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
421    ///   - `unit_id`: if transport is tcp
422    ///   - `slave_addr`: if transport is serial
423    /// - `registers`: A `Registers` struct containing the values of the registers that were read.
424    fn read_write_multiple_registers_response(
425        &mut self,
426        txn_id: u16,
427        unit_id_slave_addr: UnitIdOrSlaveAddr,
428        registers: &Registers,
429    );
430
431    /// Handles a response for a single register read request.
432    ///
433    /// This is a convenience callback for when only one register is requested.
434    ///
435    /// # Parameters
436    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
437    ///   does not natively use transaction IDs, the stack preserves the ID provided in
438    ///   the request and returns it here to allow for asynchronous tracking.
439    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
440    ///   - `unit_id`: if transport is tcp
441    ///   - `slave_addr`: if transport is serial
442    /// - `address`: The address of the register that was read.
443    /// - `value`: The value of the read register.
444    fn read_single_register_response(
445        &mut self,
446        txn_id: u16,
447        unit_id_slave_addr: UnitIdOrSlaveAddr,
448        address: u16,
449        value: u16,
450    );
451
452    /// Handles a response for a single holding register write request.
453    ///
454    /// # Parameters
455    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
456    ///   does not natively use transaction IDs, the stack preserves the ID provided in
457    ///   the request and returns it here to allow for asynchronous tracking.
458    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
459    ///   - `unit_id`: if transport is tcp
460    ///   - `slave_addr`: if transport is serial
461    /// - `address`: The address of the register that was written.
462    /// - `value`: The value that was written to the register.
463    fn read_single_holding_register_response(
464        &mut self,
465        txn_id: u16,
466        unit_id_slave_addr: UnitIdOrSlaveAddr,
467        address: u16,
468        value: u16,
469    );
470
471    /// Handles a response for a `Mask Write Register` (FC 0x16) request, confirming a successful operation.
472    ///
473    /// # Parameters
474    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
475    ///   does not natively use transaction IDs, the stack preserves the ID provided in
476    ///   the request and returns it here to allow for asynchronous tracking.
477    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
478    ///   - `unit_id`: if transport is tcp
479    ///   - `slave_addr`: if transport is serial
480    fn mask_write_register_response(&mut self, txn_id: u16, unit_id_slave_addr: UnitIdOrSlaveAddr);
481}
482
483/// Defines callbacks for handling responses to Modbus discrete input-related requests.
484///
485/// Implementors of this trait can process the data received from a Modbus server
486/// and update their application state accordingly.
487///
488/// ## When Each Callback Is Fired
489/// - `read_multiple_discrete_inputs_response`: after successful FC 0x02 with quantity > 1.
490/// - `read_single_discrete_input_response`: convenience callback when quantity was 1.
491///
492/// ## Data Semantics
493/// - `DiscreteInputs` stores bit-packed values; use helper methods on the type instead of
494///   manually decoding bit offsets in application code.
495#[cfg(feature = "discrete-inputs")]
496pub trait DiscreteInputResponse {
497    /// Handles a response for a `Read Discrete Inputs` (FC 0x02) request.
498    ///
499    /// # Parameters
500    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
501    ///   does not natively use transaction IDs, the stack preserves the ID provided in
502    ///   the request and returns it here to allow for asynchronous tracking.
503    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
504    ///   - `unit_id`: if transport is tcp
505    ///   - `slave_addr`: if transport is serial
506    /// - `discrete_inputs`: A `DiscreteInputs` struct containing the states of the read inputs.
507    fn read_multiple_discrete_inputs_response(
508        &mut self,
509        txn_id: u16,
510        unit_id_slave_addr: UnitIdOrSlaveAddr,
511        discrete_inputs: &DiscreteInputs,
512    );
513
514    /// Handles a response for a single discrete input read request.
515    ///
516    /// # Parameters
517    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
518    ///   does not natively use transaction IDs, the stack preserves the ID provided in
519    ///   the request and returns it here to allow for asynchronous tracking.
520    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
521    ///   - `unit_id`: if transport is tcp
522    ///   - `slave_addr`: if transport is serial
523    /// - `address`: The address of the input that was read.
524    /// - `value`: The boolean state of the read input.
525    fn read_single_discrete_input_response(
526        &mut self,
527        txn_id: u16,
528        unit_id_slave_addr: UnitIdOrSlaveAddr,
529        address: u16,
530        value: bool,
531    );
532}
533
534/// Trait for handling Diagnostics-family responses.
535///
536/// ## Callback Mapping
537/// - FC 0x2B / MEI 0x0E: `read_device_identification_response`
538/// - FC 0x2B / other MEI: `encapsulated_interface_transport_response`
539/// - FC 0x07: `read_exception_status_response`
540/// - FC 0x08: `diagnostics_response`
541/// - FC 0x0B: `get_comm_event_counter_response`
542/// - FC 0x0C: `get_comm_event_log_response`
543/// - FC 0x11: `report_server_id_response`
544///
545/// ## Data Semantics
546/// - `mei_type`, `sub_function`, counters, and event buffers are already validated and decoded.
547/// - Large payloads (event logs, generic encapsulated transport data) should typically be copied
548///   or forwarded quickly, then processed outside the callback hot path.
549#[cfg(feature = "diagnostics")]
550pub trait DiagnosticsResponse {
551    /// Called when a Read Device Identification response is received.
552    ///
553    /// Implementors can use this callback to process the device identity info (Vendor, Product Code, etc.).
554    ///
555    /// # Parameters
556    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
557    ///   does not natively use transaction IDs, the stack preserves the ID provided in
558    ///   the request and returns it here to allow for asynchronous tracking.
559    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
560    ///   - `unit_id`: if transport is tcp
561    ///   - `slave_addr`: if transport is serial
562    /// - `response`: Extracted device identification strings.
563    fn read_device_identification_response(
564        &mut self,
565        txn_id: u16,
566        unit_id_slave_addr: UnitIdOrSlaveAddr,
567        response: &DeviceIdentificationResponse,
568    );
569
570    /// Called when a generic Encapsulated Interface Transport response (FC 43) is received.
571    ///
572    /// # Parameters
573    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
574    ///   does not natively use transaction IDs, the stack preserves the ID provided in
575    ///   the request and returns it here to allow for asynchronous tracking.
576    /// - `unit_id_slave_addr`: The unit ID of the device that responded.
577    ///   - `unit_id`: if transport is tcp
578    ///   - `slave_addr`: if transport is serial
579    /// - `mei_type`: The MEI type returned in the response.
580    /// - `data`: The data payload returned in the response.
581    fn encapsulated_interface_transport_response(
582        &mut self,
583        txn_id: u16,
584        unit_id_slave_addr: UnitIdOrSlaveAddr,
585        mei_type: EncapsulatedInterfaceType,
586        data: &[u8],
587    );
588
589    /// Called when a Read Exception Status response (FC 07) is received.
590    ///
591    /// # Parameters
592    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
593    ///   does not natively use transaction IDs, the stack preserves the ID provided in
594    ///   the request and returns it here to allow for asynchronous tracking.
595    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
596    ///   - `unit_id`: if transport is tcp
597    ///   - `slave_addr`: if transport is serial
598    /// - `status`: The 8-bit exception status code returned by the server.
599    fn read_exception_status_response(
600        &mut self,
601        txn_id: u16,
602        unit_id_slave_addr: UnitIdOrSlaveAddr,
603        status: u8,
604    );
605
606    /// Called when a Diagnostics response (FC 08) is received.
607    ///
608    /// # Parameters
609    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
610    ///   does not natively use transaction IDs, the stack preserves the ID provided in
611    ///   the request and returns it here to allow for asynchronous tracking.
612    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
613    ///   - `unit_id`: if transport is tcp
614    ///   - `slave_addr`: if transport is serial
615    /// - `sub_function`: The sub-function code confirming the diagnostic test.
616    /// - `data`: Data payload returned by the diagnostic test (e.g., echoed loopback data).
617    fn diagnostics_response(
618        &mut self,
619        txn_id: u16,
620        unit_id_slave_addr: UnitIdOrSlaveAddr,
621        sub_function: DiagnosticSubFunction,
622        data: &[u16],
623    );
624
625    /// Called when a Get Comm Event Counter response (FC 11) is received.
626    ///
627    /// # Parameters
628    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
629    ///   does not natively use transaction IDs, the stack preserves the ID provided in
630    ///   the request and returns it here to allow for asynchronous tracking.
631    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
632    ///   - `unit_id`: if transport is tcp
633    ///   - `slave_addr`: if transport is serial
634    /// - `status`: The status word indicating if the device is busy.
635    /// - `event_count`: The number of successful messages processed by the device.
636    fn get_comm_event_counter_response(
637        &mut self,
638        txn_id: u16,
639        unit_id_slave_addr: UnitIdOrSlaveAddr,
640        status: u16,
641        event_count: u16,
642    );
643
644    /// Called when a Get Comm Event Log response (FC 12) is received.
645    ///
646    /// # Parameters
647    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
648    ///   does not natively use transaction IDs, the stack preserves the ID provided in
649    ///   the request and returns it here to allow for asynchronous tracking.
650    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
651    ///   - `unit_id`: if transport is tcp
652    ///   - `slave_addr`: if transport is serial
653    /// - `status`: The status word indicating device state.
654    /// - `event_count`: Number of successful messages processed.
655    /// - `message_count`: Quantity of messages processed since the last restart.
656    /// - `events`: Raw byte array containing the device's internal event log.
657    fn get_comm_event_log_response(
658        &mut self,
659        txn_id: u16,
660        unit_id_slave_addr: UnitIdOrSlaveAddr,
661        status: u16,
662        event_count: u16,
663        message_count: u16,
664        events: &[u8],
665    );
666
667    /// Called when a Report Server ID response (FC 17) is received.
668    ///
669    /// # Parameters
670    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
671    ///   does not natively use transaction IDs, the stack preserves the ID provided in
672    ///   the request and returns it here to allow for asynchronous tracking.
673    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
674    ///   - `unit_id`: if transport is tcp
675    ///   - `slave_addr`: if transport is serial
676    /// - `data`: Raw identity/status data provided by the manufacturer.
677    fn report_server_id_response(
678        &mut self,
679        txn_id: u16,
680        unit_id_slave_addr: UnitIdOrSlaveAddr,
681        data: &[u8],
682    );
683}