1use heapless::Vec;
14
15use mbus_core::{
16 data_unit::common::{self, MAX_ADU_FRAME_LEN, Pdu},
17 errors::MbusError,
18 function_codes::public::FunctionCode,
19 transport::{TransportType, UnitIdOrSlaveAddr},
20};
21
22#[cfg(feature = "diagnostics")]
23use mbus_core::function_codes::public::{DiagnosticSubFunction, EncapsulatedInterfaceType};
24#[cfg(feature = "coils")]
25use mbus_core::models::coil::Coils;
26#[cfg(feature = "diagnostics")]
27use mbus_core::models::diagnostic::{ObjectId, ReadDeviceIdCode};
28#[cfg(feature = "file-record")]
29use mbus_core::models::file_record::SubRequest;
30
31use crate::client::command::ClientRequest;
32
33#[cfg(feature = "coils")]
36pub(crate) fn encode_read_coils(
38 txn_id: u16,
39 unit: UnitIdOrSlaveAddr,
40 address: u16,
41 quantity: u16,
42 transport_type: TransportType,
43) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
44 if !(1..=2000).contains(&quantity) {
45 return Err(MbusError::InvalidQuantity);
46 }
47 let pdu = Pdu::build_read_window(FunctionCode::ReadCoils, address, quantity)?;
48 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
49}
50
51#[cfg(feature = "coils")]
52pub(crate) fn encode_write_single_coil(
54 txn_id: u16,
55 unit: UnitIdOrSlaveAddr,
56 address: u16,
57 value: bool,
58 transport_type: TransportType,
59) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
60 let coil_value: u16 = if value { 0xFF00 } else { 0x0000 };
61 let pdu = Pdu::build_write_single_u16(FunctionCode::WriteSingleCoil, address, coil_value)?;
62 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
63}
64
65#[cfg(feature = "coils")]
66pub(crate) fn encode_write_multiple_coils(
68 txn_id: u16,
69 unit: UnitIdOrSlaveAddr,
70 address: u16,
71 coils: &Coils,
72 transport_type: TransportType,
73) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
74 let quantity = coils.quantity();
75 if !(1..=1968).contains(&quantity) {
76 return Err(MbusError::InvalidPduLength);
77 }
78 let byte_count = quantity.div_ceil(8) as usize;
79 let pdu = Pdu::build_write_multiple(
80 FunctionCode::WriteMultipleCoils,
81 address,
82 quantity,
83 &coils.values()[..byte_count],
84 )?;
85 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
86}
87
88#[cfg(feature = "holding-registers")]
91pub(crate) fn encode_read_holding_registers(
93 txn_id: u16,
94 unit: UnitIdOrSlaveAddr,
95 address: u16,
96 quantity: u16,
97 transport_type: TransportType,
98) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
99 if !(1..=125).contains(&quantity) {
100 return Err(MbusError::InvalidQuantity);
101 }
102 let pdu = Pdu::build_read_window(FunctionCode::ReadHoldingRegisters, address, quantity)?;
103 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
104}
105
106#[cfg(feature = "input-registers")]
107pub(crate) fn encode_read_input_registers(
109 txn_id: u16,
110 unit: UnitIdOrSlaveAddr,
111 address: u16,
112 quantity: u16,
113 transport_type: TransportType,
114) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
115 if !(1..=125).contains(&quantity) {
116 return Err(MbusError::InvalidQuantity);
117 }
118 let pdu = Pdu::build_read_window(FunctionCode::ReadInputRegisters, address, quantity)?;
119 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
120}
121
122#[cfg(feature = "holding-registers")]
123pub(crate) fn encode_write_single_register(
125 txn_id: u16,
126 unit: UnitIdOrSlaveAddr,
127 address: u16,
128 value: u16,
129 transport_type: TransportType,
130) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
131 let pdu = Pdu::build_write_single_u16(FunctionCode::WriteSingleRegister, address, value)?;
132 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
133}
134
135#[cfg(feature = "holding-registers")]
136pub(crate) fn encode_write_multiple_registers(
138 txn_id: u16,
139 unit: UnitIdOrSlaveAddr,
140 address: u16,
141 values: &[u16],
142 transport_type: TransportType,
143) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
144 let quantity = values.len() as u16;
145 if !(1..=123).contains(&quantity) {
146 return Err(MbusError::InvalidQuantity);
147 }
148 let byte_pairs: Vec<u8, { MAX_ADU_FRAME_LEN }> =
149 values.iter().flat_map(|v| v.to_be_bytes()).collect();
150 let pdu = Pdu::build_write_multiple(
151 FunctionCode::WriteMultipleRegisters,
152 address,
153 quantity,
154 &byte_pairs,
155 )?;
156 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
157}
158
159#[cfg(feature = "holding-registers")]
160pub(crate) fn encode_read_write_multiple_registers(
162 txn_id: u16,
163 unit: UnitIdOrSlaveAddr,
164 read_address: u16,
165 read_quantity: u16,
166 write_address: u16,
167 write_values: &[u16],
168 transport_type: TransportType,
169) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
170 let write_quantity = write_values.len() as u16;
171 let byte_pairs: Vec<u8, { MAX_ADU_FRAME_LEN }> =
172 write_values.iter().flat_map(|v| v.to_be_bytes()).collect();
173 let pdu = Pdu::build_read_write_multiple(
174 read_address,
175 read_quantity,
176 write_address,
177 write_quantity,
178 &byte_pairs,
179 )?;
180 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
181}
182
183#[cfg(feature = "holding-registers")]
184pub(crate) fn encode_mask_write_register(
186 txn_id: u16,
187 unit: UnitIdOrSlaveAddr,
188 address: u16,
189 and_mask: u16,
190 or_mask: u16,
191 transport_type: TransportType,
192) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
193 let pdu = Pdu::build_mask_write_register(address, and_mask, or_mask)?;
194 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
195}
196
197#[cfg(feature = "discrete-inputs")]
200pub(crate) fn encode_read_discrete_inputs(
202 txn_id: u16,
203 unit: UnitIdOrSlaveAddr,
204 address: u16,
205 quantity: u16,
206 transport_type: TransportType,
207) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
208 if !(1..=2000).contains(&quantity) {
209 return Err(MbusError::InvalidQuantity);
210 }
211 let pdu = Pdu::build_read_window(FunctionCode::ReadDiscreteInputs, address, quantity)?;
212 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
213}
214
215#[cfg(feature = "fifo")]
218pub(crate) fn encode_read_fifo_queue(
220 txn_id: u16,
221 unit: UnitIdOrSlaveAddr,
222 address: u16,
223 transport_type: TransportType,
224) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
225 let pdu = Pdu::build_u16_payload(FunctionCode::ReadFifoQueue, address)?;
226 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
227}
228
229#[cfg(feature = "file-record")]
232pub(crate) fn encode_read_file_record(
234 txn_id: u16,
235 unit: UnitIdOrSlaveAddr,
236 sub_request: &SubRequest,
237 transport_type: TransportType,
238) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
239 use mbus_core::models::file_record::PduDataBytes;
240 let payload_bytes = sub_request.to_sub_req_pdu_bytes()?;
241 let data_len = payload_bytes.len() as u8;
244 let pdu = Pdu::new(FunctionCode::ReadFileRecord, payload_bytes, data_len);
245 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
246}
247
248#[cfg(feature = "file-record")]
249pub(crate) fn encode_write_file_record(
251 txn_id: u16,
252 unit: UnitIdOrSlaveAddr,
253 sub_request: &SubRequest,
254 transport_type: TransportType,
255) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
256 use mbus_core::models::file_record::PduDataBytes;
257 let payload_bytes = sub_request.to_sub_req_pdu_bytes()?;
258 let data_len = payload_bytes.len() as u8;
261 let pdu = Pdu::new(FunctionCode::WriteFileRecord, payload_bytes, data_len);
262 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
263}
264
265#[cfg(feature = "diagnostics")]
268pub(crate) fn encode_read_device_identification(
270 txn_id: u16,
271 unit: UnitIdOrSlaveAddr,
272 read_device_id_code: ReadDeviceIdCode,
273 object_id: ObjectId,
274 transport_type: TransportType,
275) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
276 let object_id_byte = u8::from(object_id);
277 let payload: [u8; 2] = [read_device_id_code as u8, object_id_byte];
278 let pdu = Pdu::build_mei_type(
279 FunctionCode::EncapsulatedInterfaceTransport,
280 EncapsulatedInterfaceType::ReadDeviceIdentification as u8,
281 &payload,
282 )?;
283 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
284}
285
286#[cfg(feature = "diagnostics")]
287pub(crate) fn encode_encapsulated_interface_transport(
289 txn_id: u16,
290 unit: UnitIdOrSlaveAddr,
291 mei_type: EncapsulatedInterfaceType,
292 data: &[u8],
293 transport_type: TransportType,
294) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
295 let pdu = Pdu::build_mei_type(
296 FunctionCode::EncapsulatedInterfaceTransport,
297 mei_type as u8,
298 data,
299 )?;
300 common::compile_adu_frame(txn_id, unit.get(), pdu, transport_type)
301}
302
303#[cfg(feature = "diagnostics")]
304pub(crate) fn encode_read_exception_status(
306 unit: UnitIdOrSlaveAddr,
307 transport_type: TransportType,
308) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
309 let pdu = Pdu::build_empty(FunctionCode::ReadExceptionStatus);
310 common::compile_adu_frame(0, unit.get(), pdu, transport_type)
312}
313
314#[cfg(feature = "diagnostics")]
315pub(crate) fn encode_diagnostics(
317 unit: UnitIdOrSlaveAddr,
318 sub_function: DiagnosticSubFunction,
319 data: &[u16],
320 transport_type: TransportType,
321) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
322 let pdu = Pdu::build_sub_function(FunctionCode::Diagnostics, sub_function as u16, data)?;
323 common::compile_adu_frame(0, unit.get(), pdu, transport_type)
324}
325
326#[cfg(feature = "diagnostics")]
327pub(crate) fn encode_get_comm_event_counter(
329 unit: UnitIdOrSlaveAddr,
330 transport_type: TransportType,
331) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
332 let pdu = Pdu::build_empty(FunctionCode::GetCommEventCounter);
333 common::compile_adu_frame(0, unit.get(), pdu, transport_type)
334}
335
336#[cfg(feature = "diagnostics")]
337pub(crate) fn encode_get_comm_event_log(
339 unit: UnitIdOrSlaveAddr,
340 transport_type: TransportType,
341) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
342 let pdu = Pdu::build_empty(FunctionCode::GetCommEventLog);
343 common::compile_adu_frame(0, unit.get(), pdu, transport_type)
344}
345
346#[cfg(feature = "diagnostics")]
347pub(crate) fn encode_report_server_id(
349 unit: UnitIdOrSlaveAddr,
350 transport_type: TransportType,
351) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
352 let pdu = Pdu::build_empty(FunctionCode::ReportServerId);
353 common::compile_adu_frame(0, unit.get(), pdu, transport_type)
354}
355
356pub fn encode_request(
365 txn_id: u16,
366 req: &ClientRequest,
367 transport_type: TransportType,
368) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
369 match req {
370 #[cfg(feature = "coils")]
371 ClientRequest::ReadMultipleCoils {
372 unit,
373 address,
374 quantity,
375 } => encode_read_coils(txn_id, *unit, *address, *quantity, transport_type),
376
377 #[cfg(feature = "coils")]
378 ClientRequest::WriteSingleCoil {
379 unit,
380 address,
381 value,
382 } => encode_write_single_coil(txn_id, *unit, *address, *value, transport_type),
383
384 #[cfg(feature = "coils")]
385 ClientRequest::WriteMultipleCoils {
386 unit,
387 address,
388 coils,
389 } => encode_write_multiple_coils(txn_id, *unit, *address, coils, transport_type),
390
391 #[cfg(feature = "holding-registers")]
392 ClientRequest::ReadHoldingRegisters {
393 unit,
394 address,
395 quantity,
396 } => encode_read_holding_registers(txn_id, *unit, *address, *quantity, transport_type),
397
398 #[cfg(feature = "input-registers")]
399 ClientRequest::ReadInputRegisters {
400 unit,
401 address,
402 quantity,
403 } => encode_read_input_registers(txn_id, *unit, *address, *quantity, transport_type),
404
405 #[cfg(feature = "holding-registers")]
406 ClientRequest::WriteSingleRegister {
407 unit,
408 address,
409 value,
410 } => encode_write_single_register(txn_id, *unit, *address, *value, transport_type),
411
412 #[cfg(feature = "holding-registers")]
413 ClientRequest::WriteMultipleRegisters {
414 unit,
415 address,
416 values,
417 } => encode_write_multiple_registers(txn_id, *unit, *address, values, transport_type),
418
419 #[cfg(feature = "holding-registers")]
420 ClientRequest::ReadWriteMultipleRegisters {
421 unit,
422 read_address,
423 read_quantity,
424 write_address,
425 write_values,
426 } => encode_read_write_multiple_registers(
427 txn_id,
428 *unit,
429 *read_address,
430 *read_quantity,
431 *write_address,
432 write_values,
433 transport_type,
434 ),
435
436 #[cfg(feature = "holding-registers")]
437 ClientRequest::MaskWriteRegister {
438 unit,
439 address,
440 and_mask,
441 or_mask,
442 } => {
443 encode_mask_write_register(txn_id, *unit, *address, *and_mask, *or_mask, transport_type)
444 }
445
446 #[cfg(feature = "discrete-inputs")]
447 ClientRequest::ReadDiscreteInputs {
448 unit,
449 address,
450 quantity,
451 } => encode_read_discrete_inputs(txn_id, *unit, *address, *quantity, transport_type),
452
453 #[cfg(feature = "fifo")]
454 ClientRequest::ReadFifoQueue { unit, address } => {
455 encode_read_fifo_queue(txn_id, *unit, *address, transport_type)
456 }
457
458 #[cfg(feature = "file-record")]
459 ClientRequest::ReadFileRecord { unit, sub_request } => {
460 encode_read_file_record(txn_id, *unit, sub_request, transport_type)
461 }
462
463 #[cfg(feature = "file-record")]
464 ClientRequest::WriteFileRecord { unit, sub_request } => {
465 encode_write_file_record(txn_id, *unit, sub_request, transport_type)
466 }
467
468 #[cfg(feature = "diagnostics")]
469 ClientRequest::ReadDeviceIdentification {
470 unit,
471 read_device_id_code,
472 object_id,
473 } => encode_read_device_identification(
474 txn_id,
475 *unit,
476 *read_device_id_code,
477 *object_id,
478 transport_type,
479 ),
480
481 #[cfg(feature = "diagnostics")]
482 ClientRequest::EncapsulatedInterfaceTransport {
483 unit,
484 mei_type,
485 data,
486 } => {
487 encode_encapsulated_interface_transport(txn_id, *unit, *mei_type, data, transport_type)
488 }
489
490 #[cfg(feature = "diagnostics")]
491 ClientRequest::ReadExceptionStatus { unit } => {
492 encode_read_exception_status(*unit, transport_type)
493 }
494
495 #[cfg(feature = "diagnostics")]
496 ClientRequest::Diagnostics {
497 unit,
498 sub_function,
499 data,
500 } => encode_diagnostics(*unit, *sub_function, data, transport_type),
501
502 #[cfg(feature = "diagnostics")]
503 ClientRequest::GetCommEventCounter { unit } => {
504 encode_get_comm_event_counter(*unit, transport_type)
505 }
506
507 #[cfg(feature = "diagnostics")]
508 ClientRequest::GetCommEventLog { unit } => encode_get_comm_event_log(*unit, transport_type),
509
510 #[cfg(feature = "diagnostics")]
511 ClientRequest::ReportServerId { unit } => encode_report_server_id(*unit, transport_type),
512
513 #[allow(unreachable_patterns)]
514 _ => unreachable!(),
515 }
516}