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}