1#[cfg(feature = "coils")]
20pub mod coil;
21#[cfg(feature = "diagnostics")]
22pub mod diagnostic;
23#[cfg(feature = "discrete-inputs")]
24pub mod discrete_input;
25#[cfg(feature = "fifo")]
26pub mod fifo_queue;
27#[cfg(feature = "file-record")]
28pub mod file_record;
29#[cfg(feature = "registers")]
30pub mod register;
31
32use crate::app::RequestErrorNotifier;
33#[cfg(feature = "diagnostics")]
34use diagnostic::ReadDeviceIdCode;
35use heapless::Vec;
36use mbus_core::data_unit::common::{ModbusMessage, SlaveAddress, derive_length_from_bytes};
37use mbus_core::function_codes::public::EncapsulatedInterfaceType;
38use mbus_core::transport::{UidSaddrFrom, UnitIdOrSlaveAddr};
39use mbus_core::{
40 data_unit::common::{self, MAX_ADU_FRAME_LEN},
41 errors::MbusError,
42 transport::{ModbusConfig, ModbusSerialConfig, TimeKeeper, Transport, TransportType},
43};
44
45#[cfg(feature = "logging")]
46macro_rules! client_log_debug {
47 ($($arg:tt)*) => {
48 log::debug!($($arg)*)
49 };
50}
51
52#[cfg(not(feature = "logging"))]
53macro_rules! client_log_debug {
54 ($($arg:tt)*) => {{
55 let _ = core::format_args!($($arg)*);
56 }};
57}
58
59#[cfg(feature = "logging")]
60macro_rules! client_log_trace {
61 ($($arg:tt)*) => {
62 log::trace!($($arg)*)
63 };
64}
65
66#[cfg(not(feature = "logging"))]
67macro_rules! client_log_trace {
68 ($($arg:tt)*) => {{
69 let _ = core::format_args!($($arg)*);
70 }};
71}
72
73type ResponseHandler<T, A, const N: usize> =
74 fn(&mut ClientServices<T, A, N>, &ExpectedResponse<T, A, N>, &ModbusMessage);
75
76#[doc(hidden)]
78pub trait SerialQueueSizeOne {}
79impl SerialQueueSizeOne for [(); 1] {}
80
81pub type SerialClientServices<TRANSPORT, APP> = ClientServices<TRANSPORT, APP, 1>;
83
84#[cfg(feature = "coils")]
89pub struct CoilsApi<'a, TRANSPORT, APP, const N: usize> {
90 client: &'a mut ClientServices<TRANSPORT, APP, N>,
91}
92
93#[cfg(feature = "coils")]
94impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
95where
96 TRANSPORT: Transport,
97 APP: ClientCommon + crate::app::CoilResponse,
98{
99 pub fn coils(&mut self) -> CoilsApi<'_, TRANSPORT, APP, N> {
101 CoilsApi { client: self }
102 }
103
104 pub fn with_coils<R>(
106 &mut self,
107 f: impl FnOnce(&mut CoilsApi<'_, TRANSPORT, APP, N>) -> R,
108 ) -> R {
109 let mut api = self.coils();
110 f(&mut api)
111 }
112}
113
114#[cfg(feature = "coils")]
115impl<TRANSPORT, APP, const N: usize> CoilsApi<'_, TRANSPORT, APP, N>
116where
117 TRANSPORT: Transport,
118 APP: ClientCommon + crate::app::CoilResponse,
119{
120 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
122 pub fn read_multiple_coils(
123 &mut self,
124 txn_id: u16,
125 unit_id_slave_addr: UnitIdOrSlaveAddr,
126 address: u16,
127 quantity: u16,
128 ) -> Result<(), MbusError> {
129 self.client
130 .read_multiple_coils(txn_id, unit_id_slave_addr, address, quantity)
131 }
132
133 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
135 pub fn read_single_coil(
136 &mut self,
137 txn_id: u16,
138 unit_id_slave_addr: UnitIdOrSlaveAddr,
139 address: u16,
140 ) -> Result<(), MbusError> {
141 self.client
142 .read_single_coil(txn_id, unit_id_slave_addr, address)
143 }
144
145 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
147 pub fn write_single_coil(
148 &mut self,
149 txn_id: u16,
150 unit_id_slave_addr: UnitIdOrSlaveAddr,
151 address: u16,
152 value: bool,
153 ) -> Result<(), MbusError> {
154 self.client
155 .write_single_coil(txn_id, unit_id_slave_addr, address, value)
156 }
157
158 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
160 pub fn write_multiple_coils(
161 &mut self,
162 txn_id: u16,
163 unit_id_slave_addr: UnitIdOrSlaveAddr,
164 address: u16,
165 values: &crate::services::coil::Coils,
166 ) -> Result<(), MbusError> {
167 self.client
168 .write_multiple_coils(txn_id, unit_id_slave_addr, address, values)
169 }
170}
171
172#[cfg(feature = "discrete-inputs")]
174pub struct DiscreteInputsApi<'a, TRANSPORT, APP, const N: usize> {
175 client: &'a mut ClientServices<TRANSPORT, APP, N>,
176}
177
178#[cfg(feature = "discrete-inputs")]
179impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
180where
181 TRANSPORT: Transport,
182 APP: ClientCommon + crate::app::DiscreteInputResponse,
183{
184 pub fn discrete_inputs(&mut self) -> DiscreteInputsApi<'_, TRANSPORT, APP, N> {
186 DiscreteInputsApi { client: self }
187 }
188
189 pub fn with_discrete_inputs<R>(
191 &mut self,
192 f: impl FnOnce(&mut DiscreteInputsApi<'_, TRANSPORT, APP, N>) -> R,
193 ) -> R {
194 let mut api = self.discrete_inputs();
195 f(&mut api)
196 }
197}
198
199#[cfg(feature = "discrete-inputs")]
200impl<TRANSPORT, APP, const N: usize> DiscreteInputsApi<'_, TRANSPORT, APP, N>
201where
202 TRANSPORT: Transport,
203 APP: ClientCommon + crate::app::DiscreteInputResponse,
204{
205 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
207 pub fn read_discrete_inputs(
208 &mut self,
209 txn_id: u16,
210 unit_id_slave_addr: UnitIdOrSlaveAddr,
211 address: u16,
212 quantity: u16,
213 ) -> Result<(), MbusError> {
214 self.client
215 .read_discrete_inputs(txn_id, unit_id_slave_addr, address, quantity)
216 }
217
218 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
220 pub fn read_single_discrete_input(
221 &mut self,
222 txn_id: u16,
223 unit_id_slave_addr: UnitIdOrSlaveAddr,
224 address: u16,
225 ) -> Result<(), MbusError> {
226 self.client
227 .read_single_discrete_input(txn_id, unit_id_slave_addr, address)
228 }
229}
230
231#[cfg(feature = "registers")]
233pub struct RegistersApi<'a, TRANSPORT, APP, const N: usize> {
234 client: &'a mut ClientServices<TRANSPORT, APP, N>,
235}
236
237#[cfg(feature = "registers")]
238impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
239where
240 TRANSPORT: Transport,
241 APP: ClientCommon + crate::app::RegisterResponse,
242{
243 pub fn registers(&mut self) -> RegistersApi<'_, TRANSPORT, APP, N> {
245 RegistersApi { client: self }
246 }
247
248 pub fn with_registers<R>(
250 &mut self,
251 f: impl FnOnce(&mut RegistersApi<'_, TRANSPORT, APP, N>) -> R,
252 ) -> R {
253 let mut api = self.registers();
254 f(&mut api)
255 }
256}
257
258#[cfg(feature = "registers")]
259impl<TRANSPORT, APP, const N: usize> RegistersApi<'_, TRANSPORT, APP, N>
260where
261 TRANSPORT: Transport,
262 APP: ClientCommon + crate::app::RegisterResponse,
263{
264 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
266 pub fn read_holding_registers(
267 &mut self,
268 txn_id: u16,
269 unit_id_slave_addr: UnitIdOrSlaveAddr,
270 from_address: u16,
271 quantity: u16,
272 ) -> Result<(), MbusError> {
273 self.client
274 .read_holding_registers(txn_id, unit_id_slave_addr, from_address, quantity)
275 }
276
277 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
279 pub fn read_single_holding_register(
280 &mut self,
281 txn_id: u16,
282 unit_id_slave_addr: UnitIdOrSlaveAddr,
283 address: u16,
284 ) -> Result<(), MbusError> {
285 self.client
286 .read_single_holding_register(txn_id, unit_id_slave_addr, address)
287 }
288
289 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
291 pub fn read_input_registers(
292 &mut self,
293 txn_id: u16,
294 unit_id_slave_addr: UnitIdOrSlaveAddr,
295 address: u16,
296 quantity: u16,
297 ) -> Result<(), MbusError> {
298 self.client
299 .read_input_registers(txn_id, unit_id_slave_addr, address, quantity)
300 }
301
302 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
304 pub fn read_single_input_register(
305 &mut self,
306 txn_id: u16,
307 unit_id_slave_addr: UnitIdOrSlaveAddr,
308 address: u16,
309 ) -> Result<(), MbusError> {
310 self.client
311 .read_single_input_register(txn_id, unit_id_slave_addr, address)
312 }
313
314 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
316 pub fn write_single_register(
317 &mut self,
318 txn_id: u16,
319 unit_id_slave_addr: UnitIdOrSlaveAddr,
320 address: u16,
321 value: u16,
322 ) -> Result<(), MbusError> {
323 self.client
324 .write_single_register(txn_id, unit_id_slave_addr, address, value)
325 }
326
327 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
329 pub fn write_multiple_registers(
330 &mut self,
331 txn_id: u16,
332 unit_id_slave_addr: UnitIdOrSlaveAddr,
333 address: u16,
334 quantity: u16,
335 values: &[u16],
336 ) -> Result<(), MbusError> {
337 self.client
338 .write_multiple_registers(txn_id, unit_id_slave_addr, address, quantity, values)
339 }
340
341 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
343 pub fn read_write_multiple_registers(
344 &mut self,
345 txn_id: u16,
346 unit_id_slave_addr: UnitIdOrSlaveAddr,
347 read_address: u16,
348 read_quantity: u16,
349 write_address: u16,
350 write_values: &[u16],
351 ) -> Result<(), MbusError> {
352 self.client.read_write_multiple_registers(
353 txn_id,
354 unit_id_slave_addr,
355 read_address,
356 read_quantity,
357 write_address,
358 write_values,
359 )
360 }
361
362 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
364 pub fn mask_write_register(
365 &mut self,
366 txn_id: u16,
367 unit_id_slave_addr: UnitIdOrSlaveAddr,
368 address: u16,
369 and_mask: u16,
370 or_mask: u16,
371 ) -> Result<(), MbusError> {
372 self.client
373 .mask_write_register(txn_id, unit_id_slave_addr, address, and_mask, or_mask)
374 }
375}
376
377#[cfg(feature = "diagnostics")]
379pub struct DiagnosticApi<'a, TRANSPORT, APP, const N: usize> {
380 client: &'a mut ClientServices<TRANSPORT, APP, N>,
381}
382
383#[cfg(feature = "diagnostics")]
384impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
385where
386 TRANSPORT: Transport,
387 APP: ClientCommon + crate::app::DiagnosticsResponse,
388{
389 pub fn diagnostic(&mut self) -> DiagnosticApi<'_, TRANSPORT, APP, N> {
391 DiagnosticApi { client: self }
392 }
393
394 pub fn with_diagnostic<R>(
396 &mut self,
397 f: impl FnOnce(&mut DiagnosticApi<'_, TRANSPORT, APP, N>) -> R,
398 ) -> R {
399 let mut api = self.diagnostic();
400 f(&mut api)
401 }
402}
403
404#[cfg(feature = "diagnostics")]
405impl<TRANSPORT, APP, const N: usize> DiagnosticApi<'_, TRANSPORT, APP, N>
406where
407 TRANSPORT: Transport,
408 APP: ClientCommon + crate::app::DiagnosticsResponse,
409{
410 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
412 pub fn read_device_identification(
413 &mut self,
414 txn_id: u16,
415 unit_id_slave_addr: UnitIdOrSlaveAddr,
416 read_device_id_code: crate::services::diagnostic::ReadDeviceIdCode,
417 object_id: crate::services::diagnostic::ObjectId,
418 ) -> Result<(), MbusError> {
419 self.client.read_device_identification(
420 txn_id,
421 unit_id_slave_addr,
422 read_device_id_code,
423 object_id,
424 )
425 }
426
427 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
429 pub fn encapsulated_interface_transport(
430 &mut self,
431 txn_id: u16,
432 unit_id_slave_addr: UnitIdOrSlaveAddr,
433 mei_type: EncapsulatedInterfaceType,
434 data: &[u8],
435 ) -> Result<(), MbusError> {
436 self.client
437 .encapsulated_interface_transport(txn_id, unit_id_slave_addr, mei_type, data)
438 }
439
440 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
442 pub fn read_exception_status(
443 &mut self,
444 txn_id: u16,
445 unit_id_slave_addr: UnitIdOrSlaveAddr,
446 ) -> Result<(), MbusError> {
447 self.client
448 .read_exception_status(txn_id, unit_id_slave_addr)
449 }
450
451 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
453 pub fn diagnostics(
454 &mut self,
455 txn_id: u16,
456 unit_id_slave_addr: UnitIdOrSlaveAddr,
457 sub_function: mbus_core::function_codes::public::DiagnosticSubFunction,
458 data: &[u16],
459 ) -> Result<(), MbusError> {
460 self.client
461 .diagnostics(txn_id, unit_id_slave_addr, sub_function, data)
462 }
463
464 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
466 pub fn get_comm_event_counter(
467 &mut self,
468 txn_id: u16,
469 unit_id_slave_addr: UnitIdOrSlaveAddr,
470 ) -> Result<(), MbusError> {
471 self.client
472 .get_comm_event_counter(txn_id, unit_id_slave_addr)
473 }
474
475 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
477 pub fn get_comm_event_log(
478 &mut self,
479 txn_id: u16,
480 unit_id_slave_addr: UnitIdOrSlaveAddr,
481 ) -> Result<(), MbusError> {
482 self.client.get_comm_event_log(txn_id, unit_id_slave_addr)
483 }
484
485 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
487 pub fn report_server_id(
488 &mut self,
489 txn_id: u16,
490 unit_id_slave_addr: UnitIdOrSlaveAddr,
491 ) -> Result<(), MbusError> {
492 self.client.report_server_id(txn_id, unit_id_slave_addr)
493 }
494}
495
496#[cfg(feature = "fifo")]
498pub struct FifoApi<'a, TRANSPORT, APP, const N: usize> {
499 client: &'a mut ClientServices<TRANSPORT, APP, N>,
500}
501
502#[cfg(feature = "fifo")]
503impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
504where
505 TRANSPORT: Transport,
506 APP: ClientCommon + crate::app::FifoQueueResponse,
507{
508 pub fn fifo(&mut self) -> FifoApi<'_, TRANSPORT, APP, N> {
510 FifoApi { client: self }
511 }
512
513 pub fn with_fifo<R>(&mut self, f: impl FnOnce(&mut FifoApi<'_, TRANSPORT, APP, N>) -> R) -> R {
515 let mut api = self.fifo();
516 f(&mut api)
517 }
518}
519
520#[cfg(feature = "fifo")]
521impl<TRANSPORT, APP, const N: usize> FifoApi<'_, TRANSPORT, APP, N>
522where
523 TRANSPORT: Transport,
524 APP: ClientCommon + crate::app::FifoQueueResponse,
525{
526 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
528 pub fn read_fifo_queue(
529 &mut self,
530 txn_id: u16,
531 unit_id_slave_addr: UnitIdOrSlaveAddr,
532 address: u16,
533 ) -> Result<(), MbusError> {
534 self.client
535 .read_fifo_queue(txn_id, unit_id_slave_addr, address)
536 }
537}
538
539#[cfg(feature = "file-record")]
541pub struct FileRecordsApi<'a, TRANSPORT, APP, const N: usize> {
542 client: &'a mut ClientServices<TRANSPORT, APP, N>,
543}
544
545#[cfg(feature = "file-record")]
546impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
547where
548 TRANSPORT: Transport,
549 APP: ClientCommon + crate::app::FileRecordResponse,
550{
551 pub fn file_records(&mut self) -> FileRecordsApi<'_, TRANSPORT, APP, N> {
553 FileRecordsApi { client: self }
554 }
555
556 pub fn with_file_records<R>(
558 &mut self,
559 f: impl FnOnce(&mut FileRecordsApi<'_, TRANSPORT, APP, N>) -> R,
560 ) -> R {
561 let mut api = self.file_records();
562 f(&mut api)
563 }
564}
565
566#[cfg(feature = "file-record")]
567impl<TRANSPORT, APP, const N: usize> FileRecordsApi<'_, TRANSPORT, APP, N>
568where
569 TRANSPORT: Transport,
570 APP: ClientCommon + crate::app::FileRecordResponse,
571{
572 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
574 pub fn read_file_record(
575 &mut self,
576 txn_id: u16,
577 unit_id_slave_addr: UnitIdOrSlaveAddr,
578 sub_request: &crate::services::file_record::SubRequest,
579 ) -> Result<(), MbusError> {
580 self.client
581 .read_file_record(txn_id, unit_id_slave_addr, sub_request)
582 }
583
584 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
586 pub fn write_file_record(
587 &mut self,
588 txn_id: u16,
589 unit_id_slave_addr: UnitIdOrSlaveAddr,
590 sub_request: &crate::services::file_record::SubRequest,
591 ) -> Result<(), MbusError> {
592 self.client
593 .write_file_record(txn_id, unit_id_slave_addr, sub_request)
594 }
595}
596
597#[derive(Debug, Clone, PartialEq, Eq)]
599pub(crate) struct Single {
600 address: u16,
601 value: u16,
602}
603#[derive(Debug, Clone, PartialEq, Eq)]
605pub(crate) struct Multiple {
606 address: u16,
607 quantity: u16,
608}
609#[derive(Debug, Clone, PartialEq, Eq)]
611pub(crate) struct Mask {
612 address: u16,
613 and_mask: u16,
614 or_mask: u16,
615}
616#[cfg(feature = "diagnostics")]
618#[derive(Debug, Clone, PartialEq, Eq)]
619pub(crate) struct Diag {
620 device_id_code: ReadDeviceIdCode,
621 encap_type: EncapsulatedInterfaceType,
622}
623
624#[derive(Debug, Clone, PartialEq, Eq)]
626pub(crate) enum OperationMeta {
627 Other,
628 Single(Single),
629 Multiple(Multiple),
630 Masking(Mask),
631 #[cfg(feature = "diagnostics")]
632 Diag(Diag),
633}
634
635impl OperationMeta {
636 fn address(&self) -> u16 {
637 match self {
638 OperationMeta::Single(s) => s.address,
639 OperationMeta::Multiple(m) => m.address,
640 OperationMeta::Masking(m) => m.address,
641 _ => 0,
642 }
643 }
644
645 fn value(&self) -> u16 {
646 match self {
647 OperationMeta::Single(s) => s.value,
648 _ => 0,
649 }
650 }
651
652 fn quantity(&self) -> u16 {
653 match self {
654 OperationMeta::Single(_) => 1,
655 OperationMeta::Multiple(m) => m.quantity,
656 _ => 0,
657 }
658 }
659
660 fn and_mask(&self) -> u16 {
661 match self {
662 OperationMeta::Masking(m) => m.and_mask,
663 _ => 0,
664 }
665 }
666
667 fn or_mask(&self) -> u16 {
668 match self {
669 OperationMeta::Masking(m) => m.or_mask,
670 _ => 0,
671 }
672 }
673
674 fn is_single(&self) -> bool {
675 matches!(self, OperationMeta::Single(_))
676 }
677
678 fn single_value(&self) -> u16 {
679 match self {
680 OperationMeta::Single(s) => s.value,
681 _ => 0,
682 }
683 }
684
685 fn device_id_code(&self) -> ReadDeviceIdCode {
686 match self {
687 #[cfg(feature = "diagnostics")]
688 OperationMeta::Diag(d) => d.device_id_code,
689 _ => ReadDeviceIdCode::default(),
690 }
691 }
692
693 fn encap_type(&self) -> EncapsulatedInterfaceType {
694 match self {
695 #[cfg(feature = "diagnostics")]
696 OperationMeta::Diag(d) => d.encap_type,
697 _ => EncapsulatedInterfaceType::default(),
698 }
699 }
700}
701
702#[derive(Debug)]
709pub(crate) struct ExpectedResponse<T, A, const N: usize> {
710 pub txn_id: u16,
712 pub unit_id_or_slave_addr: u8,
714
715 pub original_adu: Vec<u8, MAX_ADU_FRAME_LEN>,
718
719 pub sent_timestamp: u64,
721 pub retries_left: u8,
723 pub retry_attempt_index: u8,
725 pub next_retry_timestamp: Option<u64>,
730
731 pub handler: ResponseHandler<T, A, N>,
733
734 pub operation_meta: OperationMeta,
736}
737
738#[derive(Debug)]
750pub struct ClientServices<TRANSPORT, APP, const N: usize = 1> {
751 app: APP,
753 transport: TRANSPORT,
755
756 config: ModbusConfig,
758
759 rxed_frame: Vec<u8, MAX_ADU_FRAME_LEN>,
761
762 expected_responses: Vec<ExpectedResponse<TRANSPORT, APP, N>, N>,
764
765 next_timeout_check: Option<u64>,
767}
768
769pub trait ClientCommon: RequestErrorNotifier + TimeKeeper {}
777
778impl<T> ClientCommon for T where T: RequestErrorNotifier + TimeKeeper {}
779
780impl<T, APP, const N: usize> ClientServices<T, APP, N>
781where
782 T: Transport,
783 APP: ClientCommon,
784{
785 fn dispatch_response(&mut self, message: &ModbusMessage) {
786 let txn_id = message.transaction_id();
787 let unit_id_or_slave_addr = message.unit_id_or_slave_addr();
788
789 let index = if self.transport.transport_type().is_tcp_type() {
790 self.expected_responses.iter().position(|r| {
791 r.txn_id == txn_id && r.unit_id_or_slave_addr == unit_id_or_slave_addr.into()
792 })
793 } else {
794 self.expected_responses
795 .iter()
796 .position(|r| r.unit_id_or_slave_addr == unit_id_or_slave_addr.into())
797 };
798
799 let expected = match index {
800 Some(i) => self.expected_responses.swap_remove(i),
803 None => {
804 client_log_debug!(
805 "dropping unmatched response: txn_id={}, unit_id_or_slave_addr={}",
806 txn_id,
807 unit_id_or_slave_addr.get()
808 );
809 return;
810 }
811 };
812
813 client_log_trace!(
814 "dispatching response: txn_id={}, unit_id_or_slave_addr={}, queue_len_after_pop={}",
815 txn_id,
816 unit_id_or_slave_addr.get(),
817 self.expected_responses.len()
818 );
819
820 if let Some(exception_code) = message.pdu().error_code() {
823 client_log_debug!(
824 "modbus exception response: txn_id={}, unit_id_or_slave_addr={}, code=0x{:02X}",
825 txn_id,
826 unit_id_or_slave_addr.get(),
827 exception_code
828 );
829 self.app.request_failed(
830 txn_id,
831 unit_id_or_slave_addr,
832 MbusError::ModbusException(exception_code),
833 );
834 return;
835 }
836
837 (expected.handler)(self, &expected, message);
838 }
839}
840
841impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
842where
843 TRANSPORT: Transport,
844 TRANSPORT::Error: Into<MbusError>,
845 APP: RequestErrorNotifier + TimeKeeper,
846{
847 pub fn poll(&mut self) {
894 match self.transport.recv() {
896 Ok(frame) => {
897 client_log_trace!("received {} transport bytes", frame.len());
898 if self.rxed_frame.extend_from_slice(frame.as_slice()).is_err() {
899 client_log_debug!(
901 "received frame buffer overflow while appending {} bytes; clearing receive buffer",
902 frame.len()
903 );
904 self.rxed_frame.clear();
905 }
906
907 while !self.rxed_frame.is_empty() {
909 match self.ingest_frame() {
910 Ok(consumed) => {
911 client_log_trace!(
912 "ingested complete frame consuming {} bytes from rx buffer len {}",
913 consumed,
914 self.rxed_frame.len()
915 );
916 let len = self.rxed_frame.len();
917 if consumed < len {
918 self.rxed_frame.copy_within(consumed.., 0);
920 self.rxed_frame.truncate(len - consumed);
921 } else {
922 self.rxed_frame.clear();
923 }
924 }
925 Err(MbusError::BufferTooSmall) => {
926 client_log_trace!(
928 "incomplete frame in rx buffer; waiting for more bytes (buffer_len={})",
929 self.rxed_frame.len()
930 );
931 break;
932 }
933 Err(err) => {
934 client_log_debug!(
936 "frame parse/resync event: error={:?}, buffer_len={}; dropping 1 byte",
937 err,
938 self.rxed_frame.len()
939 );
940 let len = self.rxed_frame.len();
941 if len > 1 {
942 self.rxed_frame.copy_within(1.., 0);
943 self.rxed_frame.truncate(len - 1);
944 } else {
945 self.rxed_frame.clear();
946 }
947 }
948 }
949 }
950 }
951 Err(err) => {
952 let recv_error: MbusError = err.into();
953 let is_connection_loss = matches!(
954 recv_error,
955 MbusError::ConnectionClosed
956 | MbusError::ConnectionFailed
957 | MbusError::ConnectionLost
958 | MbusError::IoError
959 ) || !self.transport.is_connected();
960
961 if is_connection_loss {
962 client_log_debug!(
963 "connection loss detected during poll: error={:?}, pending_requests={}",
964 recv_error,
965 self.expected_responses.len()
966 );
967 self.fail_all_pending_requests(MbusError::ConnectionLost);
968 let _ = self.transport.disconnect();
969 self.rxed_frame.clear();
970 } else {
971 client_log_trace!("non-fatal recv status during poll: {:?}", recv_error);
972 }
973 }
974 }
975
976 self.handle_timeouts();
978 }
979
980 fn fail_all_pending_requests(&mut self, error: MbusError) {
981 let pending_count = self.expected_responses.len();
982 client_log_debug!(
983 "failing {} pending request(s) with error {:?}",
984 pending_count,
985 error
986 );
987 while let Some(response) = self.expected_responses.pop() {
988 self.app.request_failed(
989 response.txn_id,
990 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
991 error,
992 );
993 }
994 self.next_timeout_check = None;
995 }
996
997 fn handle_timeouts(&mut self) {
1012 if self.expected_responses.is_empty() {
1013 self.next_timeout_check = None;
1014 return;
1015 }
1016
1017 let current_millis = self.app.current_millis();
1018
1019 if let Some(check_at) = self.next_timeout_check
1021 && current_millis < check_at
1022 {
1023 client_log_trace!(
1024 "skipping timeout scan until {}, current_millis={}",
1025 check_at,
1026 current_millis
1027 );
1028 return;
1029 }
1030
1031 let response_timeout_ms = self.response_timeout_ms();
1032 let retry_backoff = self.config.retry_backoff_strategy();
1033 let retry_jitter = self.config.retry_jitter_strategy();
1034 let retry_random_fn = self.config.retry_random_fn();
1035 let expected_responses = &mut self.expected_responses;
1036 let mut i = 0;
1037 let mut new_next_check = u64::MAX;
1038
1039 while i < expected_responses.len() {
1040 let expected_response = &mut expected_responses[i];
1041 if let Some(retry_at) = expected_response.next_retry_timestamp {
1043 if current_millis >= retry_at {
1044 client_log_debug!(
1045 "retry due now: txn_id={}, unit_id_or_slave_addr={}, retry_attempt_index={}, retries_left={}",
1046 expected_response.txn_id,
1047 expected_response.unit_id_or_slave_addr,
1048 expected_response.retry_attempt_index.saturating_add(1),
1049 expected_response.retries_left
1050 );
1051 if let Err(_e) = self.transport.send(&expected_response.original_adu) {
1052 let response = expected_responses.swap_remove(i);
1055 client_log_debug!(
1056 "retry send failed: txn_id={}, unit_id_or_slave_addr={}; dropping request",
1057 response.txn_id,
1058 response.unit_id_or_slave_addr
1059 );
1060 self.app.request_failed(
1061 response.txn_id,
1062 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1063 MbusError::SendFailed,
1064 );
1065 continue;
1066 }
1067
1068 expected_response.retries_left =
1069 expected_response.retries_left.saturating_sub(1);
1070 expected_response.retry_attempt_index =
1071 expected_response.retry_attempt_index.saturating_add(1);
1072 expected_response.sent_timestamp = current_millis;
1073 expected_response.next_retry_timestamp = None;
1074
1075 let expires_at = current_millis.saturating_add(response_timeout_ms);
1076 if expires_at < new_next_check {
1077 new_next_check = expires_at;
1078 }
1079 i += 1;
1080 continue;
1081 }
1082
1083 if retry_at < new_next_check {
1084 new_next_check = retry_at;
1085 }
1086 i += 1;
1087 continue;
1088 }
1089
1090 let expires_at = expected_response
1092 .sent_timestamp
1093 .saturating_add(response_timeout_ms);
1094
1095 if current_millis > expires_at {
1096 if expected_response.retries_left == 0 {
1097 let response = expected_responses.swap_remove(i);
1100 client_log_debug!(
1101 "request exhausted retries: txn_id={}, unit_id_or_slave_addr={}",
1102 response.txn_id,
1103 response.unit_id_or_slave_addr
1104 );
1105 self.app.request_failed(
1106 response.txn_id,
1107 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1108 MbusError::NoRetriesLeft,
1109 );
1110 continue;
1111 }
1112
1113 let next_attempt = expected_response.retry_attempt_index.saturating_add(1);
1114 let base_delay_ms = retry_backoff.delay_ms_for_retry(next_attempt);
1115 let retry_delay_ms = retry_jitter.apply(base_delay_ms, retry_random_fn) as u64;
1116 let retry_at = current_millis.saturating_add(retry_delay_ms);
1117 expected_response.next_retry_timestamp = Some(retry_at);
1118 client_log_debug!(
1119 "scheduling retry: txn_id={}, unit_id_or_slave_addr={}, next_attempt={}, delay_ms={}, retry_at={}",
1120 expected_response.txn_id,
1121 expected_response.unit_id_or_slave_addr,
1122 next_attempt,
1123 retry_delay_ms,
1124 retry_at
1125 );
1126
1127 if retry_delay_ms == 0 {
1130 client_log_trace!(
1131 "retry delay is zero; retry will be processed in the same poll cycle for txn_id={}",
1132 expected_response.txn_id
1133 );
1134 continue;
1135 }
1136
1137 if retry_at < new_next_check {
1138 new_next_check = retry_at;
1139 }
1140 i += 1;
1141 continue;
1142 }
1143
1144 if expires_at < new_next_check {
1145 new_next_check = expires_at;
1146 }
1147 i += 1;
1148 }
1149
1150 if new_next_check != u64::MAX {
1151 self.next_timeout_check = Some(new_next_check);
1152 } else {
1153 self.next_timeout_check = None;
1154 }
1155 }
1156
1157 fn add_an_expectation(
1158 &mut self,
1159 txn_id: u16,
1160 unit_id_slave_addr: UnitIdOrSlaveAddr,
1161 frame: &heapless::Vec<u8, MAX_ADU_FRAME_LEN>,
1162 operation_meta: OperationMeta,
1163 handler: ResponseHandler<TRANSPORT, APP, N>,
1164 ) -> Result<(), MbusError> {
1165 client_log_trace!(
1166 "queueing expected response: txn_id={}, unit_id_or_slave_addr={}, queue_len_before={}",
1167 txn_id,
1168 unit_id_slave_addr.get(),
1169 self.expected_responses.len()
1170 );
1171 self.expected_responses
1172 .push(ExpectedResponse {
1173 txn_id,
1174 unit_id_or_slave_addr: unit_id_slave_addr.get(),
1175 original_adu: frame.clone(),
1176 sent_timestamp: self.app.current_millis(),
1177 retries_left: self.retry_attempts(),
1178 retry_attempt_index: 0,
1179 next_retry_timestamp: None,
1180 handler,
1181 operation_meta,
1182 })
1183 .map_err(|_| MbusError::TooManyRequests)?;
1184 Ok(())
1185 }
1186}
1187
1188impl<TRANSPORT: Transport, APP: ClientCommon, const N: usize> ClientServices<TRANSPORT, APP, N> {
1190 pub fn new(
1192 mut transport: TRANSPORT,
1193 app: APP,
1194 config: ModbusConfig,
1195 ) -> Result<Self, MbusError> {
1196 let transport_type = transport.transport_type();
1197 if matches!(
1198 transport_type,
1199 TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1200 ) && N != 1
1201 {
1202 return Err(MbusError::InvalidNumOfExpectedRsps);
1203 }
1204
1205 transport
1206 .connect(&config)
1207 .map_err(|_e| MbusError::ConnectionFailed)?;
1208
1209 client_log_debug!(
1210 "client created with transport_type={:?}, queue_capacity={}",
1211 transport_type,
1212 N
1213 );
1214
1215 Ok(Self {
1216 app,
1217 transport,
1218 rxed_frame: Vec::new(),
1219 config,
1220 expected_responses: Vec::new(),
1221 next_timeout_check: None,
1222 })
1223 }
1224
1225 pub fn app(&self) -> &APP {
1230 &self.app
1231 }
1232
1233 pub fn is_connected(&self) -> bool {
1235 self.transport.is_connected()
1236 }
1237
1238 pub fn reconnect(&mut self) -> Result<(), MbusError>
1249 where
1250 TRANSPORT::Error: Into<MbusError>,
1251 {
1252 client_log_debug!(
1253 "reconnect requested; pending_requests={}",
1254 self.expected_responses.len()
1255 );
1256 self.fail_all_pending_requests(MbusError::ConnectionLost);
1257 self.rxed_frame.clear();
1258 self.next_timeout_check = None;
1259
1260 let _ = self.transport.disconnect();
1261 self.transport.connect(&self.config).map_err(|e| e.into())
1262 }
1263
1264 pub fn new_serial(
1273 mut transport: TRANSPORT,
1274 app: APP,
1275 config: ModbusSerialConfig,
1276 ) -> Result<Self, MbusError>
1277 where
1278 [(); N]: SerialQueueSizeOne,
1279 {
1280 let transport_type = transport.transport_type();
1281 if !matches!(
1282 transport_type,
1283 TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1284 ) {
1285 return Err(MbusError::InvalidTransport);
1286 }
1287
1288 let config = ModbusConfig::Serial(config);
1289 transport
1290 .connect(&config)
1291 .map_err(|_e| MbusError::ConnectionFailed)?;
1292
1293 client_log_debug!("serial client created with queue_capacity={}", N);
1294
1295 Ok(Self {
1296 app,
1297 transport,
1298 rxed_frame: Vec::new(),
1299 config,
1300 expected_responses: Vec::new(),
1301 next_timeout_check: None,
1302 })
1303 }
1304
1305 fn response_timeout_ms(&self) -> u64 {
1307 match &self.config {
1308 ModbusConfig::Tcp(config) => config.response_timeout_ms as u64,
1309 ModbusConfig::Serial(config) => config.response_timeout_ms as u64,
1310 }
1311 }
1312
1313 fn retry_attempts(&self) -> u8 {
1315 match &self.config {
1316 ModbusConfig::Tcp(config) => config.retry_attempts,
1317 ModbusConfig::Serial(config) => config.retry_attempts,
1318 }
1319 }
1320
1321 fn ingest_frame(&mut self) -> Result<usize, MbusError> {
1323 let frame = self.rxed_frame.as_slice();
1324 let transport_type = self.transport.transport_type();
1325
1326 client_log_trace!(
1327 "attempting frame ingest: transport_type={:?}, buffer_len={}",
1328 transport_type,
1329 frame.len()
1330 );
1331
1332 let expected_length = match derive_length_from_bytes(frame, transport_type) {
1333 Some(len) => len,
1334 None => return Err(MbusError::BufferTooSmall),
1335 };
1336
1337 client_log_trace!("derived expected frame length={}", expected_length);
1338
1339 if expected_length > MAX_ADU_FRAME_LEN {
1340 client_log_debug!(
1341 "derived frame length {} exceeds MAX_ADU_FRAME_LEN {}",
1342 expected_length,
1343 MAX_ADU_FRAME_LEN
1344 );
1345 return Err(MbusError::BasicParseError);
1346 }
1347
1348 if self.rxed_frame.len() < expected_length {
1349 return Err(MbusError::BufferTooSmall);
1350 }
1351
1352 let message = match common::decompile_adu_frame(&frame[..expected_length], transport_type) {
1353 Ok(value) => value,
1354 Err(err) => {
1355 client_log_debug!(
1356 "decompile_adu_frame failed for {} bytes: {:?}",
1357 expected_length,
1358 err
1359 );
1360 return Err(err); }
1362 };
1363 use mbus_core::data_unit::common::AdditionalAddress;
1364 use mbus_core::transport::TransportType::*;
1365 let message = match self.transport.transport_type() {
1366 StdTcp | CustomTcp => {
1367 let mbap_header = match message.additional_address() {
1368 AdditionalAddress::MbapHeader(header) => header,
1369 _ => return Ok(expected_length),
1370 };
1371 let additional_addr = AdditionalAddress::MbapHeader(*mbap_header);
1372 ModbusMessage::new(additional_addr, message.pdu)
1373 }
1374 StdSerial(_) | CustomSerial(_) => {
1375 let slave_addr = match message.additional_address() {
1376 AdditionalAddress::SlaveAddress(addr) => addr.address(),
1377 _ => return Ok(expected_length),
1378 };
1379
1380 let additional_address =
1381 AdditionalAddress::SlaveAddress(SlaveAddress::new(slave_addr)?);
1382 ModbusMessage::new(additional_address, message.pdu)
1383 }
1384 };
1385
1386 self.dispatch_response(&message);
1387 client_log_trace!("frame dispatch complete for {} bytes", expected_length);
1388
1389 Ok(expected_length)
1390 }
1391}
1392
1393#[cfg(test)]
1394mod tests {
1395 use super::*;
1396 use crate::app::CoilResponse;
1397 use crate::app::DiagnosticsResponse;
1398 use crate::app::DiscreteInputResponse;
1399 use crate::app::FifoQueueResponse;
1400 use crate::app::FileRecordResponse;
1401 use crate::app::RegisterResponse;
1402 use crate::services::coil::Coils;
1403
1404 use crate::services::diagnostic::ConformityLevel;
1405 use crate::services::diagnostic::DeviceIdentificationResponse;
1406 use crate::services::diagnostic::ObjectId;
1407 use crate::services::discrete_input::DiscreteInputs;
1408 use crate::services::fifo_queue::FifoQueue;
1409 use crate::services::file_record::MAX_SUB_REQUESTS_PER_PDU;
1410 use crate::services::file_record::SubRequest;
1411 use crate::services::file_record::SubRequestParams;
1412 use crate::services::register::Registers;
1413 use core::cell::RefCell; use core::str::FromStr;
1415 use heapless::Deque;
1416 use heapless::Vec;
1417 use mbus_core::errors::MbusError;
1418 use mbus_core::function_codes::public::DiagnosticSubFunction;
1419 use mbus_core::transport::TransportType;
1420 use mbus_core::transport::{
1421 BackoffStrategy, BaudRate, JitterStrategy, ModbusConfig, ModbusSerialConfig,
1422 ModbusTcpConfig, Parity, SerialMode,
1423 };
1424
1425 const MOCK_DEQUE_CAPACITY: usize = 10; fn rand_zero() -> u32 {
1428 0
1429 }
1430
1431 fn rand_upper_percent_20() -> u32 {
1432 40
1433 }
1434
1435 #[derive(Debug, Default)]
1437 struct MockTransport {
1438 pub sent_frames: RefCell<Deque<Vec<u8, MAX_ADU_FRAME_LEN>, MOCK_DEQUE_CAPACITY>>, pub recv_frames: RefCell<Deque<Vec<u8, MAX_ADU_FRAME_LEN>, MOCK_DEQUE_CAPACITY>>, pub recv_error: RefCell<Option<MbusError>>,
1441 pub connect_should_fail: bool,
1442 pub send_should_fail: bool,
1443 pub is_connected_flag: RefCell<bool>,
1444 pub transport_type: Option<TransportType>,
1445 }
1446
1447 impl Transport for MockTransport {
1448 type Error = MbusError;
1449
1450 fn connect(&mut self, _config: &ModbusConfig) -> Result<(), Self::Error> {
1451 if self.connect_should_fail {
1452 return Err(MbusError::ConnectionFailed);
1453 }
1454 *self.is_connected_flag.borrow_mut() = true;
1455 Ok(())
1456 }
1457
1458 fn disconnect(&mut self) -> Result<(), Self::Error> {
1459 *self.is_connected_flag.borrow_mut() = false;
1460 Ok(())
1461 }
1462
1463 fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
1464 if self.send_should_fail {
1465 return Err(MbusError::SendFailed);
1466 }
1467 let mut vec_adu = Vec::new();
1468 vec_adu
1469 .extend_from_slice(adu)
1470 .map_err(|_| MbusError::BufferLenMissmatch)?;
1471 self.sent_frames
1472 .borrow_mut()
1473 .push_back(vec_adu)
1474 .map_err(|_| MbusError::BufferLenMissmatch)?;
1475 Ok(())
1476 }
1477
1478 fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
1479 if let Some(err) = self.recv_error.borrow_mut().take() {
1480 return Err(err);
1481 }
1482 self.recv_frames
1483 .borrow_mut()
1484 .pop_front()
1485 .ok_or(MbusError::Timeout)
1486 }
1487
1488 fn is_connected(&self) -> bool {
1489 *self.is_connected_flag.borrow()
1490 }
1491
1492 fn transport_type(&self) -> TransportType {
1493 self.transport_type.unwrap_or(TransportType::StdTcp)
1494 }
1495 }
1496
1497 #[derive(Debug, Default)]
1499 struct MockApp {
1500 pub received_coil_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr, Coils), 10>>, pub received_write_single_coil_responses:
1502 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, bool), 10>>,
1503 pub received_write_multiple_coils_responses:
1504 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1505 pub received_discrete_input_responses:
1506 RefCell<Vec<(u16, UnitIdOrSlaveAddr, DiscreteInputs, u16), 10>>,
1507 pub received_holding_register_responses:
1508 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1509 pub received_input_register_responses:
1510 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1511 pub received_write_single_register_responses:
1512 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1513 pub received_write_multiple_register_responses:
1514 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1515 pub received_read_write_multiple_registers_responses:
1516 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers), 10>>,
1517 pub received_mask_write_register_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1518 pub received_read_fifo_queue_responses:
1519 RefCell<Vec<(u16, UnitIdOrSlaveAddr, FifoQueue), 10>>,
1520 pub received_read_file_record_responses: RefCell<
1521 Vec<
1522 (
1523 u16,
1524 UnitIdOrSlaveAddr,
1525 Vec<SubRequestParams, MAX_SUB_REQUESTS_PER_PDU>,
1526 ),
1527 10,
1528 >,
1529 >,
1530 pub received_write_file_record_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1531 pub received_read_device_id_responses:
1532 RefCell<Vec<(u16, UnitIdOrSlaveAddr, DeviceIdentificationResponse), 10>>,
1533 pub failed_requests: RefCell<Vec<(u16, UnitIdOrSlaveAddr, MbusError), 10>>,
1534
1535 pub current_time: RefCell<u64>, }
1537
1538 impl CoilResponse for MockApp {
1539 fn read_coils_response(
1540 &mut self,
1541 txn_id: u16,
1542 unit_id_slave_addr: UnitIdOrSlaveAddr,
1543 coils: &Coils,
1544 ) {
1545 self.received_coil_responses
1546 .borrow_mut()
1547 .push((txn_id, unit_id_slave_addr, coils.clone()))
1548 .unwrap();
1549 }
1550
1551 fn read_single_coil_response(
1552 &mut self,
1553 txn_id: u16,
1554 unit_id_slave_addr: UnitIdOrSlaveAddr,
1555 address: u16,
1556 value: bool,
1557 ) {
1558 let mut values_vec = [0x00, 1];
1560 values_vec[0] = if value { 0x01 } else { 0x00 }; let coils = Coils::new(address, 1)
1562 .unwrap()
1563 .with_values(&values_vec, 1)
1564 .unwrap();
1565 self.received_coil_responses
1566 .borrow_mut()
1567 .push((txn_id, unit_id_slave_addr, coils))
1568 .unwrap();
1569 }
1570
1571 fn write_single_coil_response(
1572 &mut self,
1573 txn_id: u16,
1574 unit_id_slave_addr: UnitIdOrSlaveAddr,
1575 address: u16,
1576 value: bool,
1577 ) {
1578 self.received_write_single_coil_responses
1579 .borrow_mut()
1580 .push((txn_id, unit_id_slave_addr, address, value))
1581 .unwrap();
1582 }
1583
1584 fn write_multiple_coils_response(
1585 &mut self,
1586 txn_id: u16,
1587 unit_id_slave_addr: UnitIdOrSlaveAddr,
1588 address: u16,
1589 quantity: u16,
1590 ) {
1591 self.received_write_multiple_coils_responses
1592 .borrow_mut()
1593 .push((txn_id, unit_id_slave_addr, address, quantity))
1594 .unwrap();
1595 }
1596 }
1597
1598 impl DiscreteInputResponse for MockApp {
1599 fn read_multiple_discrete_inputs_response(
1600 &mut self,
1601 txn_id: u16,
1602 unit_id_slave_addr: UnitIdOrSlaveAddr,
1603 inputs: &DiscreteInputs,
1604 ) {
1605 self.received_discrete_input_responses
1606 .borrow_mut()
1607 .push((
1608 txn_id,
1609 unit_id_slave_addr,
1610 inputs.clone(),
1611 inputs.quantity(),
1612 ))
1613 .unwrap();
1614 }
1615
1616 fn read_single_discrete_input_response(
1617 &mut self,
1618 txn_id: u16,
1619 unit_id_slave_addr: UnitIdOrSlaveAddr,
1620 address: u16,
1621 value: bool,
1622 ) {
1623 let mut values = [0u8; mbus_core::models::discrete_input::MAX_DISCRETE_INPUT_BYTES];
1624 values[0] = if value { 0x01 } else { 0x00 };
1625 let inputs = DiscreteInputs::new(address, 1)
1626 .unwrap()
1627 .with_values(&values, 1)
1628 .unwrap();
1629 self.received_discrete_input_responses
1630 .borrow_mut()
1631 .push((txn_id, unit_id_slave_addr, inputs, 1))
1632 .unwrap();
1633 }
1634 }
1635
1636 impl RequestErrorNotifier for MockApp {
1637 fn request_failed(
1638 &mut self,
1639 txn_id: u16,
1640 unit_id_slave_addr: UnitIdOrSlaveAddr,
1641 error: MbusError,
1642 ) {
1643 self.failed_requests
1644 .borrow_mut()
1645 .push((txn_id, unit_id_slave_addr, error))
1646 .unwrap();
1647 }
1648 }
1649
1650 impl RegisterResponse for MockApp {
1651 fn read_multiple_holding_registers_response(
1652 &mut self,
1653 txn_id: u16,
1654 unit_id_slave_addr: UnitIdOrSlaveAddr,
1655 registers: &Registers,
1656 ) {
1657 let quantity = registers.quantity();
1658 self.received_holding_register_responses
1659 .borrow_mut()
1660 .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
1661 .unwrap();
1662 }
1663
1664 fn read_single_input_register_response(
1665 &mut self,
1666 txn_id: u16,
1667 unit_id_slave_addr: UnitIdOrSlaveAddr,
1668 address: u16,
1669 value: u16,
1670 ) {
1671 let values = [value];
1673 let registers = Registers::new(address, 1)
1674 .unwrap()
1675 .with_values(&values, 1)
1676 .unwrap();
1677 self.received_input_register_responses
1678 .borrow_mut()
1679 .push((txn_id, unit_id_slave_addr, registers, 1))
1680 .unwrap();
1681 }
1682
1683 fn read_single_holding_register_response(
1684 &mut self,
1685 txn_id: u16,
1686 unit_id_slave_addr: UnitIdOrSlaveAddr,
1687 address: u16,
1688 value: u16,
1689 ) {
1690 let data = [value];
1692 let registers = Registers::new(address, 1)
1694 .unwrap()
1695 .with_values(&data, 1)
1696 .unwrap();
1697
1698 self.received_holding_register_responses
1699 .borrow_mut()
1700 .push((txn_id, unit_id_slave_addr, registers, 1))
1701 .unwrap();
1702 }
1703
1704 fn read_multiple_input_registers_response(
1705 &mut self,
1706 txn_id: u16,
1707 unit_id_slave_addr: UnitIdOrSlaveAddr,
1708 registers: &Registers,
1709 ) {
1710 let quantity = registers.quantity();
1711 self.received_input_register_responses
1712 .borrow_mut()
1713 .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
1714 .unwrap();
1715 }
1716
1717 fn write_single_register_response(
1718 &mut self,
1719 txn_id: u16,
1720 unit_id_slave_addr: UnitIdOrSlaveAddr,
1721 address: u16,
1722 value: u16,
1723 ) {
1724 self.received_write_single_register_responses
1725 .borrow_mut()
1726 .push((txn_id, unit_id_slave_addr, address, value))
1727 .unwrap();
1728 }
1729
1730 fn write_multiple_registers_response(
1731 &mut self,
1732 txn_id: u16,
1733 unit_id_slave_addr: UnitIdOrSlaveAddr,
1734 address: u16,
1735 quantity: u16,
1736 ) {
1737 self.received_write_multiple_register_responses
1738 .borrow_mut()
1739 .push((txn_id, unit_id_slave_addr, address, quantity))
1740 .unwrap();
1741 }
1742
1743 fn read_write_multiple_registers_response(
1744 &mut self,
1745 txn_id: u16,
1746 unit_id_slave_addr: UnitIdOrSlaveAddr,
1747 registers: &Registers,
1748 ) {
1749 self.received_read_write_multiple_registers_responses
1750 .borrow_mut()
1751 .push((txn_id, unit_id_slave_addr, registers.clone()))
1752 .unwrap();
1753 }
1754
1755 fn mask_write_register_response(
1756 &mut self,
1757 txn_id: u16,
1758 unit_id_slave_addr: UnitIdOrSlaveAddr,
1759 ) {
1760 self.received_mask_write_register_responses
1761 .borrow_mut()
1762 .push((txn_id, unit_id_slave_addr))
1763 .unwrap();
1764 }
1765
1766 fn read_single_register_response(
1767 &mut self,
1768 txn_id: u16,
1769 unit_id_slave_addr: UnitIdOrSlaveAddr,
1770 address: u16,
1771 value: u16,
1772 ) {
1773 let data = [value];
1775 let registers = Registers::new(address, 1)
1777 .unwrap()
1778 .with_values(&data, 1)
1779 .unwrap();
1780
1781 self.received_holding_register_responses
1782 .borrow_mut()
1783 .push((txn_id, unit_id_slave_addr, registers, 1))
1784 .unwrap();
1785 }
1786 }
1787
1788 impl FifoQueueResponse for MockApp {
1789 fn read_fifo_queue_response(
1790 &mut self,
1791 txn_id: u16,
1792 unit_id_slave_addr: UnitIdOrSlaveAddr,
1793 fifo_queue: &FifoQueue,
1794 ) {
1795 self.received_read_fifo_queue_responses
1796 .borrow_mut()
1797 .push((txn_id, unit_id_slave_addr, fifo_queue.clone()))
1798 .unwrap();
1799 }
1800 }
1801
1802 impl FileRecordResponse for MockApp {
1803 fn read_file_record_response(
1804 &mut self,
1805 txn_id: u16,
1806 unit_id_slave_addr: UnitIdOrSlaveAddr,
1807 data: &[SubRequestParams],
1808 ) {
1809 let mut vec = Vec::new();
1810 vec.extend_from_slice(data).unwrap();
1811 self.received_read_file_record_responses
1812 .borrow_mut()
1813 .push((txn_id, unit_id_slave_addr, vec))
1814 .unwrap();
1815 }
1816 fn write_file_record_response(
1817 &mut self,
1818 txn_id: u16,
1819 unit_id_slave_addr: UnitIdOrSlaveAddr,
1820 ) {
1821 self.received_write_file_record_responses
1822 .borrow_mut()
1823 .push((txn_id, unit_id_slave_addr))
1824 .unwrap();
1825 }
1826 }
1827
1828 impl DiagnosticsResponse for MockApp {
1829 fn read_device_identification_response(
1830 &mut self,
1831 txn_id: u16,
1832 unit_id_slave_addr: UnitIdOrSlaveAddr,
1833 response: &DeviceIdentificationResponse,
1834 ) {
1835 self.received_read_device_id_responses
1836 .borrow_mut()
1837 .push((txn_id, unit_id_slave_addr, response.clone()))
1838 .unwrap();
1839 }
1840
1841 fn encapsulated_interface_transport_response(
1842 &mut self,
1843 _: u16,
1844 _: UnitIdOrSlaveAddr,
1845 _: EncapsulatedInterfaceType,
1846 _: &[u8],
1847 ) {
1848 }
1849
1850 fn diagnostics_response(
1851 &mut self,
1852 _: u16,
1853 _: UnitIdOrSlaveAddr,
1854 _: DiagnosticSubFunction,
1855 _: &[u16],
1856 ) {
1857 }
1858
1859 fn get_comm_event_counter_response(
1860 &mut self,
1861 _: u16,
1862 _: UnitIdOrSlaveAddr,
1863 _: u16,
1864 _: u16,
1865 ) {
1866 }
1867
1868 fn get_comm_event_log_response(
1869 &mut self,
1870 _: u16,
1871 _: UnitIdOrSlaveAddr,
1872 _: u16,
1873 _: u16,
1874 _: u16,
1875 _: &[u8],
1876 ) {
1877 }
1878
1879 fn read_exception_status_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: u8) {}
1880
1881 fn report_server_id_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: &[u8]) {}
1882 }
1883
1884 impl TimeKeeper for MockApp {
1885 fn current_millis(&self) -> u64 {
1886 *self.current_time.borrow()
1887 }
1888 }
1889
1890 #[test]
1894 fn test_client_services_new_success() {
1895 let transport = MockTransport::default();
1896 let app = MockApp::default();
1897 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1898
1899 let client_services =
1900 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
1901 assert!(client_services.is_ok());
1902 assert!(client_services.unwrap().transport.is_connected());
1903 }
1904
1905 #[test]
1907 fn test_client_services_new_connection_failure() {
1908 let mut transport = MockTransport::default();
1909 transport.connect_should_fail = true;
1910 let app = MockApp::default();
1911 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1912
1913 let client_services =
1914 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
1915 assert!(client_services.is_err());
1916 assert_eq!(client_services.unwrap_err(), MbusError::ConnectionFailed);
1917 }
1918
1919 #[test]
1920 fn test_client_services_new_serial_success() {
1921 let transport = MockTransport {
1922 transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
1923 ..Default::default()
1924 };
1925 let app = MockApp::default();
1926 let serial_config = ModbusSerialConfig {
1927 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
1928 mode: SerialMode::Rtu,
1929 baud_rate: BaudRate::Baud19200,
1930 data_bits: mbus_core::transport::DataBits::Eight,
1931 stop_bits: 1,
1932 parity: Parity::Even,
1933 response_timeout_ms: 1000,
1934 retry_attempts: 1,
1935 retry_backoff_strategy: BackoffStrategy::Immediate,
1936 retry_jitter_strategy: JitterStrategy::None,
1937 retry_random_fn: None,
1938 };
1939
1940 let client_services =
1941 ClientServices::<MockTransport, MockApp, 1>::new_serial(transport, app, serial_config);
1942 assert!(client_services.is_ok());
1943 }
1944
1945 #[test]
1946 fn test_reconnect_success_flushes_pending_requests() {
1947 let transport = MockTransport::default();
1948 let app = MockApp::default();
1949 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1950 let mut client_services =
1951 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
1952
1953 let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
1954 client_services.read_single_coil(10, unit_id, 0).unwrap();
1955 assert_eq!(client_services.expected_responses.len(), 1);
1956
1957 let reconnect_result = client_services.reconnect();
1958 assert!(reconnect_result.is_ok());
1959 assert!(client_services.is_connected());
1960 assert!(client_services.expected_responses.is_empty());
1961
1962 let failed_requests = client_services.app().failed_requests.borrow();
1963 assert_eq!(failed_requests.len(), 1);
1964 assert_eq!(failed_requests[0].0, 10);
1965 assert_eq!(failed_requests[0].2, MbusError::ConnectionLost);
1966 }
1967
1968 #[test]
1969 fn test_reconnect_failure_propagates_connect_error() {
1970 let transport = MockTransport::default();
1971 let app = MockApp::default();
1972 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1973 let mut client_services =
1974 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
1975
1976 client_services.transport.connect_should_fail = true;
1977 let reconnect_result = client_services.reconnect();
1978
1979 assert!(reconnect_result.is_err());
1980 assert_eq!(reconnect_result.unwrap_err(), MbusError::ConnectionFailed);
1981 assert!(!client_services.is_connected());
1982 }
1983
1984 #[test]
1986 fn test_read_multiple_coils_sends_valid_adu() {
1987 let transport = MockTransport::default();
1988 let app = MockApp::default();
1989 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1990 let mut client_services =
1991 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
1992
1993 let txn_id = 0x0001;
1994 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
1995 let address = 0x0000;
1996 let quantity = 8;
1997 client_services
1998 .read_multiple_coils(txn_id, unit_id, address, quantity)
1999 .unwrap();
2000
2001 let sent_frames = client_services.transport.sent_frames.borrow();
2002 assert_eq!(sent_frames.len(), 1);
2003 let sent_adu = sent_frames.front().unwrap();
2004
2005 #[rustfmt::skip]
2007 let expected_adu: [u8; 12] = [
2008 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, ];
2016 assert_eq!(sent_adu.as_slice(), &expected_adu);
2017 }
2018
2019 #[test]
2021 fn test_read_multiple_coils_invalid_quantity() {
2022 let transport = MockTransport::default();
2023 let app = MockApp::default();
2024 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2025 let mut client_services =
2026 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2027
2028 let txn_id = 0x0001;
2029 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2030 let address = 0x0000;
2031 let quantity = 0; let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); assert_eq!(result.unwrap_err(), MbusError::InvalidQuantity);
2035 }
2036
2037 #[test]
2039 fn test_read_multiple_coils_send_failure() {
2040 let mut transport = MockTransport::default();
2041 transport.send_should_fail = true;
2042 let app = MockApp::default();
2043 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2044 let mut client_services =
2045 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2046
2047 let txn_id = 0x0001;
2048 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2049 let address = 0x0000;
2050 let quantity = 8;
2051
2052 let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); assert_eq!(result.unwrap_err(), MbusError::SendFailed);
2054 }
2055
2056 #[test]
2058 fn test_ingest_frame_wrong_fc() {
2059 let transport = MockTransport::default();
2060 let app = MockApp::default();
2061 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2062 let mut client_services =
2063 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2064
2065 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x03, 0x01, 0xB3];
2067
2068 client_services
2069 .transport
2070 .recv_frames
2071 .borrow_mut()
2072 .push_back(Vec::from_slice(&response_adu).unwrap())
2073 .unwrap();
2074 client_services.poll();
2075
2076 let received_responses = client_services.app().received_coil_responses.borrow();
2077 assert!(received_responses.is_empty());
2078 }
2079
2080 #[test]
2082 fn test_ingest_frame_malformed_adu() {
2083 let transport = MockTransport::default();
2084 let app = MockApp::default();
2085 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2086 let mut client_services =
2087 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2088
2089 let malformed_adu = [0x01, 0x02, 0x03];
2091
2092 client_services
2093 .transport
2094 .recv_frames
2095 .borrow_mut()
2096 .push_back(Vec::from_slice(&malformed_adu).unwrap())
2097 .unwrap();
2098 client_services.poll();
2099
2100 let received_responses = client_services.app().received_coil_responses.borrow();
2101 assert!(received_responses.is_empty());
2102 }
2103
2104 #[test]
2106 fn test_ingest_frame_unknown_txn_id() {
2107 let transport = MockTransport::default();
2108 let app = MockApp::default();
2109 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2110 let mut client_services =
2111 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2112
2113 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2115
2116 client_services
2117 .transport
2118 .recv_frames
2119 .borrow_mut()
2120 .push_back(Vec::from_slice(&response_adu).unwrap())
2121 .unwrap();
2122 client_services.poll();
2123
2124 let received_responses = client_services.app().received_coil_responses.borrow();
2125 assert!(received_responses.is_empty());
2126 }
2127
2128 #[test]
2130 fn test_ingest_frame_pdu_parse_failure() {
2131 let transport = MockTransport::default();
2132 let app = MockApp::default();
2133 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2134 let mut client_services =
2135 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2136
2137 let txn_id = 0x0001;
2138 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2139 let address = 0x0000;
2140 let quantity = 8;
2141 client_services
2142 .read_multiple_coils(txn_id, unit_id, address, quantity) .unwrap();
2144
2145 let response_adu = [
2149 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x01, 0xB3, 0x00,
2150 ]; client_services
2153 .transport
2154 .recv_frames
2155 .borrow_mut()
2156 .push_back(Vec::from_slice(&response_adu).unwrap())
2157 .unwrap();
2158 client_services.poll();
2159
2160 let received_responses = client_services.app().received_coil_responses.borrow();
2161 assert!(received_responses.is_empty());
2162 assert!(client_services.expected_responses.is_empty());
2164 }
2165
2166 #[test]
2168 fn test_client_services_read_single_coil_e2e_success() {
2169 let transport = MockTransport::default();
2170 let app = MockApp::default();
2171 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2172 let mut client_services =
2173 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2174
2175 let txn_id = 0x0002;
2176 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2177 let address = 0x0005;
2178
2179 client_services .read_single_coil(txn_id, unit_id, address)
2182 .unwrap();
2183
2184 let sent_adu = client_services
2186 .transport
2187 .sent_frames
2188 .borrow_mut()
2189 .pop_front()
2190 .unwrap();
2191 #[rustfmt::skip]
2193 let expected_adu: [u8; 12] = [
2194 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x05, 0x00, 0x01, ];
2202 assert_eq!(sent_adu.as_slice(), &expected_adu);
2203
2204 let response_adu = [0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x01];
2208
2209 client_services
2211 .transport
2212 .recv_frames
2213 .borrow_mut()
2214 .push_back(Vec::from_slice(&response_adu).unwrap())
2215 .unwrap();
2216 client_services.poll();
2217
2218 let received_responses = client_services.app().received_coil_responses.borrow();
2220 assert_eq!(received_responses.len(), 1);
2221
2222 let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2223 let rcv_quantity = rcv_coils.quantity();
2224 assert_eq!(*rcv_txn_id, txn_id);
2225 assert_eq!(*rcv_unit_id, unit_id);
2226 assert_eq!(rcv_coils.from_address(), address);
2227 assert_eq!(rcv_coils.quantity(), 1); assert_eq!(&rcv_coils.values()[..1], &[0x01]); assert_eq!(rcv_quantity, 1);
2230
2231 assert!(client_services.expected_responses.is_empty());
2233 }
2234
2235 #[test]
2237 fn test_read_single_coil_request_sends_valid_adu() {
2238 let transport = MockTransport::default();
2239 let app = MockApp::default();
2240 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2241 let mut client_services =
2242 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2243
2244 let txn_id = 0x0002;
2245 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2246 let address = 0x0005;
2247
2248 client_services
2249 .read_single_coil(txn_id, unit_id, address) .unwrap();
2251
2252 let sent_frames = client_services.transport.sent_frames.borrow();
2253 assert_eq!(sent_frames.len(), 1);
2254 let sent_adu = sent_frames.front().unwrap();
2255
2256 #[rustfmt::skip]
2258 let expected_adu: [u8; 12] = [
2259 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x05, 0x00, 0x01, ];
2267 assert_eq!(sent_adu.as_slice(), &expected_adu);
2268
2269 assert_eq!(client_services.expected_responses.len(), 1); let single_read = client_services.expected_responses[0]
2272 .operation_meta
2273 .is_single();
2274 assert!(single_read);
2275 }
2276
2277 #[test]
2279 fn test_write_single_coil_sends_valid_adu() {
2280 let transport = MockTransport::default();
2281 let app = MockApp::default();
2282 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2283 let mut client_services =
2284 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2285
2286 let txn_id = 0x0003;
2287 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2288 let address = 0x000A;
2289 let value = true;
2290
2291 client_services
2292 .write_single_coil(txn_id, unit_id, address, value) .unwrap();
2294
2295 let sent_frames = client_services.transport.sent_frames.borrow();
2296 assert_eq!(sent_frames.len(), 1);
2297 let sent_adu = sent_frames.front().unwrap();
2298
2299 #[rustfmt::skip]
2301 let expected_adu: [u8; 12] = [
2302 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, ];
2310 assert_eq!(sent_adu.as_slice(), &expected_adu);
2311
2312 assert_eq!(client_services.expected_responses.len(), 1);
2314 let expected_address = client_services.expected_responses[0]
2315 .operation_meta
2316 .address();
2317 let expected_value = client_services.expected_responses[0].operation_meta.value() != 0;
2318
2319 assert_eq!(expected_address, address);
2320 assert_eq!(expected_value, value);
2321 }
2322
2323 #[test]
2325 fn test_client_services_write_single_coil_e2e_success() {
2326 let transport = MockTransport::default();
2327 let app = MockApp::default();
2328 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2329 let mut client_services =
2330 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2331
2332 let txn_id = 0x0003;
2333 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2334 let address = 0x000A;
2335 let value = true;
2336
2337 client_services .write_single_coil(txn_id, unit_id, address, value)
2340 .unwrap();
2341
2342 let sent_adu = client_services
2344 .transport
2345 .sent_frames
2346 .borrow_mut()
2347 .pop_front()
2348 .unwrap();
2349 #[rustfmt::skip]
2350 let expected_request_adu: [u8; 12] = [
2351 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, ];
2359 assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2360
2361 let response_adu = [
2364 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00,
2365 ];
2366
2367 client_services
2369 .transport
2370 .recv_frames
2371 .borrow_mut()
2372 .push_back(Vec::from_slice(&response_adu).unwrap())
2373 .unwrap();
2374 client_services.poll();
2375
2376 let received_responses = client_services
2378 .app
2379 .received_write_single_coil_responses
2380 .borrow();
2381 assert_eq!(received_responses.len(), 1);
2382
2383 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
2384 assert_eq!(*rcv_txn_id, txn_id);
2385 assert_eq!(*rcv_unit_id, unit_id);
2386 assert_eq!(*rcv_address, address);
2387 assert_eq!(*rcv_value, value);
2388
2389 assert!(client_services.expected_responses.is_empty());
2391 }
2392
2393 #[test]
2395 fn test_write_multiple_coils_sends_valid_adu() {
2396 let transport = MockTransport::default();
2397 let app = MockApp::default();
2398 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2399 let mut client_services =
2400 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2401
2402 let txn_id = 0x0004;
2403 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2404 let address = 0x0000;
2405 let quantity = 10;
2406
2407 let mut values = Coils::new(address, quantity).unwrap();
2409 for i in 0..quantity {
2410 values.set_value(address + i, i % 2 == 0).unwrap();
2411 }
2412
2413 client_services
2414 .write_multiple_coils(txn_id, unit_id, address, &values) .unwrap();
2416
2417 let sent_frames = client_services.transport.sent_frames.borrow();
2418 assert_eq!(sent_frames.len(), 1);
2419 let sent_adu = sent_frames.front().unwrap();
2420
2421 #[rustfmt::skip]
2423 let expected_adu: [u8; 15] = [
2424 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0x55, 0x01, ];
2434 assert_eq!(sent_adu.as_slice(), &expected_adu);
2435
2436 assert_eq!(client_services.expected_responses.len(), 1);
2438 let expected_address = client_services.expected_responses[0]
2439 .operation_meta
2440 .address();
2441 let expected_quantity = client_services.expected_responses[0]
2442 .operation_meta
2443 .quantity();
2444 assert_eq!(expected_address, address);
2445 assert_eq!(expected_quantity, quantity);
2446 }
2447
2448 #[test]
2450 fn test_client_services_write_multiple_coils_e2e_success() {
2451 let transport = MockTransport::default();
2452 let app = MockApp::default();
2453 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2454 let mut client_services =
2455 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2456
2457 let txn_id = 0x0004;
2458 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2459 let address = 0x0000;
2460 let quantity = 10;
2461
2462 let mut values = Coils::new(address, quantity).unwrap();
2464 for i in 0..quantity {
2465 values.set_value(address + i, i % 2 == 0).unwrap();
2466 }
2467
2468 client_services .write_multiple_coils(txn_id, unit_id, address, &values)
2471 .unwrap();
2472
2473 let sent_adu = client_services
2475 .transport
2476 .sent_frames
2477 .borrow_mut()
2478 .pop_front()
2479 .unwrap();
2480 #[rustfmt::skip]
2481 let expected_request_adu: [u8; 15] = [
2482 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0x55, 0x01, ];
2492 assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2493
2494 let response_adu = [
2497 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A,
2498 ];
2499
2500 client_services
2502 .transport
2503 .recv_frames
2504 .borrow_mut()
2505 .push_back(Vec::from_slice(&response_adu).unwrap())
2506 .unwrap();
2507 client_services.poll();
2508
2509 let received_responses = client_services
2511 .app
2512 .received_write_multiple_coils_responses
2513 .borrow();
2514 assert_eq!(received_responses.len(), 1);
2515
2516 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
2517 assert_eq!(*rcv_txn_id, txn_id);
2518 assert_eq!(*rcv_unit_id, unit_id);
2519 assert_eq!(*rcv_address, address);
2520 assert_eq!(*rcv_quantity, quantity);
2521
2522 assert!(client_services.expected_responses.is_empty());
2524 }
2525
2526 #[test]
2528 fn test_client_services_read_coils_e2e_success() {
2529 let transport = MockTransport::default();
2530 let app = MockApp::default();
2531 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2532 let mut client_services =
2533 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2534
2535 let txn_id = 0x0001;
2536 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2537 let address = 0x0000;
2538 let quantity = 8;
2539 client_services
2540 .read_multiple_coils(txn_id, unit_id, address, quantity) .unwrap();
2542
2543 let sent_adu = client_services
2545 .transport
2546 .sent_frames
2547 .borrow_mut()
2548 .pop_front()
2549 .unwrap(); assert_eq!(
2552 sent_adu.as_slice(),
2553 &[
2554 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08
2555 ]
2556 );
2557
2558 assert_eq!(client_services.expected_responses.len(), 1); let from_address = client_services.expected_responses[0]
2561 .operation_meta
2562 .address();
2563 let expected_quantity = client_services.expected_responses[0]
2564 .operation_meta
2565 .quantity();
2566
2567 assert_eq!(expected_quantity, quantity);
2568 assert_eq!(from_address, address);
2569
2570 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2574
2575 client_services
2577 .transport
2578 .recv_frames
2579 .borrow_mut()
2580 .push_back(Vec::from_slice(&response_adu).unwrap())
2581 .unwrap();
2582 client_services.poll(); let received_responses = client_services.app().received_coil_responses.borrow();
2588 assert_eq!(received_responses.len(), 1);
2589
2590 let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2591 let rcv_quantity = rcv_coils.quantity();
2592 assert_eq!(*rcv_txn_id, txn_id);
2593 assert_eq!(*rcv_unit_id, unit_id);
2594 assert_eq!(rcv_coils.from_address(), address);
2595 assert_eq!(rcv_coils.quantity(), quantity);
2596 assert_eq!(&rcv_coils.values()[..1], &[0xB3]);
2597 assert_eq!(rcv_quantity, quantity);
2598
2599 assert!(client_services.expected_responses.is_empty());
2601 }
2602
2603 #[test]
2605 fn test_client_services_timeout_with_retry() {
2606 let transport = MockTransport::default();
2607 transport.recv_frames.borrow_mut().clear();
2609 let app = MockApp::default();
2610 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2611 tcp_config.response_timeout_ms = 100; tcp_config.retry_attempts = 1; let config = ModbusConfig::Tcp(tcp_config);
2614
2615 let mut client_services =
2616 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2617
2618 let txn_id = 0x0005;
2619 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2620 let address = 0x0000;
2621
2622 client_services
2623 .read_single_coil(txn_id, unit_id, address)
2624 .unwrap();
2625
2626 *client_services.app().current_time.borrow_mut() = 150;
2628 client_services.poll(); assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2633 assert_eq!(client_services.expected_responses.len(), 1); assert_eq!(client_services.expected_responses[0].retries_left, 0); *client_services.app().current_time.borrow_mut() = 300;
2638 client_services.poll(); assert!(client_services.expected_responses.is_empty());
2643 }
2645
2646 #[test]
2648 fn test_client_services_concurrent_timeouts() {
2649 let transport = MockTransport::default();
2650 let app = MockApp::default();
2651
2652 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2654 tcp_config.response_timeout_ms = 100;
2655 tcp_config.retry_attempts = 1;
2656 let config = ModbusConfig::Tcp(tcp_config);
2657
2658 let mut client_services =
2659 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2660
2661 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2662
2663 client_services
2665 .read_single_coil(1, unit_id, 0x0000)
2666 .unwrap();
2667 client_services
2668 .read_single_coil(2, unit_id, 0x0001)
2669 .unwrap();
2670
2671 assert_eq!(client_services.expected_responses.len(), 2);
2673 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2674
2675 *client_services.app().current_time.borrow_mut() = 150;
2677
2678 client_services.poll();
2680
2681 assert_eq!(client_services.expected_responses.len(), 2);
2683 assert_eq!(client_services.expected_responses[0].retries_left, 0);
2684 assert_eq!(client_services.expected_responses[1].retries_left, 0);
2685
2686 assert_eq!(client_services.transport.sent_frames.borrow().len(), 4);
2688
2689 *client_services.app().current_time.borrow_mut() = 300;
2691
2692 client_services.poll();
2694
2695 assert!(client_services.expected_responses.is_empty());
2697
2698 let failed_requests = client_services.app().failed_requests.borrow();
2700 assert_eq!(failed_requests.len(), 2);
2701
2702 let has_txn_1 = failed_requests
2704 .iter()
2705 .any(|(txn, _, err)| *txn == 1 && *err == MbusError::NoRetriesLeft);
2706 let has_txn_2 = failed_requests
2707 .iter()
2708 .any(|(txn, _, err)| *txn == 2 && *err == MbusError::NoRetriesLeft);
2709 assert!(has_txn_1, "Transaction 1 should have failed");
2710 assert!(has_txn_2, "Transaction 2 should have failed");
2711 }
2712
2713 #[test]
2714 fn test_poll_connection_loss_flushes_pending_requests() {
2715 let transport = MockTransport::default();
2716 let app = MockApp::default();
2717 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2718 let mut client_services =
2719 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2720
2721 let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
2722 client_services.read_single_coil(1, unit_id, 0).unwrap();
2723 client_services.read_single_coil(2, unit_id, 1).unwrap();
2724 assert_eq!(client_services.expected_responses.len(), 2);
2725
2726 *client_services.transport.is_connected_flag.borrow_mut() = false;
2727 *client_services.transport.recv_error.borrow_mut() = Some(MbusError::ConnectionClosed);
2728
2729 client_services.poll();
2730
2731 assert!(client_services.expected_responses.is_empty());
2732 assert_eq!(client_services.next_timeout_check, None);
2733
2734 let failed_requests = client_services.app().failed_requests.borrow();
2735 assert_eq!(failed_requests.len(), 2);
2736 assert!(
2737 failed_requests
2738 .iter()
2739 .all(|(txn, _, err)| (*txn == 1 || *txn == 2) && *err == MbusError::ConnectionLost)
2740 );
2741 }
2742
2743 #[test]
2744 fn test_fixed_backoff_schedules_and_does_not_retry_early() {
2745 let transport = MockTransport::default();
2746 let app = MockApp::default();
2747 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2748 tcp_config.response_timeout_ms = 100;
2749 tcp_config.retry_attempts = 1;
2750 tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 50 };
2751 let config = ModbusConfig::Tcp(tcp_config);
2752
2753 let mut client_services =
2754 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2755
2756 client_services
2757 .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2758 .unwrap();
2759 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2760
2761 *client_services.app().current_time.borrow_mut() = 101;
2762 client_services.poll();
2763 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2764 assert_eq!(
2765 client_services.expected_responses[0].next_retry_timestamp,
2766 Some(151)
2767 );
2768
2769 *client_services.app().current_time.borrow_mut() = 150;
2770 client_services.poll();
2771 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2772
2773 *client_services.app().current_time.borrow_mut() = 151;
2774 client_services.poll();
2775 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2776 }
2777
2778 #[test]
2779 fn test_exponential_backoff_growth() {
2780 let transport = MockTransport::default();
2781 let app = MockApp::default();
2782 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2783 tcp_config.response_timeout_ms = 100;
2784 tcp_config.retry_attempts = 2;
2785 tcp_config.retry_backoff_strategy = BackoffStrategy::Exponential {
2786 base_delay_ms: 50,
2787 max_delay_ms: 500,
2788 };
2789 let config = ModbusConfig::Tcp(tcp_config);
2790
2791 let mut client_services =
2792 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2793
2794 client_services
2795 .read_single_coil(7, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2796 .unwrap();
2797
2798 *client_services.app().current_time.borrow_mut() = 101;
2799 client_services.poll();
2800 assert_eq!(
2801 client_services.expected_responses[0].next_retry_timestamp,
2802 Some(151)
2803 );
2804
2805 *client_services.app().current_time.borrow_mut() = 151;
2806 client_services.poll();
2807 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2808
2809 *client_services.app().current_time.borrow_mut() = 252;
2810 client_services.poll();
2811 assert_eq!(
2812 client_services.expected_responses[0].next_retry_timestamp,
2813 Some(352)
2814 );
2815
2816 *client_services.app().current_time.borrow_mut() = 352;
2817 client_services.poll();
2818 assert_eq!(client_services.transport.sent_frames.borrow().len(), 3);
2819 }
2820
2821 #[test]
2822 fn test_jitter_bounds_with_random_source_lower_bound() {
2823 let transport = MockTransport::default();
2824 let app = MockApp::default();
2825 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2826 tcp_config.response_timeout_ms = 100;
2827 tcp_config.retry_attempts = 1;
2828 tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2829 tcp_config.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2830 tcp_config.retry_random_fn = Some(rand_zero);
2831 let config = ModbusConfig::Tcp(tcp_config);
2832
2833 let mut client_services =
2834 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2835 client_services
2836 .read_single_coil(10, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2837 .unwrap();
2838
2839 *client_services.app().current_time.borrow_mut() = 101;
2840 client_services.poll();
2841 assert_eq!(
2842 client_services.expected_responses[0].next_retry_timestamp,
2843 Some(181)
2844 );
2845 }
2846
2847 #[test]
2848 fn test_jitter_bounds_with_random_source_upper_bound() {
2849 let transport3 = MockTransport::default();
2850 let app3 = MockApp::default();
2851 let mut tcp_config3 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2852 tcp_config3.response_timeout_ms = 100;
2853 tcp_config3.retry_attempts = 1;
2854 tcp_config3.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2855 tcp_config3.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2856 tcp_config3.retry_random_fn = Some(rand_upper_percent_20);
2857 let config3 = ModbusConfig::Tcp(tcp_config3);
2858
2859 let mut client_services3 =
2860 ClientServices::<MockTransport, MockApp, 10>::new(transport3, app3, config3).unwrap();
2861 client_services3
2862 .read_single_coil(12, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2863 .unwrap();
2864
2865 *client_services3.app.current_time.borrow_mut() = 101;
2866 client_services3.poll();
2867 assert_eq!(
2868 client_services3.expected_responses[0].next_retry_timestamp,
2869 Some(221)
2870 );
2871 }
2872
2873 #[test]
2874 fn test_jitter_falls_back_without_random_source() {
2875 let transport2 = MockTransport::default();
2876 let app2 = MockApp::default();
2877 let mut tcp_config2 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2878 tcp_config2.response_timeout_ms = 100;
2879 tcp_config2.retry_attempts = 1;
2880 tcp_config2.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2881 tcp_config2.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2882 tcp_config2.retry_random_fn = None;
2883 let config2 = ModbusConfig::Tcp(tcp_config2);
2884
2885 let mut client_services2 =
2886 ClientServices::<MockTransport, MockApp, 10>::new(transport2, app2, config2).unwrap();
2887 client_services2
2888 .read_single_coil(11, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2889 .unwrap();
2890
2891 *client_services2.app.current_time.borrow_mut() = 101;
2892 client_services2.poll();
2893 assert_eq!(
2894 client_services2.expected_responses[0].next_retry_timestamp,
2895 Some(201)
2896 );
2897 }
2898
2899 #[test]
2900 fn test_serial_retry_scheduling_uses_backoff() {
2901 let transport = MockTransport {
2902 transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
2903 ..Default::default()
2904 };
2905 let app = MockApp::default();
2906
2907 let serial_config = ModbusSerialConfig {
2908 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
2909 mode: SerialMode::Rtu,
2910 baud_rate: BaudRate::Baud9600,
2911 data_bits: mbus_core::transport::DataBits::Eight,
2912 stop_bits: 1,
2913 parity: Parity::None,
2914 response_timeout_ms: 100,
2915 retry_attempts: 1,
2916 retry_backoff_strategy: BackoffStrategy::Fixed { delay_ms: 25 },
2917 retry_jitter_strategy: JitterStrategy::None,
2918 retry_random_fn: None,
2919 };
2920
2921 let mut client_services = ClientServices::<MockTransport, MockApp, 1>::new(
2922 transport,
2923 app,
2924 ModbusConfig::Serial(serial_config),
2925 )
2926 .unwrap();
2927
2928 client_services
2929 .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2930 .unwrap();
2931
2932 *client_services.app().current_time.borrow_mut() = 101;
2933 client_services.poll();
2934 assert_eq!(
2935 client_services.expected_responses[0].next_retry_timestamp,
2936 Some(126)
2937 );
2938
2939 *client_services.app().current_time.borrow_mut() = 126;
2940 client_services.poll();
2941 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2942 }
2943
2944 #[test]
2946 fn test_too_many_requests_error() {
2947 let transport = MockTransport::default();
2948 let app = MockApp::default();
2949 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2950 let mut client_services =
2952 ClientServices::<MockTransport, MockApp, 1>::new(transport, app, config).unwrap();
2953
2954 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2955 client_services
2957 .read_multiple_coils(1, unit_id, 0, 1)
2958 .unwrap();
2959 assert_eq!(client_services.expected_responses.len(), 1);
2960
2961 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2962 let result = client_services.read_multiple_coils(2, unit_id, 0, 1);
2964 assert!(result.is_err());
2965 assert_eq!(result.unwrap_err(), MbusError::TooManyRequests);
2966 assert_eq!(client_services.expected_responses.len(), 1); }
2968
2969 #[test]
2971 fn test_read_holding_registers_sends_valid_adu() {
2972 let transport = MockTransport::default();
2973 let app = MockApp::default();
2974 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2975 let mut client_services =
2976 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2977
2978 let txn_id = 0x0005;
2979 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2980 let address = 0x0000;
2981 let quantity = 2;
2982 client_services
2983 .read_holding_registers(txn_id, unit_id, address, quantity)
2984 .unwrap();
2985
2986 let sent_frames = client_services.transport.sent_frames.borrow();
2987 assert_eq!(sent_frames.len(), 1);
2988 let sent_adu = sent_frames.front().unwrap();
2989
2990 #[rustfmt::skip]
2992 let expected_adu: [u8; 12] = [
2993 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, ];
3001 assert_eq!(sent_adu.as_slice(), &expected_adu);
3002 }
3003
3004 #[test]
3006 fn test_client_services_read_holding_registers_e2e_success() {
3007 let transport = MockTransport::default();
3008 let app = MockApp::default();
3009 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3010 let mut client_services =
3011 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3012
3013 let txn_id = 0x0005;
3014 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3015 let address = 0x0000;
3016 let quantity = 2;
3017 client_services
3018 .read_holding_registers(txn_id, unit_id, address, quantity)
3019 .unwrap();
3020
3021 let response_adu = [
3024 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x01, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78,
3025 ];
3026 client_services
3027 .transport
3028 .recv_frames
3029 .borrow_mut()
3030 .push_back(Vec::from_slice(&response_adu).unwrap())
3031 .unwrap();
3032 client_services.poll();
3033
3034 let received_responses = client_services
3035 .app
3036 .received_holding_register_responses
3037 .borrow();
3038 assert_eq!(received_responses.len(), 1);
3039 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3040 assert_eq!(*rcv_txn_id, txn_id);
3041 assert_eq!(*rcv_unit_id, unit_id);
3042 assert_eq!(rcv_registers.from_address(), address);
3043 assert_eq!(rcv_registers.quantity(), quantity);
3044 assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3045 assert_eq!(*rcv_quantity, quantity);
3046 assert!(client_services.expected_responses.is_empty());
3047 }
3048
3049 #[test]
3051 fn test_read_input_registers_sends_valid_adu() {
3052 let transport = MockTransport::default();
3053 let app = MockApp::default();
3054 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3055 let mut client_services =
3056 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3057
3058 let txn_id = 0x0006;
3059 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3060 let address = 0x0000;
3061 let quantity = 2;
3062 client_services
3063 .read_input_registers(txn_id, unit_id, address, quantity)
3064 .unwrap();
3065
3066 let sent_frames = client_services.transport.sent_frames.borrow();
3067 assert_eq!(sent_frames.len(), 1);
3068 let sent_adu = sent_frames.front().unwrap();
3069
3070 #[rustfmt::skip]
3072 let expected_adu: [u8; 12] = [
3073 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x00, 0x00, 0x02, ];
3081 assert_eq!(sent_adu.as_slice(), &expected_adu);
3082 }
3083
3084 #[test]
3086 fn test_client_services_read_input_registers_e2e_success() {
3087 let transport = MockTransport::default();
3088 let app = MockApp::default();
3089 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3090 let mut client_services =
3091 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3092
3093 let txn_id = 0x0006;
3094 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3095 let address = 0x0000;
3096 let quantity = 2;
3097 client_services
3098 .read_input_registers(txn_id, unit_id, address, quantity)
3099 .unwrap();
3100
3101 let response_adu = [
3104 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x01, 0x04, 0x04, 0xAA, 0xBB, 0xCC, 0xDD,
3105 ];
3106 client_services
3107 .transport
3108 .recv_frames
3109 .borrow_mut()
3110 .push_back(Vec::from_slice(&response_adu).unwrap())
3111 .unwrap();
3112 client_services.poll();
3113
3114 let received_responses = client_services
3115 .app
3116 .received_input_register_responses
3117 .borrow();
3118 assert_eq!(received_responses.len(), 1);
3119 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3120 assert_eq!(*rcv_txn_id, txn_id);
3121 assert_eq!(*rcv_unit_id, unit_id);
3122 assert_eq!(rcv_registers.from_address(), address);
3123 assert_eq!(rcv_registers.quantity(), quantity);
3124 assert_eq!(&rcv_registers.values()[..2], &[0xAABB, 0xCCDD]);
3125 assert_eq!(*rcv_quantity, quantity);
3126 assert!(client_services.expected_responses.is_empty());
3127 }
3128
3129 #[test]
3131 fn test_write_single_register_sends_valid_adu() {
3132 let transport = MockTransport::default();
3133 let app = MockApp::default();
3134 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3135 let mut client_services =
3136 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3137
3138 let txn_id = 0x0007;
3139 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3140 let address = 0x0001;
3141 let value = 0x1234;
3142 client_services
3143 .write_single_register(txn_id, unit_id, address, value)
3144 .unwrap();
3145
3146 let sent_frames = client_services.transport.sent_frames.borrow();
3147 assert_eq!(sent_frames.len(), 1);
3148 let sent_adu = sent_frames.front().unwrap();
3149
3150 #[rustfmt::skip]
3152 let expected_adu: [u8; 12] = [
3153 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34, ];
3161 assert_eq!(sent_adu.as_slice(), &expected_adu);
3162 }
3163
3164 #[test]
3166 fn test_client_services_write_single_register_e2e_success() {
3167 let transport = MockTransport::default();
3168 let app = MockApp::default();
3169 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3170 let mut client_services =
3171 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3172
3173 let txn_id = 0x0007;
3174 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3175 let address = 0x0001;
3176 let value = 0x1234;
3177 client_services
3178 .write_single_register(txn_id, unit_id, address, value)
3179 .unwrap();
3180
3181 let response_adu = [
3184 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34,
3185 ];
3186 client_services
3187 .transport
3188 .recv_frames
3189 .borrow_mut()
3190 .push_back(Vec::from_slice(&response_adu).unwrap())
3191 .unwrap();
3192 client_services.poll();
3193
3194 let received_responses = client_services
3195 .app
3196 .received_write_single_register_responses
3197 .borrow();
3198 assert_eq!(received_responses.len(), 1);
3199 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
3200 assert_eq!(*rcv_txn_id, txn_id);
3201 assert_eq!(*rcv_unit_id, unit_id);
3202 assert_eq!(*rcv_address, address);
3203 assert_eq!(*rcv_value, value);
3204 assert!(client_services.expected_responses.is_empty());
3205 }
3206
3207 #[test]
3209 fn test_write_multiple_registers_sends_valid_adu() {
3210 let transport = MockTransport::default();
3211 let app = MockApp::default();
3212 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3213 let mut client_services =
3214 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3215
3216 let txn_id = 0x0008;
3217 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3218 let address = 0x0001;
3219 let quantity = 2;
3220 let values = [0x1234, 0x5678];
3221 client_services
3222 .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3223 .unwrap();
3224
3225 let sent_frames = client_services.transport.sent_frames.borrow();
3226 assert_eq!(sent_frames.len(), 1);
3227 let sent_adu = sent_frames.front().unwrap();
3228
3229 #[rustfmt::skip]
3231 let expected_adu: [u8; 17] = [ 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x34, 0x56, 0x78, ];
3242 assert_eq!(sent_adu.as_slice(), &expected_adu);
3243 }
3244
3245 #[test]
3247 fn test_client_services_write_multiple_registers_e2e_success() {
3248 let transport = MockTransport::default();
3249 let app = MockApp::default();
3250 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3251 let mut client_services =
3252 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3253
3254 let txn_id = 0x0008;
3255 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3256 let address = 0x0001;
3257 let quantity = 2;
3258 let values = [0x1234, 0x5678];
3259 client_services
3260 .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3261 .unwrap();
3262
3263 let response_adu = [
3266 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02,
3267 ];
3268 client_services
3269 .transport
3270 .recv_frames
3271 .borrow_mut()
3272 .push_back(Vec::from_slice(&response_adu).unwrap())
3273 .unwrap();
3274 client_services.poll();
3275
3276 let received_responses = client_services
3277 .app
3278 .received_write_multiple_register_responses
3279 .borrow();
3280 assert_eq!(received_responses.len(), 1);
3281 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
3282 assert_eq!(*rcv_txn_id, txn_id);
3283 assert_eq!(*rcv_unit_id, unit_id);
3284 assert_eq!(*rcv_address, address);
3285 assert_eq!(*rcv_quantity, quantity);
3286 assert!(client_services.expected_responses.is_empty());
3287 }
3288
3289 #[test]
3291 fn test_client_services_handles_exception_response() {
3292 let transport = MockTransport::default();
3293 let app = MockApp::default();
3294 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3295 let mut client_services =
3296 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3297
3298 let txn_id = 0x0009;
3299 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3300 let address = 0x0000;
3301 let quantity = 1;
3302
3303 client_services
3304 .read_holding_registers(txn_id, unit_id, address, quantity)
3305 .unwrap();
3306
3307 let exception_adu = [
3310 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x01, 0x83, 0x02, ];
3317 client_services
3318 .transport
3319 .recv_frames
3320 .borrow_mut()
3321 .push_back(Vec::from_slice(&exception_adu).unwrap())
3322 .unwrap();
3323 client_services.poll();
3324
3325 assert!(
3327 client_services
3328 .app
3329 .received_holding_register_responses
3330 .borrow()
3331 .is_empty()
3332 );
3333 assert_eq!(client_services.app().failed_requests.borrow().len(), 1);
3335 let (failed_txn, failed_unit, failed_err) =
3336 &client_services.app().failed_requests.borrow()[0];
3337 assert_eq!(*failed_txn, txn_id);
3338 assert_eq!(*failed_unit, unit_id);
3339 assert_eq!(*failed_err, MbusError::ModbusException(0x02));
3340 }
3341
3342 #[test]
3344 fn test_read_single_holding_register_sends_valid_adu() {
3345 let transport = MockTransport::default();
3346 let app = MockApp::default();
3347 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3348 let mut client_services =
3349 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3350
3351 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3352 client_services
3353 .read_single_holding_register(10, unit_id, 100)
3354 .unwrap();
3355
3356 let sent_frames = client_services.transport.sent_frames.borrow();
3357 assert_eq!(sent_frames.len(), 1);
3358 let sent_adu = sent_frames.front().unwrap();
3359
3360 #[rustfmt::skip]
3361 let expected_adu: [u8; 12] = [
3362 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x64, 0x00, 0x01, ];
3370 assert_eq!(sent_adu.as_slice(), &expected_adu);
3371
3372 assert_eq!(client_services.expected_responses.len(), 1);
3374 let single_read = client_services.expected_responses[0]
3375 .operation_meta
3376 .is_single();
3377 assert!(single_read);
3378 }
3379
3380 #[test]
3382 fn test_client_services_read_single_holding_register_e2e_success() {
3383 let transport = MockTransport::default();
3384 let app = MockApp::default();
3385 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3386 let mut client_services =
3387 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3388
3389 let txn_id = 10;
3390 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3391 let address = 100;
3392
3393 client_services
3394 .read_single_holding_register(txn_id, unit_id, address)
3395 .unwrap();
3396
3397 let response_adu = [
3399 0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x03, 0x02, 0x12, 0x34,
3400 ];
3401 client_services
3402 .transport
3403 .recv_frames
3404 .borrow_mut()
3405 .push_back(Vec::from_slice(&response_adu).unwrap())
3406 .unwrap();
3407 client_services.poll();
3408
3409 let received_responses = client_services
3410 .app
3411 .received_holding_register_responses
3412 .borrow();
3413 assert_eq!(received_responses.len(), 1);
3414 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3415 assert_eq!(*rcv_txn_id, txn_id);
3416 assert_eq!(*rcv_unit_id, unit_id);
3417 assert_eq!(rcv_registers.from_address(), address);
3418 assert_eq!(rcv_registers.quantity(), 1);
3419 assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
3420 assert_eq!(*rcv_quantity, 1);
3421 }
3422
3423 #[test]
3425 fn test_read_single_input_register_sends_valid_adu() {
3426 let transport = MockTransport::default();
3427 let app = MockApp::default();
3428 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3429 let mut client_services =
3430 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3431
3432 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3433 client_services
3434 .read_single_input_register(10, unit_id, 100)
3435 .unwrap();
3436
3437 let sent_frames = client_services.transport.sent_frames.borrow();
3438 assert_eq!(sent_frames.len(), 1);
3439 let sent_adu = sent_frames.front().unwrap();
3440
3441 #[rustfmt::skip]
3442 let expected_adu: [u8; 12] = [
3443 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x64, 0x00, 0x01, ];
3451 assert_eq!(sent_adu.as_slice(), &expected_adu);
3452
3453 assert_eq!(client_services.expected_responses.len(), 1);
3455 let single_read = client_services.expected_responses[0]
3456 .operation_meta
3457 .is_single();
3458 assert!(single_read);
3459 }
3460
3461 #[test]
3463 fn test_client_services_read_single_input_register_e2e_success() {
3464 let transport = MockTransport::default();
3465 let app = MockApp::default();
3466 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3467 let mut client_services =
3468 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3469
3470 let txn_id = 10;
3471 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3472 let address = 100;
3473
3474 client_services
3475 .read_single_input_register(txn_id, unit_id, address)
3476 .unwrap();
3477
3478 let response_adu = [
3481 0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x04, 0x02, 0x12, 0x34,
3482 ];
3483 client_services
3484 .transport
3485 .recv_frames
3486 .borrow_mut()
3487 .push_back(Vec::from_slice(&response_adu).unwrap())
3488 .unwrap();
3489 client_services.poll();
3490
3491 let received_responses = client_services
3492 .app
3493 .received_input_register_responses
3494 .borrow();
3495 assert_eq!(received_responses.len(), 1);
3496 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3497 assert_eq!(*rcv_txn_id, txn_id);
3498 assert_eq!(*rcv_unit_id, unit_id);
3499 assert_eq!(rcv_registers.from_address(), address);
3500 assert_eq!(rcv_registers.quantity(), 1);
3501 assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
3502 assert_eq!(*rcv_quantity, 1);
3503 }
3504
3505 #[test]
3507 fn test_read_write_multiple_registers_sends_valid_adu() {
3508 let transport = MockTransport::default();
3509 let app = MockApp::default();
3510 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3511 let mut client_services =
3512 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3513
3514 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3515 let write_values = [0xAAAA, 0xBBBB];
3516 client_services
3517 .read_write_multiple_registers(11, unit_id, 10, 2, 20, &write_values)
3518 .unwrap();
3519
3520 let sent_frames = client_services.transport.sent_frames.borrow();
3521 assert_eq!(sent_frames.len(), 1);
3522 let sent_adu = sent_frames.front().unwrap();
3523
3524 #[rustfmt::skip]
3525 let expected_adu: [u8; 21] = [
3526 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x17, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x14, 0x00, 0x02, 0x04, 0xAA, 0xAA, 0xBB, 0xBB, ];
3539 assert_eq!(sent_adu.as_slice(), &expected_adu);
3540 }
3541
3542 #[test]
3544 fn test_mask_write_register_sends_valid_adu() {
3545 let transport = MockTransport::default();
3546 let app = MockApp::default();
3547 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3548 let mut client_services =
3549 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3550
3551 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3552 client_services
3553 .mask_write_register(12, unit_id, 30, 0xF0F0, 0x0F0F)
3554 .unwrap();
3555
3556 let sent_frames = client_services.transport.sent_frames.borrow();
3557 assert_eq!(sent_frames.len(), 1);
3558 let sent_adu = sent_frames.front().unwrap();
3559
3560 #[rustfmt::skip]
3561 let expected_adu: [u8; 14] = [
3562 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F, ];
3571 assert_eq!(sent_adu.as_slice(), &expected_adu);
3572 }
3573
3574 #[test]
3576 fn test_client_services_read_write_multiple_registers_e2e_success() {
3577 let transport = MockTransport::default();
3578 let app = MockApp::default();
3579 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3580 let mut client_services =
3581 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3582
3583 let txn_id = 11;
3584 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3585 let read_address = 10;
3586 let read_quantity = 2;
3587 let write_address = 20;
3588 let write_values = [0xAAAA, 0xBBBB];
3589
3590 client_services
3591 .read_write_multiple_registers(
3592 txn_id,
3593 unit_id,
3594 read_address,
3595 read_quantity,
3596 write_address,
3597 &write_values,
3598 )
3599 .unwrap();
3600
3601 let response_adu = [
3603 0x00, 0x0B, 0x00, 0x00, 0x00, 0x07, 0x01, 0x17, 0x04, 0x12, 0x34, 0x56, 0x78,
3604 ];
3605 client_services
3606 .transport
3607 .recv_frames
3608 .borrow_mut()
3609 .push_back(Vec::from_slice(&response_adu).unwrap())
3610 .unwrap();
3611 client_services.poll();
3612
3613 let received_responses = client_services
3614 .app
3615 .received_read_write_multiple_registers_responses
3616 .borrow();
3617 assert_eq!(received_responses.len(), 1);
3618 let (rcv_txn_id, rcv_unit_id, rcv_registers) = &received_responses[0];
3619 assert_eq!(*rcv_txn_id, txn_id);
3620 assert_eq!(*rcv_unit_id, unit_id);
3621 assert_eq!(rcv_registers.from_address(), read_address);
3622 assert_eq!(rcv_registers.quantity(), read_quantity);
3623 assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3624 }
3625
3626 #[test]
3628 fn test_client_services_mask_write_register_e2e_success() {
3629 let transport = MockTransport::default();
3630 let app = MockApp::default();
3631 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3632 let mut client_services =
3633 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3634
3635 let txn_id = 12;
3636 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3637 let address = 30;
3638 let and_mask = 0xF0F0;
3639 let or_mask = 0x0F0F;
3640
3641 client_services
3642 .mask_write_register(txn_id, unit_id, address, and_mask, or_mask)
3643 .unwrap();
3644
3645 let response_adu = [
3647 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F,
3648 ];
3649 client_services
3650 .transport
3651 .recv_frames
3652 .borrow_mut()
3653 .push_back(Vec::from_slice(&response_adu).unwrap())
3654 .unwrap();
3655 client_services.poll();
3656
3657 let received_responses = client_services
3658 .app
3659 .received_mask_write_register_responses
3660 .borrow();
3661 assert_eq!(received_responses.len(), 1);
3662 let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
3663 assert_eq!(*rcv_txn_id, txn_id);
3664 assert_eq!(*rcv_unit_id, unit_id);
3665 }
3666
3667 #[test]
3669 fn test_client_services_read_fifo_queue_e2e_success() {
3670 let transport = MockTransport::default();
3671 let app = MockApp::default();
3672 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3673 let mut client_services =
3674 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3675
3676 let txn_id = 13;
3677 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3678 let address = 40;
3679
3680 client_services
3681 .read_fifo_queue(txn_id, unit_id, address)
3682 .unwrap();
3683
3684 #[rustfmt::skip]
3686 let response_adu = [
3687 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x18, 0x00, 0x06, 0x00, 0x02, 0xAA, 0xAA, 0xBB, 0xBB, ];
3697 client_services
3698 .transport
3699 .recv_frames
3700 .borrow_mut()
3701 .push_back(Vec::from_slice(&response_adu).unwrap())
3702 .unwrap();
3703 client_services.poll();
3704
3705 let received_responses = client_services
3706 .app
3707 .received_read_fifo_queue_responses
3708 .borrow();
3709 assert_eq!(received_responses.len(), 1);
3710 let (rcv_txn_id, rcv_unit_id, rcv_fifo_queue) = &received_responses[0];
3711 assert_eq!(*rcv_txn_id, txn_id);
3712 assert_eq!(*rcv_unit_id, unit_id);
3713 assert_eq!(rcv_fifo_queue.length(), 2);
3714 assert_eq!(&rcv_fifo_queue.queue()[..2], &[0xAAAA, 0xBBBB]);
3715 }
3716
3717 #[test]
3719 fn test_client_services_read_file_record_e2e_success() {
3720 let transport = MockTransport::default();
3721 let app = MockApp::default();
3722 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3723 let mut client_services =
3724 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3725
3726 let txn_id = 14;
3727 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3728 let mut sub_req = SubRequest::new();
3729 sub_req.add_read_sub_request(4, 1, 2).unwrap();
3730
3731 client_services
3732 .read_file_record(txn_id, unit_id, &sub_req)
3733 .unwrap();
3734
3735 let response_adu = [
3741 0x00, 0x0E, 0x00, 0x00, 0x00, 0x09, 0x01, 0x14, 0x06, 0x05, 0x06, 0x12, 0x34, 0x56,
3742 0x78,
3743 ];
3744
3745 client_services
3746 .transport
3747 .recv_frames
3748 .borrow_mut()
3749 .push_back(Vec::from_slice(&response_adu).unwrap())
3750 .unwrap();
3751 client_services.poll();
3752
3753 let received_responses = client_services
3754 .app
3755 .received_read_file_record_responses
3756 .borrow();
3757 assert_eq!(received_responses.len(), 1);
3758 let (rcv_txn_id, rcv_unit_id, rcv_data) = &received_responses[0];
3759 assert_eq!(*rcv_txn_id, txn_id);
3760 assert_eq!(*rcv_unit_id, unit_id);
3761 assert_eq!(rcv_data.len(), 1);
3762 assert_eq!(
3763 rcv_data[0].record_data.as_ref().unwrap().as_slice(),
3764 &[0x1234, 0x5678]
3765 );
3766 }
3767
3768 #[test]
3770 fn test_client_services_write_file_record_e2e_success() {
3771 let transport = MockTransport::default();
3772 let app = MockApp::default();
3773 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3774 let mut client_services =
3775 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3776
3777 let txn_id = 15;
3778 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3779 let mut sub_req = SubRequest::new();
3780 let mut data = Vec::new();
3781 data.push(0x1122).unwrap();
3782 sub_req.add_write_sub_request(4, 1, 1, data).unwrap();
3783
3784 client_services
3785 .write_file_record(txn_id, unit_id, &sub_req)
3786 .unwrap();
3787
3788 let response_adu = [
3791 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0C, 0x01, 0x15, 0x09, 0x06, 0x00, 0x04, 0x00, 0x01,
3792 0x00, 0x01, 0x11, 0x22,
3793 ];
3794
3795 client_services
3796 .transport
3797 .recv_frames
3798 .borrow_mut()
3799 .push_back(Vec::from_slice(&response_adu).unwrap())
3800 .unwrap();
3801 client_services.poll();
3802
3803 let received_responses = client_services
3804 .app
3805 .received_write_file_record_responses
3806 .borrow();
3807 assert_eq!(received_responses.len(), 1);
3808 let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
3809 assert_eq!(*rcv_txn_id, txn_id);
3810 assert_eq!(*rcv_unit_id, unit_id);
3811 }
3812
3813 #[test]
3815 fn test_client_services_read_discrete_inputs_e2e_success() {
3816 let transport = MockTransport::default();
3817 let app = MockApp::default();
3818 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3819 let mut client_services =
3820 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3821
3822 let txn_id = 16;
3823 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3824 let address = 50;
3825 let quantity = 8;
3826
3827 client_services
3828 .read_discrete_inputs(txn_id, unit_id, address, quantity)
3829 .unwrap();
3830
3831 let response_adu = [0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0xAA];
3833
3834 client_services
3835 .transport
3836 .recv_frames
3837 .borrow_mut()
3838 .push_back(Vec::from_slice(&response_adu).unwrap())
3839 .unwrap();
3840 client_services.poll();
3841
3842 let received_responses = client_services
3843 .app
3844 .received_discrete_input_responses
3845 .borrow();
3846 assert_eq!(received_responses.len(), 1);
3847 let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
3848 assert_eq!(*rcv_txn_id, txn_id);
3849 assert_eq!(*rcv_unit_id, unit_id);
3850 assert_eq!(rcv_inputs.from_address(), address);
3851 assert_eq!(rcv_inputs.quantity(), quantity);
3852 assert_eq!(rcv_inputs.values(), &[0xAA]);
3853 assert_eq!(*rcv_quantity, quantity);
3854 }
3855
3856 #[test]
3858 fn test_client_services_read_single_discrete_input_e2e_success() {
3859 let transport = MockTransport::default();
3860 let app = MockApp::default();
3861 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3862 let mut client_services =
3863 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3864
3865 let txn_id = 17;
3866 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3867 let address = 10;
3868
3869 client_services
3870 .read_single_discrete_input(txn_id, unit_id, address)
3871 .unwrap();
3872
3873 let sent_frames = client_services.transport.sent_frames.borrow();
3875 assert_eq!(sent_frames.len(), 1);
3876 let expected_request = [
3880 0x00, 0x11, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x0A, 0x00, 0x01,
3881 ];
3882 assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
3883 drop(sent_frames);
3884
3885 let response_adu = [0x00, 0x11, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0x01];
3887
3888 client_services
3889 .transport
3890 .recv_frames
3891 .borrow_mut()
3892 .push_back(Vec::from_slice(&response_adu).unwrap())
3893 .unwrap();
3894 client_services.poll();
3895
3896 let received_responses = client_services
3897 .app
3898 .received_discrete_input_responses
3899 .borrow();
3900 assert_eq!(received_responses.len(), 1);
3901 let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
3902 assert_eq!(*rcv_txn_id, txn_id);
3903 assert_eq!(*rcv_unit_id, unit_id);
3904 assert_eq!(rcv_inputs.from_address(), address);
3905 assert_eq!(rcv_inputs.quantity(), 1);
3906 assert_eq!(rcv_inputs.value(address).unwrap(), true);
3907 assert_eq!(*rcv_quantity, 1);
3908 }
3909
3910 #[test]
3912 fn test_client_services_read_device_identification_e2e_success() {
3913 let transport = MockTransport::default();
3914 let app = MockApp::default();
3915 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3916 let mut client_services =
3917 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3918
3919 let txn_id = 20;
3920 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3921 let read_code = ReadDeviceIdCode::Basic;
3922 let object_id = ObjectId::from(0x00);
3923
3924 client_services
3925 .read_device_identification(txn_id, unit_id, read_code, object_id)
3926 .unwrap();
3927
3928 let sent_frames = client_services.transport.sent_frames.borrow();
3930 assert_eq!(sent_frames.len(), 1);
3931 let expected_request = [
3935 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2B, 0x0E, 0x01, 0x00,
3936 ];
3937 assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
3938 drop(sent_frames);
3939
3940 let response_adu = [
3945 0x00, 0x14, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x01,
3946 0x00, 0x03, 0x46, 0x6F, 0x6F,
3947 ];
3948
3949 client_services
3950 .transport
3951 .recv_frames
3952 .borrow_mut()
3953 .push_back(Vec::from_slice(&response_adu).unwrap())
3954 .unwrap();
3955 client_services.poll();
3956
3957 let received_responses = client_services
3958 .app
3959 .received_read_device_id_responses
3960 .borrow();
3961 assert_eq!(received_responses.len(), 1);
3962 let (rcv_txn_id, rcv_unit_id, rcv_resp) = &received_responses[0];
3963 assert_eq!(*rcv_txn_id, txn_id);
3964 assert_eq!(*rcv_unit_id, unit_id);
3965 assert_eq!(rcv_resp.read_device_id_code, ReadDeviceIdCode::Basic);
3966 assert_eq!(
3967 rcv_resp.conformity_level,
3968 ConformityLevel::BasicStreamAndIndividual
3969 );
3970 assert_eq!(rcv_resp.number_of_objects, 1);
3971
3972 assert_eq!(&rcv_resp.objects_data[..5], &[0x00, 0x03, 0x46, 0x6F, 0x6F]);
3974 }
3975
3976 #[test]
3978 fn test_client_services_read_device_identification_multi_transaction() {
3979 let transport = MockTransport::default();
3980 let app = MockApp::default();
3981 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3982 let mut client_services =
3983 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3984
3985 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3986 let txn_id_1 = 21;
3988 client_services
3989 .read_device_identification(
3990 txn_id_1,
3991 unit_id,
3992 ReadDeviceIdCode::Basic,
3993 ObjectId::from(0x00),
3994 )
3995 .unwrap();
3996
3997 let txn_id_2 = 22;
3999 client_services
4000 .read_device_identification(
4001 txn_id_2,
4002 unit_id,
4003 ReadDeviceIdCode::Regular,
4004 ObjectId::from(0x00),
4005 )
4006 .unwrap();
4007
4008 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
4009
4010 let response_adu_2 = [
4014 0x00, 0x16, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x82, 0x00, 0x00, 0x00,
4015 ];
4016 client_services
4017 .transport
4018 .recv_frames
4019 .borrow_mut()
4020 .push_back(Vec::from_slice(&response_adu_2).unwrap())
4021 .unwrap();
4022
4023 client_services.poll();
4024
4025 {
4026 let received_responses = client_services
4027 .app
4028 .received_read_device_id_responses
4029 .borrow();
4030 assert_eq!(received_responses.len(), 1);
4031 assert_eq!(received_responses[0].0, txn_id_2);
4032 assert_eq!(
4033 received_responses[0].2.read_device_id_code,
4034 ReadDeviceIdCode::Regular
4035 );
4036 }
4037
4038 let response_adu_1 = [
4041 0x00, 0x15, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x00,
4042 ];
4043 client_services
4044 .transport
4045 .recv_frames
4046 .borrow_mut()
4047 .push_back(Vec::from_slice(&response_adu_1).unwrap())
4048 .unwrap();
4049
4050 client_services.poll();
4051
4052 {
4053 let received_responses = client_services
4054 .app
4055 .received_read_device_id_responses
4056 .borrow();
4057 assert_eq!(received_responses.len(), 2);
4058 assert_eq!(received_responses[1].0, txn_id_1);
4059 assert_eq!(
4060 received_responses[1].2.read_device_id_code,
4061 ReadDeviceIdCode::Basic
4062 );
4063 }
4064 }
4065
4066 #[test]
4068 fn test_client_services_read_device_identification_mismatch_code() {
4069 let transport = MockTransport::default();
4070 let app = MockApp::default();
4071 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4072 let mut client_services =
4073 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4074
4075 let txn_id = 30;
4076 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4077 client_services
4079 .read_device_identification(
4080 txn_id,
4081 unit_id,
4082 ReadDeviceIdCode::Basic,
4083 ObjectId::from(0x00),
4084 )
4085 .unwrap();
4086
4087 let response_adu = [
4090 0x00, 0x1E, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x81, 0x00, 0x00, 0x00,
4091 ];
4092
4093 client_services
4094 .transport
4095 .recv_frames
4096 .borrow_mut()
4097 .push_back(Vec::from_slice(&response_adu).unwrap())
4098 .unwrap();
4099
4100 client_services.poll();
4101
4102 assert!(
4104 client_services
4105 .app
4106 .received_read_device_id_responses
4107 .borrow()
4108 .is_empty()
4109 );
4110
4111 let failed = client_services.app().failed_requests.borrow();
4113 assert_eq!(failed.len(), 1);
4114 assert_eq!(failed[0].2, MbusError::InvalidDeviceIdentification);
4115 }
4116
4117 #[test]
4119 fn test_client_services_read_exception_status_e2e_success() {
4120 let transport = MockTransport::default();
4121 let app = MockApp::default();
4122 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4123 let mut client_services =
4124 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4125
4126 let txn_id = 40;
4127 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4128
4129 let err = client_services.read_exception_status(txn_id, unit_id).err();
4130 assert_eq!(err, Some(MbusError::InvalidTransport));
4132 }
4133
4134 #[test]
4136 fn test_client_services_diagnostics_query_data_success() {
4137 let transport = MockTransport::default();
4138 let app = MockApp::default();
4139 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4140 let mut client_services =
4141 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4142
4143 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4144 let data = [0x1234, 0x5678];
4145 let sub_function = DiagnosticSubFunction::ReturnQueryData;
4146 let err = client_services
4147 .diagnostics(50, unit_id, sub_function, &data)
4148 .err();
4149 assert_eq!(err, Some(MbusError::InvalidTransport));
4150 }
4151
4152 #[test]
4154 fn test_client_services_get_comm_event_counter_success() {
4155 let transport = MockTransport::default();
4156 let app = MockApp::default();
4157 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4158 let mut client_services =
4159 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4160 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4161 let err = client_services.get_comm_event_counter(60, unit_id).err();
4162
4163 assert_eq!(err, Some(MbusError::InvalidTransport));
4164 }
4165
4166 #[test]
4168 fn test_client_services_report_server_id_success() {
4169 let transport = MockTransport::default();
4170 let app = MockApp::default();
4171 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4172 let mut client_services =
4173 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4174
4175 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4176 let err = client_services.report_server_id(70, unit_id).err();
4177
4178 assert_eq!(err, Some(MbusError::InvalidTransport));
4179 }
4180
4181 #[test]
4185 fn test_broadcast_read_multiple_coils_not_allowed() {
4186 let transport = MockTransport::default();
4187 let app = MockApp::default();
4188 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4189 let mut client_services =
4190 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4191
4192 let txn_id = 0x0001;
4193 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4194 let address = 0x0000;
4195 let quantity = 8;
4196 let res = client_services.read_multiple_coils(txn_id, unit_id, address, quantity);
4197 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4198 }
4199
4200 #[test]
4202 fn test_broadcast_write_single_coil_tcp_not_allowed() {
4203 let transport = MockTransport::default();
4204 let app = MockApp::default();
4205 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4206 let mut client_services =
4207 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4208
4209 let txn_id = 0x0002;
4210 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4211 let res = client_services.write_single_coil(txn_id, unit_id, 0x0000, true);
4212 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4213 }
4214
4215 #[test]
4217 fn test_broadcast_write_multiple_coils_tcp_not_allowed() {
4218 let transport = MockTransport::default();
4219 let app = MockApp::default();
4220 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4221 let mut client_services =
4222 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4223
4224 let txn_id = 0x0003;
4225 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4226 let mut values = Coils::new(0x0000, 2).unwrap();
4227 values.set_value(0x0000, true).unwrap();
4228 values.set_value(0x0001, false).unwrap();
4229
4230 let res = client_services.write_multiple_coils(txn_id, unit_id, 0x0000, &values);
4231 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4232 }
4233
4234 #[test]
4236 fn test_broadcast_read_discrete_inputs_not_allowed() {
4237 let transport = MockTransport::default();
4238 let app = MockApp::default();
4239 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4240 let mut client_services =
4241 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4242
4243 let txn_id = 0x0006;
4244 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4245 let res = client_services.read_discrete_inputs(txn_id, unit_id, 0x0000, 2);
4246 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4247 }
4248
4249 #[test]
4252 fn test_client_services_clears_buffer_on_overflow() {
4253 let transport = MockTransport::default();
4254 let app = MockApp::default();
4255 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4256 let mut client_services =
4257 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4258
4259 let initial_garbage = [0xFF; MAX_ADU_FRAME_LEN - 10];
4261 client_services
4262 .rxed_frame
4263 .extend_from_slice(&initial_garbage)
4264 .unwrap();
4265
4266 let chunk = [0xAA; 20];
4268 client_services
4269 .transport
4270 .recv_frames
4271 .borrow_mut()
4272 .push_back(Vec::from_slice(&chunk).unwrap())
4273 .unwrap();
4274
4275 client_services.poll();
4277
4278 assert!(
4279 client_services.rxed_frame.is_empty(),
4280 "Buffer should be cleared on overflow to prevent crashing and recover from stream noise."
4281 );
4282 }
4283}