Skip to main content

mbus_client/services/file_record/
apis.rs

1use crate::{
2    app::FileRecordResponse,
3    services::{
4        ClientCommon, ClientServices, OperationMeta,
5        file_record::{self, SubRequest},
6    },
7};
8use mbus_core::{
9    errors::MbusError,
10    transport::{Transport, UnitIdOrSlaveAddr},
11};
12
13impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
14where
15    TRANSPORT: Transport,
16    APP: ClientCommon + FileRecordResponse,
17{
18    /// Sends a Read File Record request (Function Code 0x14).
19    ///
20    /// This function performs a data reference operation to allow reading of "File Records".
21    /// A Modbus file is a collection of records. Each file can contain up to 10000 records
22    /// (addressed 0000 to 9999 decimal).
23    ///
24    /// # Parameters
25    /// - `txn_id`: Transaction ID of the original request. While Modbus Serial (RTU/ASCII)
26    ///   does not natively use transaction IDs, the stack preserves the ID provided in
27    ///   the request and returns it here to allow for asynchronous tracking.
28    /// - `unit_id_slave_addr`: The target Modbus unit ID or slave address.
29    ///   - `unit_id`: if transport is tcp
30    ///   - `slave_addr`: if transport is serial/// - `sub_request`: A structure containing one or more sub-requests. Each sub-request
31    ///     specifies the file number, record number, and the number of registers to read.
32    ///
33    /// # Returns
34    /// - `Ok(())`: If the request was successfully built, the expectation was queued,
35    ///   and the frame was transmitted.
36    /// - `Err(MbusError)`: If the address is a broadcast address (not allowed for FC 0x14),
37    ///   if the PDU exceeds the maximum allowed size, or if transport fails.
38    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
39    pub fn read_file_record(
40        &mut self,
41        txn_id: u16,
42        unit_id_slave_addr: UnitIdOrSlaveAddr,
43        sub_request: &SubRequest,
44    ) -> Result<(), MbusError> {
45        // Modbus protocol specification: Broadcast is not supported for Read File Record.
46        if unit_id_slave_addr.is_broadcast() {
47            return Err(MbusError::broadcast_not_allowed());
48        }
49
50        // Construct the ADU frame (MBAP/Serial Header + PDU + CRC/LRC if applicable)
51        let frame = file_record::service::ServiceBuilder::read_file_record(
52            txn_id,
53            unit_id_slave_addr.get(),
54            sub_request,
55            self.transport.transport_type(),
56        )?;
57
58        // Register an expectation so the client knows how to route the incoming response.
59        self.add_an_expectation(
60            txn_id,
61            unit_id_slave_addr,
62            &frame,
63            OperationMeta::Other,
64            Self::handle_read_file_record_response,
65        )?;
66
67        // Dispatch the frame through the underlying transport (TCP/RTU/ASCII).
68        self.dispatch_request_frame(txn_id, unit_id_slave_addr, &frame)?;
69        Ok(())
70    }
71
72    /// Sends a Write File Record request (Function Code 0x15).
73    ///
74    /// This function performs a data reference operation to allow writing of "File Records".
75    /// The request can contain multiple sub-requests, each writing a sequence of registers
76    /// to a specific file and record.
77    ///
78    /// # Parameters
79    /// - `txn_id`: Transaction ID for tracking the request/response pair.
80    /// - `unit_id_slave_addr`: The target Modbus unit ID (TCP) or slave address (Serial).
81    /// - `sub_request`: A structure containing one or more sub-requests. Each sub-request
82    ///   specifies the file number, record number, record length, and the actual data to write.
83    ///
84    /// # Returns
85    /// - `Ok(())`: If the request was successfully built and transmitted.
86    /// - `Err(MbusError)`: If broadcast is attempted, if the PDU is malformed, or transport fails.
87    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
88    pub fn write_file_record(
89        &mut self,
90        txn_id: u16,
91        unit_id_slave_addr: UnitIdOrSlaveAddr,
92        sub_request: &SubRequest,
93    ) -> Result<(), MbusError> {
94        // Modbus protocol specification: Broadcast is not supported for Write File Record.
95        if unit_id_slave_addr.is_broadcast() {
96            return Err(MbusError::broadcast_not_allowed()); // Modbus forbids broadcast for Write File Record
97        }
98
99        // Construct the ADU frame using the service builder.
100        let frame = file_record::service::ServiceBuilder::write_file_record(
101            txn_id,
102            unit_id_slave_addr.get(),
103            sub_request,
104            self.transport.transport_type(),
105        )?;
106
107        // Register the expectation for the response handler.
108        self.add_an_expectation(
109            txn_id,
110            unit_id_slave_addr,
111            &frame,
112            OperationMeta::Other,
113            Self::handle_write_file_record_response,
114        )?;
115
116        // Send the compiled frame.
117        self.dispatch_request_frame(txn_id, unit_id_slave_addr, &frame)?;
118        Ok(())
119    }
120}