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 wire_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 == wire_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 wire_txn_id,
807 unit_id_or_slave_addr.get()
808 );
809 return;
810 }
811 };
812
813 let request_txn_id = expected.txn_id;
814
815 client_log_trace!(
816 "dispatching response: txn_id={}, unit_id_or_slave_addr={}, queue_len_after_pop={}",
817 request_txn_id,
818 unit_id_or_slave_addr.get(),
819 self.expected_responses.len()
820 );
821
822 if let Some(exception_code) = message.pdu().error_code() {
825 client_log_debug!(
826 "modbus exception response: txn_id={}, unit_id_or_slave_addr={}, code=0x{:02X}",
827 request_txn_id,
828 unit_id_or_slave_addr.get(),
829 exception_code
830 );
831 self.app.request_failed(
832 request_txn_id,
833 unit_id_or_slave_addr,
834 MbusError::ModbusException(exception_code),
835 );
836 return;
837 }
838
839 (expected.handler)(self, &expected, message);
840 }
841}
842
843impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
844where
845 TRANSPORT: Transport,
846 TRANSPORT::Error: Into<MbusError>,
847 APP: RequestErrorNotifier + TimeKeeper,
848{
849 pub fn poll(&mut self) {
896 match self.transport.recv() {
898 Ok(frame) => {
899 client_log_trace!("received {} transport bytes", frame.len());
900 if self.rxed_frame.extend_from_slice(frame.as_slice()).is_err() {
901 client_log_debug!(
903 "received frame buffer overflow while appending {} bytes; clearing receive buffer",
904 frame.len()
905 );
906 self.rxed_frame.clear();
907 }
908
909 while !self.rxed_frame.is_empty() {
911 match self.ingest_frame() {
912 Ok(consumed) => {
913 client_log_trace!(
914 "ingested complete frame consuming {} bytes from rx buffer len {}",
915 consumed,
916 self.rxed_frame.len()
917 );
918 let len = self.rxed_frame.len();
919 if consumed < len {
920 self.rxed_frame.copy_within(consumed.., 0);
922 self.rxed_frame.truncate(len - consumed);
923 } else {
924 self.rxed_frame.clear();
925 }
926 }
927 Err(MbusError::BufferTooSmall) => {
928 client_log_trace!(
930 "incomplete frame in rx buffer; waiting for more bytes (buffer_len={})",
931 self.rxed_frame.len()
932 );
933 break;
934 }
935 Err(err) => {
936 client_log_debug!(
938 "frame parse/resync event: error={:?}, buffer_len={}; dropping 1 byte",
939 err,
940 self.rxed_frame.len()
941 );
942 let len = self.rxed_frame.len();
943 if len > 1 {
944 self.rxed_frame.copy_within(1.., 0);
945 self.rxed_frame.truncate(len - 1);
946 } else {
947 self.rxed_frame.clear();
948 }
949 }
950 }
951 }
952 }
953 Err(err) => {
954 let recv_error: MbusError = err.into();
955 let is_connection_loss = matches!(
956 recv_error,
957 MbusError::ConnectionClosed
958 | MbusError::ConnectionFailed
959 | MbusError::ConnectionLost
960 | MbusError::IoError
961 ) || !self.transport.is_connected();
962
963 if is_connection_loss {
964 client_log_debug!(
965 "connection loss detected during poll: error={:?}, pending_requests={}",
966 recv_error,
967 self.expected_responses.len()
968 );
969 self.fail_all_pending_requests(MbusError::ConnectionLost);
970 let _ = self.transport.disconnect();
971 self.rxed_frame.clear();
972 } else {
973 client_log_trace!("non-fatal recv status during poll: {:?}", recv_error);
974 }
975 }
976 }
977
978 self.handle_timeouts();
980 }
981
982 fn fail_all_pending_requests(&mut self, error: MbusError) {
983 let pending_count = self.expected_responses.len();
984 client_log_debug!(
985 "failing {} pending request(s) with error {:?}",
986 pending_count,
987 error
988 );
989 while let Some(response) = self.expected_responses.pop() {
990 self.app.request_failed(
991 response.txn_id,
992 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
993 error,
994 );
995 }
996 self.next_timeout_check = None;
997 }
998
999 fn handle_timeouts(&mut self) {
1014 if self.expected_responses.is_empty() {
1015 self.next_timeout_check = None;
1016 return;
1017 }
1018
1019 let current_millis = self.app.current_millis();
1020
1021 if let Some(check_at) = self.next_timeout_check
1023 && current_millis < check_at
1024 {
1025 client_log_trace!(
1026 "skipping timeout scan until {}, current_millis={}",
1027 check_at,
1028 current_millis
1029 );
1030 return;
1031 }
1032
1033 let response_timeout_ms = self.response_timeout_ms();
1034 let retry_backoff = self.config.retry_backoff_strategy();
1035 let retry_jitter = self.config.retry_jitter_strategy();
1036 let retry_random_fn = self.config.retry_random_fn();
1037 let expected_responses = &mut self.expected_responses;
1038 let mut i = 0;
1039 let mut new_next_check = u64::MAX;
1040
1041 while i < expected_responses.len() {
1042 let expected_response = &mut expected_responses[i];
1043 if let Some(retry_at) = expected_response.next_retry_timestamp {
1045 if current_millis >= retry_at {
1046 client_log_debug!(
1047 "retry due now: txn_id={}, unit_id_or_slave_addr={}, retry_attempt_index={}, retries_left={}",
1048 expected_response.txn_id,
1049 expected_response.unit_id_or_slave_addr,
1050 expected_response.retry_attempt_index.saturating_add(1),
1051 expected_response.retries_left
1052 );
1053 if let Err(_e) = self.transport.send(&expected_response.original_adu) {
1054 let response = expected_responses.swap_remove(i);
1057 client_log_debug!(
1058 "retry send failed: txn_id={}, unit_id_or_slave_addr={}; dropping request",
1059 response.txn_id,
1060 response.unit_id_or_slave_addr
1061 );
1062 self.app.request_failed(
1063 response.txn_id,
1064 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1065 MbusError::SendFailed,
1066 );
1067 continue;
1068 }
1069
1070 expected_response.retries_left =
1071 expected_response.retries_left.saturating_sub(1);
1072 expected_response.retry_attempt_index =
1073 expected_response.retry_attempt_index.saturating_add(1);
1074 expected_response.sent_timestamp = current_millis;
1075 expected_response.next_retry_timestamp = None;
1076
1077 let expires_at = current_millis.saturating_add(response_timeout_ms);
1078 if expires_at < new_next_check {
1079 new_next_check = expires_at;
1080 }
1081 i += 1;
1082 continue;
1083 }
1084
1085 if retry_at < new_next_check {
1086 new_next_check = retry_at;
1087 }
1088 i += 1;
1089 continue;
1090 }
1091
1092 let expires_at = expected_response
1094 .sent_timestamp
1095 .saturating_add(response_timeout_ms);
1096
1097 if current_millis > expires_at {
1098 if expected_response.retries_left == 0 {
1099 let response = expected_responses.swap_remove(i);
1102 client_log_debug!(
1103 "request exhausted retries: txn_id={}, unit_id_or_slave_addr={}",
1104 response.txn_id,
1105 response.unit_id_or_slave_addr
1106 );
1107 self.app.request_failed(
1108 response.txn_id,
1109 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1110 MbusError::NoRetriesLeft,
1111 );
1112 continue;
1113 }
1114
1115 let next_attempt = expected_response.retry_attempt_index.saturating_add(1);
1116 let base_delay_ms = retry_backoff.delay_ms_for_retry(next_attempt);
1117 let retry_delay_ms = retry_jitter.apply(base_delay_ms, retry_random_fn) as u64;
1118 let retry_at = current_millis.saturating_add(retry_delay_ms);
1119 expected_response.next_retry_timestamp = Some(retry_at);
1120 client_log_debug!(
1121 "scheduling retry: txn_id={}, unit_id_or_slave_addr={}, next_attempt={}, delay_ms={}, retry_at={}",
1122 expected_response.txn_id,
1123 expected_response.unit_id_or_slave_addr,
1124 next_attempt,
1125 retry_delay_ms,
1126 retry_at
1127 );
1128
1129 if retry_delay_ms == 0 {
1132 client_log_trace!(
1133 "retry delay is zero; retry will be processed in the same poll cycle for txn_id={}",
1134 expected_response.txn_id
1135 );
1136 continue;
1137 }
1138
1139 if retry_at < new_next_check {
1140 new_next_check = retry_at;
1141 }
1142 i += 1;
1143 continue;
1144 }
1145
1146 if expires_at < new_next_check {
1147 new_next_check = expires_at;
1148 }
1149 i += 1;
1150 }
1151
1152 if new_next_check != u64::MAX {
1153 self.next_timeout_check = Some(new_next_check);
1154 } else {
1155 self.next_timeout_check = None;
1156 }
1157 }
1158
1159 fn add_an_expectation(
1160 &mut self,
1161 txn_id: u16,
1162 unit_id_slave_addr: UnitIdOrSlaveAddr,
1163 frame: &heapless::Vec<u8, MAX_ADU_FRAME_LEN>,
1164 operation_meta: OperationMeta,
1165 handler: ResponseHandler<TRANSPORT, APP, N>,
1166 ) -> Result<(), MbusError> {
1167 client_log_trace!(
1168 "queueing expected response: txn_id={}, unit_id_or_slave_addr={}, queue_len_before={}",
1169 txn_id,
1170 unit_id_slave_addr.get(),
1171 self.expected_responses.len()
1172 );
1173 self.expected_responses
1174 .push(ExpectedResponse {
1175 txn_id,
1176 unit_id_or_slave_addr: unit_id_slave_addr.get(),
1177 original_adu: frame.clone(),
1178 sent_timestamp: self.app.current_millis(),
1179 retries_left: self.retry_attempts(),
1180 retry_attempt_index: 0,
1181 next_retry_timestamp: None,
1182 handler,
1183 operation_meta,
1184 })
1185 .map_err(|_| MbusError::TooManyRequests)?;
1186 Ok(())
1187 }
1188}
1189
1190impl<TRANSPORT: Transport, APP: ClientCommon, const N: usize> ClientServices<TRANSPORT, APP, N> {
1192 pub fn new(
1194 mut transport: TRANSPORT,
1195 app: APP,
1196 config: ModbusConfig,
1197 ) -> Result<Self, MbusError> {
1198 let transport_type = transport.transport_type();
1199 if matches!(
1200 transport_type,
1201 TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1202 ) && N != 1
1203 {
1204 return Err(MbusError::InvalidNumOfExpectedRsps);
1205 }
1206
1207 transport
1208 .connect(&config)
1209 .map_err(|_e| MbusError::ConnectionFailed)?;
1210
1211 client_log_debug!(
1212 "client created with transport_type={:?}, queue_capacity={}",
1213 transport_type,
1214 N
1215 );
1216
1217 Ok(Self {
1218 app,
1219 transport,
1220 rxed_frame: Vec::new(),
1221 config,
1222 expected_responses: Vec::new(),
1223 next_timeout_check: None,
1224 })
1225 }
1226
1227 pub fn app(&self) -> &APP {
1232 &self.app
1233 }
1234
1235 pub fn is_connected(&self) -> bool {
1237 self.transport.is_connected()
1238 }
1239
1240 pub fn reconnect(&mut self) -> Result<(), MbusError>
1251 where
1252 TRANSPORT::Error: Into<MbusError>,
1253 {
1254 client_log_debug!(
1255 "reconnect requested; pending_requests={}",
1256 self.expected_responses.len()
1257 );
1258 self.fail_all_pending_requests(MbusError::ConnectionLost);
1259 self.rxed_frame.clear();
1260 self.next_timeout_check = None;
1261
1262 let _ = self.transport.disconnect();
1263 self.transport.connect(&self.config).map_err(|e| e.into())
1264 }
1265
1266 pub fn new_serial(
1275 mut transport: TRANSPORT,
1276 app: APP,
1277 config: ModbusSerialConfig,
1278 ) -> Result<Self, MbusError>
1279 where
1280 [(); N]: SerialQueueSizeOne,
1281 {
1282 let transport_type = transport.transport_type();
1283 if !matches!(
1284 transport_type,
1285 TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1286 ) {
1287 return Err(MbusError::InvalidTransport);
1288 }
1289
1290 let config = ModbusConfig::Serial(config);
1291 transport
1292 .connect(&config)
1293 .map_err(|_e| MbusError::ConnectionFailed)?;
1294
1295 client_log_debug!("serial client created with queue_capacity={}", N);
1296
1297 Ok(Self {
1298 app,
1299 transport,
1300 rxed_frame: Vec::new(),
1301 config,
1302 expected_responses: Vec::new(),
1303 next_timeout_check: None,
1304 })
1305 }
1306
1307 fn response_timeout_ms(&self) -> u64 {
1309 match &self.config {
1310 ModbusConfig::Tcp(config) => config.response_timeout_ms as u64,
1311 ModbusConfig::Serial(config) => config.response_timeout_ms as u64,
1312 }
1313 }
1314
1315 fn retry_attempts(&self) -> u8 {
1317 match &self.config {
1318 ModbusConfig::Tcp(config) => config.retry_attempts,
1319 ModbusConfig::Serial(config) => config.retry_attempts,
1320 }
1321 }
1322
1323 fn ingest_frame(&mut self) -> Result<usize, MbusError> {
1325 let frame = self.rxed_frame.as_slice();
1326 let transport_type = self.transport.transport_type();
1327
1328 client_log_trace!(
1329 "attempting frame ingest: transport_type={:?}, buffer_len={}",
1330 transport_type,
1331 frame.len()
1332 );
1333
1334 let expected_length = match derive_length_from_bytes(frame, transport_type) {
1335 Some(len) => len,
1336 None => return Err(MbusError::BufferTooSmall),
1337 };
1338
1339 client_log_trace!("derived expected frame length={}", expected_length);
1340
1341 if expected_length > MAX_ADU_FRAME_LEN {
1342 client_log_debug!(
1343 "derived frame length {} exceeds MAX_ADU_FRAME_LEN {}",
1344 expected_length,
1345 MAX_ADU_FRAME_LEN
1346 );
1347 return Err(MbusError::BasicParseError);
1348 }
1349
1350 if self.rxed_frame.len() < expected_length {
1351 return Err(MbusError::BufferTooSmall);
1352 }
1353
1354 let message = match common::decompile_adu_frame(&frame[..expected_length], transport_type) {
1355 Ok(value) => value,
1356 Err(err) => {
1357 client_log_debug!(
1358 "decompile_adu_frame failed for {} bytes: {:?}",
1359 expected_length,
1360 err
1361 );
1362 return Err(err); }
1364 };
1365 use mbus_core::data_unit::common::AdditionalAddress;
1366 use mbus_core::transport::TransportType::*;
1367 let message = match self.transport.transport_type() {
1368 StdTcp | CustomTcp => {
1369 let mbap_header = match message.additional_address() {
1370 AdditionalAddress::MbapHeader(header) => header,
1371 _ => return Ok(expected_length),
1372 };
1373 let additional_addr = AdditionalAddress::MbapHeader(*mbap_header);
1374 ModbusMessage::new(additional_addr, message.pdu)
1375 }
1376 StdSerial(_) | CustomSerial(_) => {
1377 let slave_addr = match message.additional_address() {
1378 AdditionalAddress::SlaveAddress(addr) => addr.address(),
1379 _ => return Ok(expected_length),
1380 };
1381
1382 let additional_address =
1383 AdditionalAddress::SlaveAddress(SlaveAddress::new(slave_addr)?);
1384 ModbusMessage::new(additional_address, message.pdu)
1385 }
1386 };
1387
1388 self.dispatch_response(&message);
1389 client_log_trace!("frame dispatch complete for {} bytes", expected_length);
1390
1391 Ok(expected_length)
1392 }
1393}
1394
1395#[cfg(test)]
1396mod tests {
1397 use super::*;
1398 use crate::app::CoilResponse;
1399 use crate::app::DiagnosticsResponse;
1400 use crate::app::DiscreteInputResponse;
1401 use crate::app::FifoQueueResponse;
1402 use crate::app::FileRecordResponse;
1403 use crate::app::RegisterResponse;
1404 use crate::services::coil::Coils;
1405
1406 use crate::services::diagnostic::ConformityLevel;
1407 use crate::services::diagnostic::DeviceIdentificationResponse;
1408 use crate::services::diagnostic::ObjectId;
1409 use crate::services::discrete_input::DiscreteInputs;
1410 use crate::services::fifo_queue::FifoQueue;
1411 use crate::services::file_record::MAX_SUB_REQUESTS_PER_PDU;
1412 use crate::services::file_record::SubRequest;
1413 use crate::services::file_record::SubRequestParams;
1414 use crate::services::register::Registers;
1415 use core::cell::RefCell; use core::str::FromStr;
1417 use heapless::Deque;
1418 use heapless::Vec;
1419 use mbus_core::errors::MbusError;
1420 use mbus_core::function_codes::public::DiagnosticSubFunction;
1421 use mbus_core::transport::TransportType;
1422 use mbus_core::transport::checksum;
1423 use mbus_core::transport::{
1424 BackoffStrategy, BaudRate, JitterStrategy, ModbusConfig, ModbusSerialConfig,
1425 ModbusTcpConfig, Parity, SerialMode,
1426 };
1427
1428 const MOCK_DEQUE_CAPACITY: usize = 10; fn rand_zero() -> u32 {
1431 0
1432 }
1433
1434 fn rand_upper_percent_20() -> u32 {
1435 40
1436 }
1437
1438 fn make_serial_config() -> ModbusSerialConfig {
1439 ModbusSerialConfig {
1440 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
1441 mode: SerialMode::Rtu,
1442 baud_rate: BaudRate::Baud19200,
1443 data_bits: mbus_core::transport::DataBits::Eight,
1444 stop_bits: 1,
1445 parity: Parity::Even,
1446 response_timeout_ms: 100,
1447 retry_attempts: 0,
1448 retry_backoff_strategy: BackoffStrategy::Immediate,
1449 retry_jitter_strategy: JitterStrategy::None,
1450 retry_random_fn: None,
1451 }
1452 }
1453
1454 fn make_serial_client() -> ClientServices<MockTransport, MockApp, 1> {
1455 let transport = MockTransport {
1456 transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
1457 ..Default::default()
1458 };
1459 let app = MockApp::default();
1460 ClientServices::<MockTransport, MockApp, 1>::new_serial(
1461 transport,
1462 app,
1463 make_serial_config(),
1464 )
1465 .unwrap()
1466 }
1467
1468 fn make_rtu_exception_adu(
1469 unit_id: UnitIdOrSlaveAddr,
1470 function_code: u8,
1471 exception_code: u8,
1472 ) -> Vec<u8, MAX_ADU_FRAME_LEN> {
1473 let mut frame = Vec::new();
1474 frame.push(unit_id.get()).unwrap();
1475 frame.push(function_code | 0x80).unwrap();
1476 frame.push(exception_code).unwrap();
1477 let crc = checksum::crc16(frame.as_slice()).to_le_bytes();
1478 frame.extend_from_slice(&crc).unwrap();
1479 frame
1480 }
1481
1482 #[derive(Debug, Default)]
1484 struct MockTransport {
1485 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>>,
1488 pub connect_should_fail: bool,
1489 pub send_should_fail: bool,
1490 pub is_connected_flag: RefCell<bool>,
1491 pub transport_type: Option<TransportType>,
1492 }
1493
1494 impl Transport for MockTransport {
1495 type Error = MbusError;
1496
1497 fn connect(&mut self, _config: &ModbusConfig) -> Result<(), Self::Error> {
1498 if self.connect_should_fail {
1499 return Err(MbusError::ConnectionFailed);
1500 }
1501 *self.is_connected_flag.borrow_mut() = true;
1502 Ok(())
1503 }
1504
1505 fn disconnect(&mut self) -> Result<(), Self::Error> {
1506 *self.is_connected_flag.borrow_mut() = false;
1507 Ok(())
1508 }
1509
1510 fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
1511 if self.send_should_fail {
1512 return Err(MbusError::SendFailed);
1513 }
1514 let mut vec_adu = Vec::new();
1515 vec_adu
1516 .extend_from_slice(adu)
1517 .map_err(|_| MbusError::BufferLenMissmatch)?;
1518 self.sent_frames
1519 .borrow_mut()
1520 .push_back(vec_adu)
1521 .map_err(|_| MbusError::BufferLenMissmatch)?;
1522 Ok(())
1523 }
1524
1525 fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
1526 if let Some(err) = self.recv_error.borrow_mut().take() {
1527 return Err(err);
1528 }
1529 self.recv_frames
1530 .borrow_mut()
1531 .pop_front()
1532 .ok_or(MbusError::Timeout)
1533 }
1534
1535 fn is_connected(&self) -> bool {
1536 *self.is_connected_flag.borrow()
1537 }
1538
1539 fn transport_type(&self) -> TransportType {
1540 self.transport_type.unwrap_or(TransportType::StdTcp)
1541 }
1542 }
1543
1544 #[derive(Debug, Default)]
1546 struct MockApp {
1547 pub received_coil_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr, Coils), 10>>, pub received_write_single_coil_responses:
1549 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, bool), 10>>,
1550 pub received_write_multiple_coils_responses:
1551 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1552 pub received_discrete_input_responses:
1553 RefCell<Vec<(u16, UnitIdOrSlaveAddr, DiscreteInputs, u16), 10>>,
1554 pub received_holding_register_responses:
1555 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1556 pub received_input_register_responses:
1557 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1558 pub received_write_single_register_responses:
1559 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1560 pub received_write_multiple_register_responses:
1561 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1562 pub received_read_write_multiple_registers_responses:
1563 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers), 10>>,
1564 pub received_mask_write_register_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1565 pub received_read_fifo_queue_responses:
1566 RefCell<Vec<(u16, UnitIdOrSlaveAddr, FifoQueue), 10>>,
1567 pub received_read_file_record_responses: RefCell<
1568 Vec<
1569 (
1570 u16,
1571 UnitIdOrSlaveAddr,
1572 Vec<SubRequestParams, MAX_SUB_REQUESTS_PER_PDU>,
1573 ),
1574 10,
1575 >,
1576 >,
1577 pub received_write_file_record_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1578 pub received_read_device_id_responses:
1579 RefCell<Vec<(u16, UnitIdOrSlaveAddr, DeviceIdentificationResponse), 10>>,
1580 pub failed_requests: RefCell<Vec<(u16, UnitIdOrSlaveAddr, MbusError), 10>>,
1581
1582 pub current_time: RefCell<u64>, }
1584
1585 impl CoilResponse for MockApp {
1586 fn read_coils_response(
1587 &mut self,
1588 txn_id: u16,
1589 unit_id_slave_addr: UnitIdOrSlaveAddr,
1590 coils: &Coils,
1591 ) {
1592 self.received_coil_responses
1593 .borrow_mut()
1594 .push((txn_id, unit_id_slave_addr, coils.clone()))
1595 .unwrap();
1596 }
1597
1598 fn read_single_coil_response(
1599 &mut self,
1600 txn_id: u16,
1601 unit_id_slave_addr: UnitIdOrSlaveAddr,
1602 address: u16,
1603 value: bool,
1604 ) {
1605 let mut values_vec = [0x00, 1];
1607 values_vec[0] = if value { 0x01 } else { 0x00 }; let coils = Coils::new(address, 1)
1609 .unwrap()
1610 .with_values(&values_vec, 1)
1611 .unwrap();
1612 self.received_coil_responses
1613 .borrow_mut()
1614 .push((txn_id, unit_id_slave_addr, coils))
1615 .unwrap();
1616 }
1617
1618 fn write_single_coil_response(
1619 &mut self,
1620 txn_id: u16,
1621 unit_id_slave_addr: UnitIdOrSlaveAddr,
1622 address: u16,
1623 value: bool,
1624 ) {
1625 self.received_write_single_coil_responses
1626 .borrow_mut()
1627 .push((txn_id, unit_id_slave_addr, address, value))
1628 .unwrap();
1629 }
1630
1631 fn write_multiple_coils_response(
1632 &mut self,
1633 txn_id: u16,
1634 unit_id_slave_addr: UnitIdOrSlaveAddr,
1635 address: u16,
1636 quantity: u16,
1637 ) {
1638 self.received_write_multiple_coils_responses
1639 .borrow_mut()
1640 .push((txn_id, unit_id_slave_addr, address, quantity))
1641 .unwrap();
1642 }
1643 }
1644
1645 impl DiscreteInputResponse for MockApp {
1646 fn read_multiple_discrete_inputs_response(
1647 &mut self,
1648 txn_id: u16,
1649 unit_id_slave_addr: UnitIdOrSlaveAddr,
1650 inputs: &DiscreteInputs,
1651 ) {
1652 self.received_discrete_input_responses
1653 .borrow_mut()
1654 .push((
1655 txn_id,
1656 unit_id_slave_addr,
1657 inputs.clone(),
1658 inputs.quantity(),
1659 ))
1660 .unwrap();
1661 }
1662
1663 fn read_single_discrete_input_response(
1664 &mut self,
1665 txn_id: u16,
1666 unit_id_slave_addr: UnitIdOrSlaveAddr,
1667 address: u16,
1668 value: bool,
1669 ) {
1670 let mut values = [0u8; mbus_core::models::discrete_input::MAX_DISCRETE_INPUT_BYTES];
1671 values[0] = if value { 0x01 } else { 0x00 };
1672 let inputs = DiscreteInputs::new(address, 1)
1673 .unwrap()
1674 .with_values(&values, 1)
1675 .unwrap();
1676 self.received_discrete_input_responses
1677 .borrow_mut()
1678 .push((txn_id, unit_id_slave_addr, inputs, 1))
1679 .unwrap();
1680 }
1681 }
1682
1683 impl RequestErrorNotifier for MockApp {
1684 fn request_failed(
1685 &mut self,
1686 txn_id: u16,
1687 unit_id_slave_addr: UnitIdOrSlaveAddr,
1688 error: MbusError,
1689 ) {
1690 self.failed_requests
1691 .borrow_mut()
1692 .push((txn_id, unit_id_slave_addr, error))
1693 .unwrap();
1694 }
1695 }
1696
1697 impl RegisterResponse for MockApp {
1698 fn read_multiple_holding_registers_response(
1699 &mut self,
1700 txn_id: u16,
1701 unit_id_slave_addr: UnitIdOrSlaveAddr,
1702 registers: &Registers,
1703 ) {
1704 let quantity = registers.quantity();
1705 self.received_holding_register_responses
1706 .borrow_mut()
1707 .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
1708 .unwrap();
1709 }
1710
1711 fn read_single_input_register_response(
1712 &mut self,
1713 txn_id: u16,
1714 unit_id_slave_addr: UnitIdOrSlaveAddr,
1715 address: u16,
1716 value: u16,
1717 ) {
1718 let values = [value];
1720 let registers = Registers::new(address, 1)
1721 .unwrap()
1722 .with_values(&values, 1)
1723 .unwrap();
1724 self.received_input_register_responses
1725 .borrow_mut()
1726 .push((txn_id, unit_id_slave_addr, registers, 1))
1727 .unwrap();
1728 }
1729
1730 fn read_single_holding_register_response(
1731 &mut self,
1732 txn_id: u16,
1733 unit_id_slave_addr: UnitIdOrSlaveAddr,
1734 address: u16,
1735 value: u16,
1736 ) {
1737 let data = [value];
1739 let registers = Registers::new(address, 1)
1741 .unwrap()
1742 .with_values(&data, 1)
1743 .unwrap();
1744
1745 self.received_holding_register_responses
1746 .borrow_mut()
1747 .push((txn_id, unit_id_slave_addr, registers, 1))
1748 .unwrap();
1749 }
1750
1751 fn read_multiple_input_registers_response(
1752 &mut self,
1753 txn_id: u16,
1754 unit_id_slave_addr: UnitIdOrSlaveAddr,
1755 registers: &Registers,
1756 ) {
1757 let quantity = registers.quantity();
1758 self.received_input_register_responses
1759 .borrow_mut()
1760 .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
1761 .unwrap();
1762 }
1763
1764 fn write_single_register_response(
1765 &mut self,
1766 txn_id: u16,
1767 unit_id_slave_addr: UnitIdOrSlaveAddr,
1768 address: u16,
1769 value: u16,
1770 ) {
1771 self.received_write_single_register_responses
1772 .borrow_mut()
1773 .push((txn_id, unit_id_slave_addr, address, value))
1774 .unwrap();
1775 }
1776
1777 fn write_multiple_registers_response(
1778 &mut self,
1779 txn_id: u16,
1780 unit_id_slave_addr: UnitIdOrSlaveAddr,
1781 address: u16,
1782 quantity: u16,
1783 ) {
1784 self.received_write_multiple_register_responses
1785 .borrow_mut()
1786 .push((txn_id, unit_id_slave_addr, address, quantity))
1787 .unwrap();
1788 }
1789
1790 fn read_write_multiple_registers_response(
1791 &mut self,
1792 txn_id: u16,
1793 unit_id_slave_addr: UnitIdOrSlaveAddr,
1794 registers: &Registers,
1795 ) {
1796 self.received_read_write_multiple_registers_responses
1797 .borrow_mut()
1798 .push((txn_id, unit_id_slave_addr, registers.clone()))
1799 .unwrap();
1800 }
1801
1802 fn mask_write_register_response(
1803 &mut self,
1804 txn_id: u16,
1805 unit_id_slave_addr: UnitIdOrSlaveAddr,
1806 ) {
1807 self.received_mask_write_register_responses
1808 .borrow_mut()
1809 .push((txn_id, unit_id_slave_addr))
1810 .unwrap();
1811 }
1812
1813 fn read_single_register_response(
1814 &mut self,
1815 txn_id: u16,
1816 unit_id_slave_addr: UnitIdOrSlaveAddr,
1817 address: u16,
1818 value: u16,
1819 ) {
1820 let data = [value];
1822 let registers = Registers::new(address, 1)
1824 .unwrap()
1825 .with_values(&data, 1)
1826 .unwrap();
1827
1828 self.received_holding_register_responses
1829 .borrow_mut()
1830 .push((txn_id, unit_id_slave_addr, registers, 1))
1831 .unwrap();
1832 }
1833 }
1834
1835 impl FifoQueueResponse for MockApp {
1836 fn read_fifo_queue_response(
1837 &mut self,
1838 txn_id: u16,
1839 unit_id_slave_addr: UnitIdOrSlaveAddr,
1840 fifo_queue: &FifoQueue,
1841 ) {
1842 self.received_read_fifo_queue_responses
1843 .borrow_mut()
1844 .push((txn_id, unit_id_slave_addr, fifo_queue.clone()))
1845 .unwrap();
1846 }
1847 }
1848
1849 impl FileRecordResponse for MockApp {
1850 fn read_file_record_response(
1851 &mut self,
1852 txn_id: u16,
1853 unit_id_slave_addr: UnitIdOrSlaveAddr,
1854 data: &[SubRequestParams],
1855 ) {
1856 let mut vec = Vec::new();
1857 vec.extend_from_slice(data).unwrap();
1858 self.received_read_file_record_responses
1859 .borrow_mut()
1860 .push((txn_id, unit_id_slave_addr, vec))
1861 .unwrap();
1862 }
1863 fn write_file_record_response(
1864 &mut self,
1865 txn_id: u16,
1866 unit_id_slave_addr: UnitIdOrSlaveAddr,
1867 ) {
1868 self.received_write_file_record_responses
1869 .borrow_mut()
1870 .push((txn_id, unit_id_slave_addr))
1871 .unwrap();
1872 }
1873 }
1874
1875 impl DiagnosticsResponse for MockApp {
1876 fn read_device_identification_response(
1877 &mut self,
1878 txn_id: u16,
1879 unit_id_slave_addr: UnitIdOrSlaveAddr,
1880 response: &DeviceIdentificationResponse,
1881 ) {
1882 self.received_read_device_id_responses
1883 .borrow_mut()
1884 .push((txn_id, unit_id_slave_addr, response.clone()))
1885 .unwrap();
1886 }
1887
1888 fn encapsulated_interface_transport_response(
1889 &mut self,
1890 _: u16,
1891 _: UnitIdOrSlaveAddr,
1892 _: EncapsulatedInterfaceType,
1893 _: &[u8],
1894 ) {
1895 }
1896
1897 fn diagnostics_response(
1898 &mut self,
1899 _: u16,
1900 _: UnitIdOrSlaveAddr,
1901 _: DiagnosticSubFunction,
1902 _: &[u16],
1903 ) {
1904 }
1905
1906 fn get_comm_event_counter_response(
1907 &mut self,
1908 _: u16,
1909 _: UnitIdOrSlaveAddr,
1910 _: u16,
1911 _: u16,
1912 ) {
1913 }
1914
1915 fn get_comm_event_log_response(
1916 &mut self,
1917 _: u16,
1918 _: UnitIdOrSlaveAddr,
1919 _: u16,
1920 _: u16,
1921 _: u16,
1922 _: &[u8],
1923 ) {
1924 }
1925
1926 fn read_exception_status_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: u8) {}
1927
1928 fn report_server_id_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: &[u8]) {}
1929 }
1930
1931 impl TimeKeeper for MockApp {
1932 fn current_millis(&self) -> u64 {
1933 *self.current_time.borrow()
1934 }
1935 }
1936
1937 #[test]
1941 fn test_client_services_new_success() {
1942 let transport = MockTransport::default();
1943 let app = MockApp::default();
1944 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1945
1946 let client_services =
1947 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
1948 assert!(client_services.is_ok());
1949 assert!(client_services.unwrap().transport.is_connected());
1950 }
1951
1952 #[test]
1954 fn test_client_services_new_connection_failure() {
1955 let mut transport = MockTransport::default();
1956 transport.connect_should_fail = true;
1957 let app = MockApp::default();
1958 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1959
1960 let client_services =
1961 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
1962 assert!(client_services.is_err());
1963 assert_eq!(client_services.unwrap_err(), MbusError::ConnectionFailed);
1964 }
1965
1966 #[test]
1967 fn test_client_services_new_serial_success() {
1968 let transport = MockTransport {
1969 transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
1970 ..Default::default()
1971 };
1972 let app = MockApp::default();
1973 let serial_config = ModbusSerialConfig {
1974 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
1975 mode: SerialMode::Rtu,
1976 baud_rate: BaudRate::Baud19200,
1977 data_bits: mbus_core::transport::DataBits::Eight,
1978 stop_bits: 1,
1979 parity: Parity::Even,
1980 response_timeout_ms: 1000,
1981 retry_attempts: 1,
1982 retry_backoff_strategy: BackoffStrategy::Immediate,
1983 retry_jitter_strategy: JitterStrategy::None,
1984 retry_random_fn: None,
1985 };
1986
1987 let client_services =
1988 ClientServices::<MockTransport, MockApp, 1>::new_serial(transport, app, serial_config);
1989 assert!(client_services.is_ok());
1990 }
1991
1992 #[test]
1993 fn test_reconnect_success_flushes_pending_requests() {
1994 let transport = MockTransport::default();
1995 let app = MockApp::default();
1996 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1997 let mut client_services =
1998 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
1999
2000 let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
2001 client_services.read_single_coil(10, unit_id, 0).unwrap();
2002 assert_eq!(client_services.expected_responses.len(), 1);
2003
2004 let reconnect_result = client_services.reconnect();
2005 assert!(reconnect_result.is_ok());
2006 assert!(client_services.is_connected());
2007 assert!(client_services.expected_responses.is_empty());
2008
2009 let failed_requests = client_services.app().failed_requests.borrow();
2010 assert_eq!(failed_requests.len(), 1);
2011 assert_eq!(failed_requests[0].0, 10);
2012 assert_eq!(failed_requests[0].2, MbusError::ConnectionLost);
2013 }
2014
2015 #[test]
2016 fn test_reconnect_failure_propagates_connect_error() {
2017 let transport = MockTransport::default();
2018 let app = MockApp::default();
2019 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2020 let mut client_services =
2021 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2022
2023 client_services.transport.connect_should_fail = true;
2024 let reconnect_result = client_services.reconnect();
2025
2026 assert!(reconnect_result.is_err());
2027 assert_eq!(reconnect_result.unwrap_err(), MbusError::ConnectionFailed);
2028 assert!(!client_services.is_connected());
2029 }
2030
2031 #[test]
2033 fn test_read_multiple_coils_sends_valid_adu() {
2034 let transport = MockTransport::default();
2035 let app = MockApp::default();
2036 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2037 let mut client_services =
2038 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2039
2040 let txn_id = 0x0001;
2041 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2042 let address = 0x0000;
2043 let quantity = 8;
2044 client_services
2045 .read_multiple_coils(txn_id, unit_id, address, quantity)
2046 .unwrap();
2047
2048 let sent_frames = client_services.transport.sent_frames.borrow();
2049 assert_eq!(sent_frames.len(), 1);
2050 let sent_adu = sent_frames.front().unwrap();
2051
2052 #[rustfmt::skip]
2054 let expected_adu: [u8; 12] = [
2055 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, ];
2063 assert_eq!(sent_adu.as_slice(), &expected_adu);
2064 }
2065
2066 #[test]
2068 fn test_read_multiple_coils_invalid_quantity() {
2069 let transport = MockTransport::default();
2070 let app = MockApp::default();
2071 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2072 let mut client_services =
2073 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2074
2075 let txn_id = 0x0001;
2076 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2077 let address = 0x0000;
2078 let quantity = 0; let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); assert_eq!(result.unwrap_err(), MbusError::InvalidQuantity);
2082 }
2083
2084 #[test]
2086 fn test_read_multiple_coils_send_failure() {
2087 let mut transport = MockTransport::default();
2088 transport.send_should_fail = true;
2089 let app = MockApp::default();
2090 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2091 let mut client_services =
2092 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2093
2094 let txn_id = 0x0001;
2095 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2096 let address = 0x0000;
2097 let quantity = 8;
2098
2099 let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); assert_eq!(result.unwrap_err(), MbusError::SendFailed);
2101 }
2102
2103 #[test]
2105 fn test_ingest_frame_wrong_fc() {
2106 let transport = MockTransport::default();
2107 let app = MockApp::default();
2108 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2109 let mut client_services =
2110 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2111
2112 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x03, 0x01, 0xB3];
2114
2115 client_services
2116 .transport
2117 .recv_frames
2118 .borrow_mut()
2119 .push_back(Vec::from_slice(&response_adu).unwrap())
2120 .unwrap();
2121 client_services.poll();
2122
2123 let received_responses = client_services.app().received_coil_responses.borrow();
2124 assert!(received_responses.is_empty());
2125 }
2126
2127 #[test]
2129 fn test_ingest_frame_malformed_adu() {
2130 let transport = MockTransport::default();
2131 let app = MockApp::default();
2132 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2133 let mut client_services =
2134 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2135
2136 let malformed_adu = [0x01, 0x02, 0x03];
2138
2139 client_services
2140 .transport
2141 .recv_frames
2142 .borrow_mut()
2143 .push_back(Vec::from_slice(&malformed_adu).unwrap())
2144 .unwrap();
2145 client_services.poll();
2146
2147 let received_responses = client_services.app().received_coil_responses.borrow();
2148 assert!(received_responses.is_empty());
2149 }
2150
2151 #[test]
2153 fn test_ingest_frame_unknown_txn_id() {
2154 let transport = MockTransport::default();
2155 let app = MockApp::default();
2156 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2157 let mut client_services =
2158 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2159
2160 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2162
2163 client_services
2164 .transport
2165 .recv_frames
2166 .borrow_mut()
2167 .push_back(Vec::from_slice(&response_adu).unwrap())
2168 .unwrap();
2169 client_services.poll();
2170
2171 let received_responses = client_services.app().received_coil_responses.borrow();
2172 assert!(received_responses.is_empty());
2173 }
2174
2175 #[test]
2177 fn test_ingest_frame_pdu_parse_failure() {
2178 let transport = MockTransport::default();
2179 let app = MockApp::default();
2180 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2181 let mut client_services =
2182 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2183
2184 let txn_id = 0x0001;
2185 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2186 let address = 0x0000;
2187 let quantity = 8;
2188 client_services
2189 .read_multiple_coils(txn_id, unit_id, address, quantity) .unwrap();
2191
2192 let response_adu = [
2196 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x01, 0xB3, 0x00,
2197 ]; client_services
2200 .transport
2201 .recv_frames
2202 .borrow_mut()
2203 .push_back(Vec::from_slice(&response_adu).unwrap())
2204 .unwrap();
2205 client_services.poll();
2206
2207 let received_responses = client_services.app().received_coil_responses.borrow();
2208 assert!(received_responses.is_empty());
2209 assert!(client_services.expected_responses.is_empty());
2211 }
2212
2213 #[test]
2215 fn test_client_services_read_single_coil_e2e_success() {
2216 let transport = MockTransport::default();
2217 let app = MockApp::default();
2218 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2219 let mut client_services =
2220 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2221
2222 let txn_id = 0x0002;
2223 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2224 let address = 0x0005;
2225
2226 client_services .read_single_coil(txn_id, unit_id, address)
2229 .unwrap();
2230
2231 let sent_adu = client_services
2233 .transport
2234 .sent_frames
2235 .borrow_mut()
2236 .pop_front()
2237 .unwrap();
2238 #[rustfmt::skip]
2240 let expected_adu: [u8; 12] = [
2241 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x05, 0x00, 0x01, ];
2249 assert_eq!(sent_adu.as_slice(), &expected_adu);
2250
2251 let response_adu = [0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x01];
2255
2256 client_services
2258 .transport
2259 .recv_frames
2260 .borrow_mut()
2261 .push_back(Vec::from_slice(&response_adu).unwrap())
2262 .unwrap();
2263 client_services.poll();
2264
2265 let received_responses = client_services.app().received_coil_responses.borrow();
2267 assert_eq!(received_responses.len(), 1);
2268
2269 let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2270 let rcv_quantity = rcv_coils.quantity();
2271 assert_eq!(*rcv_txn_id, txn_id);
2272 assert_eq!(*rcv_unit_id, unit_id);
2273 assert_eq!(rcv_coils.from_address(), address);
2274 assert_eq!(rcv_coils.quantity(), 1); assert_eq!(&rcv_coils.values()[..1], &[0x01]); assert_eq!(rcv_quantity, 1);
2277
2278 assert!(client_services.expected_responses.is_empty());
2280 }
2281
2282 #[test]
2284 fn test_read_single_coil_request_sends_valid_adu() {
2285 let transport = MockTransport::default();
2286 let app = MockApp::default();
2287 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2288 let mut client_services =
2289 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2290
2291 let txn_id = 0x0002;
2292 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2293 let address = 0x0005;
2294
2295 client_services
2296 .read_single_coil(txn_id, unit_id, address) .unwrap();
2298
2299 let sent_frames = client_services.transport.sent_frames.borrow();
2300 assert_eq!(sent_frames.len(), 1);
2301 let sent_adu = sent_frames.front().unwrap();
2302
2303 #[rustfmt::skip]
2305 let expected_adu: [u8; 12] = [
2306 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x05, 0x00, 0x01, ];
2314 assert_eq!(sent_adu.as_slice(), &expected_adu);
2315
2316 assert_eq!(client_services.expected_responses.len(), 1); let single_read = client_services.expected_responses[0]
2319 .operation_meta
2320 .is_single();
2321 assert!(single_read);
2322 }
2323
2324 #[test]
2326 fn test_write_single_coil_sends_valid_adu() {
2327 let transport = MockTransport::default();
2328 let app = MockApp::default();
2329 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2330 let mut client_services =
2331 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2332
2333 let txn_id = 0x0003;
2334 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2335 let address = 0x000A;
2336 let value = true;
2337
2338 client_services
2339 .write_single_coil(txn_id, unit_id, address, value) .unwrap();
2341
2342 let sent_frames = client_services.transport.sent_frames.borrow();
2343 assert_eq!(sent_frames.len(), 1);
2344 let sent_adu = sent_frames.front().unwrap();
2345
2346 #[rustfmt::skip]
2348 let expected_adu: [u8; 12] = [
2349 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, ];
2357 assert_eq!(sent_adu.as_slice(), &expected_adu);
2358
2359 assert_eq!(client_services.expected_responses.len(), 1);
2361 let expected_address = client_services.expected_responses[0]
2362 .operation_meta
2363 .address();
2364 let expected_value = client_services.expected_responses[0].operation_meta.value() != 0;
2365
2366 assert_eq!(expected_address, address);
2367 assert_eq!(expected_value, value);
2368 }
2369
2370 #[test]
2372 fn test_client_services_write_single_coil_e2e_success() {
2373 let transport = MockTransport::default();
2374 let app = MockApp::default();
2375 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2376 let mut client_services =
2377 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2378
2379 let txn_id = 0x0003;
2380 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2381 let address = 0x000A;
2382 let value = true;
2383
2384 client_services .write_single_coil(txn_id, unit_id, address, value)
2387 .unwrap();
2388
2389 let sent_adu = client_services
2391 .transport
2392 .sent_frames
2393 .borrow_mut()
2394 .pop_front()
2395 .unwrap();
2396 #[rustfmt::skip]
2397 let expected_request_adu: [u8; 12] = [
2398 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, ];
2406 assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2407
2408 let response_adu = [
2411 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00,
2412 ];
2413
2414 client_services
2416 .transport
2417 .recv_frames
2418 .borrow_mut()
2419 .push_back(Vec::from_slice(&response_adu).unwrap())
2420 .unwrap();
2421 client_services.poll();
2422
2423 let received_responses = client_services
2425 .app
2426 .received_write_single_coil_responses
2427 .borrow();
2428 assert_eq!(received_responses.len(), 1);
2429
2430 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
2431 assert_eq!(*rcv_txn_id, txn_id);
2432 assert_eq!(*rcv_unit_id, unit_id);
2433 assert_eq!(*rcv_address, address);
2434 assert_eq!(*rcv_value, value);
2435
2436 assert!(client_services.expected_responses.is_empty());
2438 }
2439
2440 #[test]
2442 fn test_write_multiple_coils_sends_valid_adu() {
2443 let transport = MockTransport::default();
2444 let app = MockApp::default();
2445 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2446 let mut client_services =
2447 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2448
2449 let txn_id = 0x0004;
2450 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2451 let address = 0x0000;
2452 let quantity = 10;
2453
2454 let mut values = Coils::new(address, quantity).unwrap();
2456 for i in 0..quantity {
2457 values.set_value(address + i, i % 2 == 0).unwrap();
2458 }
2459
2460 client_services
2461 .write_multiple_coils(txn_id, unit_id, address, &values) .unwrap();
2463
2464 let sent_frames = client_services.transport.sent_frames.borrow();
2465 assert_eq!(sent_frames.len(), 1);
2466 let sent_adu = sent_frames.front().unwrap();
2467
2468 #[rustfmt::skip]
2470 let expected_adu: [u8; 15] = [
2471 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0x55, 0x01, ];
2481 assert_eq!(sent_adu.as_slice(), &expected_adu);
2482
2483 assert_eq!(client_services.expected_responses.len(), 1);
2485 let expected_address = client_services.expected_responses[0]
2486 .operation_meta
2487 .address();
2488 let expected_quantity = client_services.expected_responses[0]
2489 .operation_meta
2490 .quantity();
2491 assert_eq!(expected_address, address);
2492 assert_eq!(expected_quantity, quantity);
2493 }
2494
2495 #[test]
2497 fn test_client_services_write_multiple_coils_e2e_success() {
2498 let transport = MockTransport::default();
2499 let app = MockApp::default();
2500 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2501 let mut client_services =
2502 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2503
2504 let txn_id = 0x0004;
2505 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2506 let address = 0x0000;
2507 let quantity = 10;
2508
2509 let mut values = Coils::new(address, quantity).unwrap();
2511 for i in 0..quantity {
2512 values.set_value(address + i, i % 2 == 0).unwrap();
2513 }
2514
2515 client_services .write_multiple_coils(txn_id, unit_id, address, &values)
2518 .unwrap();
2519
2520 let sent_adu = client_services
2522 .transport
2523 .sent_frames
2524 .borrow_mut()
2525 .pop_front()
2526 .unwrap();
2527 #[rustfmt::skip]
2528 let expected_request_adu: [u8; 15] = [
2529 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0x55, 0x01, ];
2539 assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2540
2541 let response_adu = [
2544 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A,
2545 ];
2546
2547 client_services
2549 .transport
2550 .recv_frames
2551 .borrow_mut()
2552 .push_back(Vec::from_slice(&response_adu).unwrap())
2553 .unwrap();
2554 client_services.poll();
2555
2556 let received_responses = client_services
2558 .app
2559 .received_write_multiple_coils_responses
2560 .borrow();
2561 assert_eq!(received_responses.len(), 1);
2562
2563 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
2564 assert_eq!(*rcv_txn_id, txn_id);
2565 assert_eq!(*rcv_unit_id, unit_id);
2566 assert_eq!(*rcv_address, address);
2567 assert_eq!(*rcv_quantity, quantity);
2568
2569 assert!(client_services.expected_responses.is_empty());
2571 }
2572
2573 #[test]
2575 fn test_client_services_read_coils_e2e_success() {
2576 let transport = MockTransport::default();
2577 let app = MockApp::default();
2578 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2579 let mut client_services =
2580 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2581
2582 let txn_id = 0x0001;
2583 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2584 let address = 0x0000;
2585 let quantity = 8;
2586 client_services
2587 .read_multiple_coils(txn_id, unit_id, address, quantity) .unwrap();
2589
2590 let sent_adu = client_services
2592 .transport
2593 .sent_frames
2594 .borrow_mut()
2595 .pop_front()
2596 .unwrap(); assert_eq!(
2599 sent_adu.as_slice(),
2600 &[
2601 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08
2602 ]
2603 );
2604
2605 assert_eq!(client_services.expected_responses.len(), 1); let from_address = client_services.expected_responses[0]
2608 .operation_meta
2609 .address();
2610 let expected_quantity = client_services.expected_responses[0]
2611 .operation_meta
2612 .quantity();
2613
2614 assert_eq!(expected_quantity, quantity);
2615 assert_eq!(from_address, address);
2616
2617 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2621
2622 client_services
2624 .transport
2625 .recv_frames
2626 .borrow_mut()
2627 .push_back(Vec::from_slice(&response_adu).unwrap())
2628 .unwrap();
2629 client_services.poll(); let received_responses = client_services.app().received_coil_responses.borrow();
2635 assert_eq!(received_responses.len(), 1);
2636
2637 let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2638 let rcv_quantity = rcv_coils.quantity();
2639 assert_eq!(*rcv_txn_id, txn_id);
2640 assert_eq!(*rcv_unit_id, unit_id);
2641 assert_eq!(rcv_coils.from_address(), address);
2642 assert_eq!(rcv_coils.quantity(), quantity);
2643 assert_eq!(&rcv_coils.values()[..1], &[0xB3]);
2644 assert_eq!(rcv_quantity, quantity);
2645
2646 assert!(client_services.expected_responses.is_empty());
2648 }
2649
2650 #[test]
2652 fn test_client_services_timeout_with_retry() {
2653 let transport = MockTransport::default();
2654 transport.recv_frames.borrow_mut().clear();
2656 let app = MockApp::default();
2657 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2658 tcp_config.response_timeout_ms = 100; tcp_config.retry_attempts = 1; let config = ModbusConfig::Tcp(tcp_config);
2661
2662 let mut client_services =
2663 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2664
2665 let txn_id = 0x0005;
2666 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2667 let address = 0x0000;
2668
2669 client_services
2670 .read_single_coil(txn_id, unit_id, address)
2671 .unwrap();
2672
2673 *client_services.app().current_time.borrow_mut() = 150;
2675 client_services.poll(); assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2680 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;
2685 client_services.poll(); assert!(client_services.expected_responses.is_empty());
2690 }
2692
2693 #[test]
2695 fn test_client_services_concurrent_timeouts() {
2696 let transport = MockTransport::default();
2697 let app = MockApp::default();
2698
2699 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2701 tcp_config.response_timeout_ms = 100;
2702 tcp_config.retry_attempts = 1;
2703 let config = ModbusConfig::Tcp(tcp_config);
2704
2705 let mut client_services =
2706 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2707
2708 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2709
2710 client_services
2712 .read_single_coil(1, unit_id, 0x0000)
2713 .unwrap();
2714 client_services
2715 .read_single_coil(2, unit_id, 0x0001)
2716 .unwrap();
2717
2718 assert_eq!(client_services.expected_responses.len(), 2);
2720 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2721
2722 *client_services.app().current_time.borrow_mut() = 150;
2724
2725 client_services.poll();
2727
2728 assert_eq!(client_services.expected_responses.len(), 2);
2730 assert_eq!(client_services.expected_responses[0].retries_left, 0);
2731 assert_eq!(client_services.expected_responses[1].retries_left, 0);
2732
2733 assert_eq!(client_services.transport.sent_frames.borrow().len(), 4);
2735
2736 *client_services.app().current_time.borrow_mut() = 300;
2738
2739 client_services.poll();
2741
2742 assert!(client_services.expected_responses.is_empty());
2744
2745 let failed_requests = client_services.app().failed_requests.borrow();
2747 assert_eq!(failed_requests.len(), 2);
2748
2749 let has_txn_1 = failed_requests
2751 .iter()
2752 .any(|(txn, _, err)| *txn == 1 && *err == MbusError::NoRetriesLeft);
2753 let has_txn_2 = failed_requests
2754 .iter()
2755 .any(|(txn, _, err)| *txn == 2 && *err == MbusError::NoRetriesLeft);
2756 assert!(has_txn_1, "Transaction 1 should have failed");
2757 assert!(has_txn_2, "Transaction 2 should have failed");
2758 }
2759
2760 #[test]
2761 fn test_poll_connection_loss_flushes_pending_requests() {
2762 let transport = MockTransport::default();
2763 let app = MockApp::default();
2764 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2765 let mut client_services =
2766 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2767
2768 let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
2769 client_services.read_single_coil(1, unit_id, 0).unwrap();
2770 client_services.read_single_coil(2, unit_id, 1).unwrap();
2771 assert_eq!(client_services.expected_responses.len(), 2);
2772
2773 *client_services.transport.is_connected_flag.borrow_mut() = false;
2774 *client_services.transport.recv_error.borrow_mut() = Some(MbusError::ConnectionClosed);
2775
2776 client_services.poll();
2777
2778 assert!(client_services.expected_responses.is_empty());
2779 assert_eq!(client_services.next_timeout_check, None);
2780
2781 let failed_requests = client_services.app().failed_requests.borrow();
2782 assert_eq!(failed_requests.len(), 2);
2783 assert!(
2784 failed_requests
2785 .iter()
2786 .all(|(txn, _, err)| (*txn == 1 || *txn == 2) && *err == MbusError::ConnectionLost)
2787 );
2788 }
2789
2790 #[test]
2791 fn test_fixed_backoff_schedules_and_does_not_retry_early() {
2792 let transport = MockTransport::default();
2793 let app = MockApp::default();
2794 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2795 tcp_config.response_timeout_ms = 100;
2796 tcp_config.retry_attempts = 1;
2797 tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 50 };
2798 let config = ModbusConfig::Tcp(tcp_config);
2799
2800 let mut client_services =
2801 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2802
2803 client_services
2804 .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2805 .unwrap();
2806 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2807
2808 *client_services.app().current_time.borrow_mut() = 101;
2809 client_services.poll();
2810 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2811 assert_eq!(
2812 client_services.expected_responses[0].next_retry_timestamp,
2813 Some(151)
2814 );
2815
2816 *client_services.app().current_time.borrow_mut() = 150;
2817 client_services.poll();
2818 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2819
2820 *client_services.app().current_time.borrow_mut() = 151;
2821 client_services.poll();
2822 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2823 }
2824
2825 #[test]
2826 fn test_exponential_backoff_growth() {
2827 let transport = MockTransport::default();
2828 let app = MockApp::default();
2829 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2830 tcp_config.response_timeout_ms = 100;
2831 tcp_config.retry_attempts = 2;
2832 tcp_config.retry_backoff_strategy = BackoffStrategy::Exponential {
2833 base_delay_ms: 50,
2834 max_delay_ms: 500,
2835 };
2836 let config = ModbusConfig::Tcp(tcp_config);
2837
2838 let mut client_services =
2839 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2840
2841 client_services
2842 .read_single_coil(7, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2843 .unwrap();
2844
2845 *client_services.app().current_time.borrow_mut() = 101;
2846 client_services.poll();
2847 assert_eq!(
2848 client_services.expected_responses[0].next_retry_timestamp,
2849 Some(151)
2850 );
2851
2852 *client_services.app().current_time.borrow_mut() = 151;
2853 client_services.poll();
2854 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2855
2856 *client_services.app().current_time.borrow_mut() = 252;
2857 client_services.poll();
2858 assert_eq!(
2859 client_services.expected_responses[0].next_retry_timestamp,
2860 Some(352)
2861 );
2862
2863 *client_services.app().current_time.borrow_mut() = 352;
2864 client_services.poll();
2865 assert_eq!(client_services.transport.sent_frames.borrow().len(), 3);
2866 }
2867
2868 #[test]
2869 fn test_jitter_bounds_with_random_source_lower_bound() {
2870 let transport = MockTransport::default();
2871 let app = MockApp::default();
2872 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2873 tcp_config.response_timeout_ms = 100;
2874 tcp_config.retry_attempts = 1;
2875 tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2876 tcp_config.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2877 tcp_config.retry_random_fn = Some(rand_zero);
2878 let config = ModbusConfig::Tcp(tcp_config);
2879
2880 let mut client_services =
2881 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2882 client_services
2883 .read_single_coil(10, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2884 .unwrap();
2885
2886 *client_services.app().current_time.borrow_mut() = 101;
2887 client_services.poll();
2888 assert_eq!(
2889 client_services.expected_responses[0].next_retry_timestamp,
2890 Some(181)
2891 );
2892 }
2893
2894 #[test]
2895 fn test_jitter_bounds_with_random_source_upper_bound() {
2896 let transport3 = MockTransport::default();
2897 let app3 = MockApp::default();
2898 let mut tcp_config3 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2899 tcp_config3.response_timeout_ms = 100;
2900 tcp_config3.retry_attempts = 1;
2901 tcp_config3.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2902 tcp_config3.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2903 tcp_config3.retry_random_fn = Some(rand_upper_percent_20);
2904 let config3 = ModbusConfig::Tcp(tcp_config3);
2905
2906 let mut client_services3 =
2907 ClientServices::<MockTransport, MockApp, 10>::new(transport3, app3, config3).unwrap();
2908 client_services3
2909 .read_single_coil(12, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2910 .unwrap();
2911
2912 *client_services3.app.current_time.borrow_mut() = 101;
2913 client_services3.poll();
2914 assert_eq!(
2915 client_services3.expected_responses[0].next_retry_timestamp,
2916 Some(221)
2917 );
2918 }
2919
2920 #[test]
2921 fn test_jitter_falls_back_without_random_source() {
2922 let transport2 = MockTransport::default();
2923 let app2 = MockApp::default();
2924 let mut tcp_config2 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2925 tcp_config2.response_timeout_ms = 100;
2926 tcp_config2.retry_attempts = 1;
2927 tcp_config2.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2928 tcp_config2.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2929 tcp_config2.retry_random_fn = None;
2930 let config2 = ModbusConfig::Tcp(tcp_config2);
2931
2932 let mut client_services2 =
2933 ClientServices::<MockTransport, MockApp, 10>::new(transport2, app2, config2).unwrap();
2934 client_services2
2935 .read_single_coil(11, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2936 .unwrap();
2937
2938 *client_services2.app.current_time.borrow_mut() = 101;
2939 client_services2.poll();
2940 assert_eq!(
2941 client_services2.expected_responses[0].next_retry_timestamp,
2942 Some(201)
2943 );
2944 }
2945
2946 #[test]
2947 fn test_serial_retry_scheduling_uses_backoff() {
2948 let transport = MockTransport {
2949 transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
2950 ..Default::default()
2951 };
2952 let app = MockApp::default();
2953
2954 let serial_config = ModbusSerialConfig {
2955 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
2956 mode: SerialMode::Rtu,
2957 baud_rate: BaudRate::Baud9600,
2958 data_bits: mbus_core::transport::DataBits::Eight,
2959 stop_bits: 1,
2960 parity: Parity::None,
2961 response_timeout_ms: 100,
2962 retry_attempts: 1,
2963 retry_backoff_strategy: BackoffStrategy::Fixed { delay_ms: 25 },
2964 retry_jitter_strategy: JitterStrategy::None,
2965 retry_random_fn: None,
2966 };
2967
2968 let mut client_services = ClientServices::<MockTransport, MockApp, 1>::new(
2969 transport,
2970 app,
2971 ModbusConfig::Serial(serial_config),
2972 )
2973 .unwrap();
2974
2975 client_services
2976 .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2977 .unwrap();
2978
2979 *client_services.app().current_time.borrow_mut() = 101;
2980 client_services.poll();
2981 assert_eq!(
2982 client_services.expected_responses[0].next_retry_timestamp,
2983 Some(126)
2984 );
2985
2986 *client_services.app().current_time.borrow_mut() = 126;
2987 client_services.poll();
2988 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2989 }
2990
2991 #[test]
2993 fn test_too_many_requests_error() {
2994 let transport = MockTransport::default();
2995 let app = MockApp::default();
2996 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2997 let mut client_services =
2999 ClientServices::<MockTransport, MockApp, 1>::new(transport, app, config).unwrap();
3000
3001 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3002 client_services
3004 .read_multiple_coils(1, unit_id, 0, 1)
3005 .unwrap();
3006 assert_eq!(client_services.expected_responses.len(), 1);
3007
3008 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3009 let result = client_services.read_multiple_coils(2, unit_id, 0, 1);
3011 assert!(result.is_err());
3012 assert_eq!(result.unwrap_err(), MbusError::TooManyRequests);
3013 assert_eq!(client_services.expected_responses.len(), 1); }
3015
3016 #[test]
3018 fn test_read_holding_registers_sends_valid_adu() {
3019 let transport = MockTransport::default();
3020 let app = MockApp::default();
3021 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3022 let mut client_services =
3023 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3024
3025 let txn_id = 0x0005;
3026 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3027 let address = 0x0000;
3028 let quantity = 2;
3029 client_services
3030 .read_holding_registers(txn_id, unit_id, address, quantity)
3031 .unwrap();
3032
3033 let sent_frames = client_services.transport.sent_frames.borrow();
3034 assert_eq!(sent_frames.len(), 1);
3035 let sent_adu = sent_frames.front().unwrap();
3036
3037 #[rustfmt::skip]
3039 let expected_adu: [u8; 12] = [
3040 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, ];
3048 assert_eq!(sent_adu.as_slice(), &expected_adu);
3049 }
3050
3051 #[test]
3053 fn test_client_services_read_holding_registers_e2e_success() {
3054 let transport = MockTransport::default();
3055 let app = MockApp::default();
3056 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3057 let mut client_services =
3058 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3059
3060 let txn_id = 0x0005;
3061 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3062 let address = 0x0000;
3063 let quantity = 2;
3064 client_services
3065 .read_holding_registers(txn_id, unit_id, address, quantity)
3066 .unwrap();
3067
3068 let response_adu = [
3071 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x01, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78,
3072 ];
3073 client_services
3074 .transport
3075 .recv_frames
3076 .borrow_mut()
3077 .push_back(Vec::from_slice(&response_adu).unwrap())
3078 .unwrap();
3079 client_services.poll();
3080
3081 let received_responses = client_services
3082 .app
3083 .received_holding_register_responses
3084 .borrow();
3085 assert_eq!(received_responses.len(), 1);
3086 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3087 assert_eq!(*rcv_txn_id, txn_id);
3088 assert_eq!(*rcv_unit_id, unit_id);
3089 assert_eq!(rcv_registers.from_address(), address);
3090 assert_eq!(rcv_registers.quantity(), quantity);
3091 assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3092 assert_eq!(*rcv_quantity, quantity);
3093 assert!(client_services.expected_responses.is_empty());
3094 }
3095
3096 #[test]
3098 fn test_read_input_registers_sends_valid_adu() {
3099 let transport = MockTransport::default();
3100 let app = MockApp::default();
3101 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3102 let mut client_services =
3103 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3104
3105 let txn_id = 0x0006;
3106 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3107 let address = 0x0000;
3108 let quantity = 2;
3109 client_services
3110 .read_input_registers(txn_id, unit_id, address, quantity)
3111 .unwrap();
3112
3113 let sent_frames = client_services.transport.sent_frames.borrow();
3114 assert_eq!(sent_frames.len(), 1);
3115 let sent_adu = sent_frames.front().unwrap();
3116
3117 #[rustfmt::skip]
3119 let expected_adu: [u8; 12] = [
3120 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x00, 0x00, 0x02, ];
3128 assert_eq!(sent_adu.as_slice(), &expected_adu);
3129 }
3130
3131 #[test]
3133 fn test_client_services_read_input_registers_e2e_success() {
3134 let transport = MockTransport::default();
3135 let app = MockApp::default();
3136 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3137 let mut client_services =
3138 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3139
3140 let txn_id = 0x0006;
3141 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3142 let address = 0x0000;
3143 let quantity = 2;
3144 client_services
3145 .read_input_registers(txn_id, unit_id, address, quantity)
3146 .unwrap();
3147
3148 let response_adu = [
3151 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x01, 0x04, 0x04, 0xAA, 0xBB, 0xCC, 0xDD,
3152 ];
3153 client_services
3154 .transport
3155 .recv_frames
3156 .borrow_mut()
3157 .push_back(Vec::from_slice(&response_adu).unwrap())
3158 .unwrap();
3159 client_services.poll();
3160
3161 let received_responses = client_services
3162 .app
3163 .received_input_register_responses
3164 .borrow();
3165 assert_eq!(received_responses.len(), 1);
3166 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3167 assert_eq!(*rcv_txn_id, txn_id);
3168 assert_eq!(*rcv_unit_id, unit_id);
3169 assert_eq!(rcv_registers.from_address(), address);
3170 assert_eq!(rcv_registers.quantity(), quantity);
3171 assert_eq!(&rcv_registers.values()[..2], &[0xAABB, 0xCCDD]);
3172 assert_eq!(*rcv_quantity, quantity);
3173 assert!(client_services.expected_responses.is_empty());
3174 }
3175
3176 #[test]
3178 fn test_write_single_register_sends_valid_adu() {
3179 let transport = MockTransport::default();
3180 let app = MockApp::default();
3181 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3182 let mut client_services =
3183 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3184
3185 let txn_id = 0x0007;
3186 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3187 let address = 0x0001;
3188 let value = 0x1234;
3189 client_services
3190 .write_single_register(txn_id, unit_id, address, value)
3191 .unwrap();
3192
3193 let sent_frames = client_services.transport.sent_frames.borrow();
3194 assert_eq!(sent_frames.len(), 1);
3195 let sent_adu = sent_frames.front().unwrap();
3196
3197 #[rustfmt::skip]
3199 let expected_adu: [u8; 12] = [
3200 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34, ];
3208 assert_eq!(sent_adu.as_slice(), &expected_adu);
3209 }
3210
3211 #[test]
3213 fn test_client_services_write_single_register_e2e_success() {
3214 let transport = MockTransport::default();
3215 let app = MockApp::default();
3216 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3217 let mut client_services =
3218 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3219
3220 let txn_id = 0x0007;
3221 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3222 let address = 0x0001;
3223 let value = 0x1234;
3224 client_services
3225 .write_single_register(txn_id, unit_id, address, value)
3226 .unwrap();
3227
3228 let response_adu = [
3231 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34,
3232 ];
3233 client_services
3234 .transport
3235 .recv_frames
3236 .borrow_mut()
3237 .push_back(Vec::from_slice(&response_adu).unwrap())
3238 .unwrap();
3239 client_services.poll();
3240
3241 let received_responses = client_services
3242 .app
3243 .received_write_single_register_responses
3244 .borrow();
3245 assert_eq!(received_responses.len(), 1);
3246 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
3247 assert_eq!(*rcv_txn_id, txn_id);
3248 assert_eq!(*rcv_unit_id, unit_id);
3249 assert_eq!(*rcv_address, address);
3250 assert_eq!(*rcv_value, value);
3251 assert!(client_services.expected_responses.is_empty());
3252 }
3253
3254 #[test]
3256 fn test_write_multiple_registers_sends_valid_adu() {
3257 let transport = MockTransport::default();
3258 let app = MockApp::default();
3259 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3260 let mut client_services =
3261 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3262
3263 let txn_id = 0x0008;
3264 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3265 let address = 0x0001;
3266 let quantity = 2;
3267 let values = [0x1234, 0x5678];
3268 client_services
3269 .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3270 .unwrap();
3271
3272 let sent_frames = client_services.transport.sent_frames.borrow();
3273 assert_eq!(sent_frames.len(), 1);
3274 let sent_adu = sent_frames.front().unwrap();
3275
3276 #[rustfmt::skip]
3278 let expected_adu: [u8; 17] = [ 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x34, 0x56, 0x78, ];
3289 assert_eq!(sent_adu.as_slice(), &expected_adu);
3290 }
3291
3292 #[test]
3294 fn test_client_services_write_multiple_registers_e2e_success() {
3295 let transport = MockTransport::default();
3296 let app = MockApp::default();
3297 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3298 let mut client_services =
3299 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3300
3301 let txn_id = 0x0008;
3302 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3303 let address = 0x0001;
3304 let quantity = 2;
3305 let values = [0x1234, 0x5678];
3306 client_services
3307 .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3308 .unwrap();
3309
3310 let response_adu = [
3313 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02,
3314 ];
3315 client_services
3316 .transport
3317 .recv_frames
3318 .borrow_mut()
3319 .push_back(Vec::from_slice(&response_adu).unwrap())
3320 .unwrap();
3321 client_services.poll();
3322
3323 let received_responses = client_services
3324 .app
3325 .received_write_multiple_register_responses
3326 .borrow();
3327 assert_eq!(received_responses.len(), 1);
3328 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
3329 assert_eq!(*rcv_txn_id, txn_id);
3330 assert_eq!(*rcv_unit_id, unit_id);
3331 assert_eq!(*rcv_address, address);
3332 assert_eq!(*rcv_quantity, quantity);
3333 assert!(client_services.expected_responses.is_empty());
3334 }
3335
3336 #[test]
3338 fn test_client_services_handles_exception_response() {
3339 let transport = MockTransport::default();
3340 let app = MockApp::default();
3341 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3342 let mut client_services =
3343 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3344
3345 let txn_id = 0x0009;
3346 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3347 let address = 0x0000;
3348 let quantity = 1;
3349
3350 client_services
3351 .read_holding_registers(txn_id, unit_id, address, quantity)
3352 .unwrap();
3353
3354 let exception_adu = [
3357 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x01, 0x83, 0x02, ];
3364 client_services
3365 .transport
3366 .recv_frames
3367 .borrow_mut()
3368 .push_back(Vec::from_slice(&exception_adu).unwrap())
3369 .unwrap();
3370 client_services.poll();
3371
3372 assert!(
3374 client_services
3375 .app
3376 .received_holding_register_responses
3377 .borrow()
3378 .is_empty()
3379 );
3380 assert_eq!(client_services.app().failed_requests.borrow().len(), 1);
3382 let (failed_txn, failed_unit, failed_err) =
3383 &client_services.app().failed_requests.borrow()[0];
3384 assert_eq!(*failed_txn, txn_id);
3385 assert_eq!(*failed_unit, unit_id);
3386 assert_eq!(*failed_err, MbusError::ModbusException(0x02));
3387 }
3388
3389 #[test]
3390 fn test_serial_exception_coil_response_fails_immediately_with_request_txn_id() {
3391 let mut client_services = make_serial_client();
3392
3393 let txn_id = 0x2001;
3394 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3395 let mut values = Coils::new(0x0000, 10).unwrap();
3396 values.set_value(0x0000, true).unwrap();
3397 values.set_value(0x0001, false).unwrap();
3398 values.set_value(0x0002, true).unwrap();
3399 values.set_value(0x0003, false).unwrap();
3400 values.set_value(0x0004, true).unwrap();
3401 values.set_value(0x0005, false).unwrap();
3402 values.set_value(0x0006, true).unwrap();
3403 values.set_value(0x0007, false).unwrap();
3404 values.set_value(0x0008, true).unwrap();
3405 values.set_value(0x0009, false).unwrap();
3406
3407 client_services
3408 .write_multiple_coils(txn_id, unit_id, 0x0000, &values)
3409 .unwrap();
3410
3411 let exception_adu = make_rtu_exception_adu(unit_id, 0x0F, 0x01);
3412 client_services
3413 .transport
3414 .recv_frames
3415 .borrow_mut()
3416 .push_back(exception_adu)
3417 .unwrap();
3418
3419 client_services.poll();
3420
3421 let failed = client_services.app().failed_requests.borrow();
3422 assert_eq!(failed.len(), 1);
3423 assert_eq!(failed[0].0, txn_id);
3424 assert_eq!(failed[0].1, unit_id);
3425 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
3426 assert!(
3427 client_services
3428 .app
3429 .received_write_multiple_coils_responses
3430 .borrow()
3431 .is_empty()
3432 );
3433 assert!(client_services.expected_responses.is_empty());
3434 }
3435
3436 #[test]
3437 fn test_serial_exception_register_response_fails_immediately_with_request_txn_id() {
3438 let mut client_services = make_serial_client();
3439
3440 let txn_id = 0x2002;
3441 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3442 client_services
3443 .read_holding_registers(txn_id, unit_id, 0x0000, 1)
3444 .unwrap();
3445
3446 let exception_adu = make_rtu_exception_adu(unit_id, 0x03, 0x02);
3447 client_services
3448 .transport
3449 .recv_frames
3450 .borrow_mut()
3451 .push_back(exception_adu)
3452 .unwrap();
3453
3454 client_services.poll();
3455
3456 let failed = client_services.app().failed_requests.borrow();
3457 assert_eq!(failed.len(), 1);
3458 assert_eq!(failed[0].0, txn_id);
3459 assert_eq!(failed[0].1, unit_id);
3460 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
3461 assert!(
3462 client_services
3463 .app
3464 .received_holding_register_responses
3465 .borrow()
3466 .is_empty()
3467 );
3468 assert!(client_services.expected_responses.is_empty());
3469 }
3470
3471 #[test]
3472 fn test_serial_exception_discrete_input_response_fails_immediately_with_request_txn_id() {
3473 let mut client_services = make_serial_client();
3474
3475 let txn_id = 0x2003;
3476 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3477 client_services
3478 .read_discrete_inputs(txn_id, unit_id, 0x0000, 8)
3479 .unwrap();
3480
3481 let exception_adu = make_rtu_exception_adu(unit_id, 0x02, 0x02);
3482 client_services
3483 .transport
3484 .recv_frames
3485 .borrow_mut()
3486 .push_back(exception_adu)
3487 .unwrap();
3488
3489 client_services.poll();
3490
3491 let failed = client_services.app().failed_requests.borrow();
3492 assert_eq!(failed.len(), 1);
3493 assert_eq!(failed[0].0, txn_id);
3494 assert_eq!(failed[0].1, unit_id);
3495 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
3496 assert!(
3497 client_services
3498 .app
3499 .received_discrete_input_responses
3500 .borrow()
3501 .is_empty()
3502 );
3503 assert!(client_services.expected_responses.is_empty());
3504 }
3505
3506 #[test]
3507 fn test_serial_exception_fifo_response_fails_immediately_with_request_txn_id() {
3508 let mut client_services = make_serial_client();
3509
3510 let txn_id = 0x2004;
3511 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3512 client_services
3513 .read_fifo_queue(txn_id, unit_id, 0x0001)
3514 .unwrap();
3515
3516 let exception_adu = make_rtu_exception_adu(unit_id, 0x18, 0x01);
3517 client_services
3518 .transport
3519 .recv_frames
3520 .borrow_mut()
3521 .push_back(exception_adu)
3522 .unwrap();
3523
3524 client_services.poll();
3525
3526 let failed = client_services.app().failed_requests.borrow();
3527 assert_eq!(failed.len(), 1);
3528 assert_eq!(failed[0].0, txn_id);
3529 assert_eq!(failed[0].1, unit_id);
3530 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
3531 assert!(
3532 client_services
3533 .app
3534 .received_read_fifo_queue_responses
3535 .borrow()
3536 .is_empty()
3537 );
3538 assert!(client_services.expected_responses.is_empty());
3539 }
3540
3541 #[test]
3542 fn test_serial_exception_file_record_response_fails_immediately_with_request_txn_id() {
3543 let mut client_services = make_serial_client();
3544
3545 let txn_id = 0x2005;
3546 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3547 let mut sub_req = SubRequest::new();
3548 sub_req.add_read_sub_request(4, 1, 2).unwrap();
3549 client_services
3550 .read_file_record(txn_id, unit_id, &sub_req)
3551 .unwrap();
3552
3553 let exception_adu = make_rtu_exception_adu(unit_id, 0x14, 0x02);
3554 client_services
3555 .transport
3556 .recv_frames
3557 .borrow_mut()
3558 .push_back(exception_adu)
3559 .unwrap();
3560
3561 client_services.poll();
3562
3563 let failed = client_services.app().failed_requests.borrow();
3564 assert_eq!(failed.len(), 1);
3565 assert_eq!(failed[0].0, txn_id);
3566 assert_eq!(failed[0].1, unit_id);
3567 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
3568 assert!(
3569 client_services
3570 .app
3571 .received_read_file_record_responses
3572 .borrow()
3573 .is_empty()
3574 );
3575 assert!(client_services.expected_responses.is_empty());
3576 }
3577
3578 #[test]
3579 fn test_serial_exception_diagnostic_response_fails_immediately_with_request_txn_id() {
3580 let mut client_services = make_serial_client();
3581
3582 let txn_id = 0x2006;
3583 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3584 client_services
3585 .read_device_identification(
3586 txn_id,
3587 unit_id,
3588 ReadDeviceIdCode::Basic,
3589 ObjectId::from(0x00),
3590 )
3591 .unwrap();
3592
3593 let exception_adu = make_rtu_exception_adu(unit_id, 0x2B, 0x01);
3594 client_services
3595 .transport
3596 .recv_frames
3597 .borrow_mut()
3598 .push_back(exception_adu)
3599 .unwrap();
3600
3601 client_services.poll();
3602
3603 let failed = client_services.app().failed_requests.borrow();
3604 assert_eq!(failed.len(), 1);
3605 assert_eq!(failed[0].0, txn_id);
3606 assert_eq!(failed[0].1, unit_id);
3607 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
3608 assert!(
3609 client_services
3610 .app
3611 .received_read_device_id_responses
3612 .borrow()
3613 .is_empty()
3614 );
3615 assert!(client_services.expected_responses.is_empty());
3616 }
3617
3618 #[test]
3620 fn test_read_single_holding_register_sends_valid_adu() {
3621 let transport = MockTransport::default();
3622 let app = MockApp::default();
3623 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3624 let mut client_services =
3625 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3626
3627 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3628 client_services
3629 .read_single_holding_register(10, unit_id, 100)
3630 .unwrap();
3631
3632 let sent_frames = client_services.transport.sent_frames.borrow();
3633 assert_eq!(sent_frames.len(), 1);
3634 let sent_adu = sent_frames.front().unwrap();
3635
3636 #[rustfmt::skip]
3637 let expected_adu: [u8; 12] = [
3638 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x64, 0x00, 0x01, ];
3646 assert_eq!(sent_adu.as_slice(), &expected_adu);
3647
3648 assert_eq!(client_services.expected_responses.len(), 1);
3650 let single_read = client_services.expected_responses[0]
3651 .operation_meta
3652 .is_single();
3653 assert!(single_read);
3654 }
3655
3656 #[test]
3658 fn test_client_services_read_single_holding_register_e2e_success() {
3659 let transport = MockTransport::default();
3660 let app = MockApp::default();
3661 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3662 let mut client_services =
3663 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3664
3665 let txn_id = 10;
3666 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3667 let address = 100;
3668
3669 client_services
3670 .read_single_holding_register(txn_id, unit_id, address)
3671 .unwrap();
3672
3673 let response_adu = [
3675 0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x03, 0x02, 0x12, 0x34,
3676 ];
3677 client_services
3678 .transport
3679 .recv_frames
3680 .borrow_mut()
3681 .push_back(Vec::from_slice(&response_adu).unwrap())
3682 .unwrap();
3683 client_services.poll();
3684
3685 let received_responses = client_services
3686 .app
3687 .received_holding_register_responses
3688 .borrow();
3689 assert_eq!(received_responses.len(), 1);
3690 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3691 assert_eq!(*rcv_txn_id, txn_id);
3692 assert_eq!(*rcv_unit_id, unit_id);
3693 assert_eq!(rcv_registers.from_address(), address);
3694 assert_eq!(rcv_registers.quantity(), 1);
3695 assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
3696 assert_eq!(*rcv_quantity, 1);
3697 }
3698
3699 #[test]
3701 fn test_read_single_input_register_sends_valid_adu() {
3702 let transport = MockTransport::default();
3703 let app = MockApp::default();
3704 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3705 let mut client_services =
3706 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3707
3708 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3709 client_services
3710 .read_single_input_register(10, unit_id, 100)
3711 .unwrap();
3712
3713 let sent_frames = client_services.transport.sent_frames.borrow();
3714 assert_eq!(sent_frames.len(), 1);
3715 let sent_adu = sent_frames.front().unwrap();
3716
3717 #[rustfmt::skip]
3718 let expected_adu: [u8; 12] = [
3719 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x64, 0x00, 0x01, ];
3727 assert_eq!(sent_adu.as_slice(), &expected_adu);
3728
3729 assert_eq!(client_services.expected_responses.len(), 1);
3731 let single_read = client_services.expected_responses[0]
3732 .operation_meta
3733 .is_single();
3734 assert!(single_read);
3735 }
3736
3737 #[test]
3739 fn test_client_services_read_single_input_register_e2e_success() {
3740 let transport = MockTransport::default();
3741 let app = MockApp::default();
3742 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3743 let mut client_services =
3744 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3745
3746 let txn_id = 10;
3747 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3748 let address = 100;
3749
3750 client_services
3751 .read_single_input_register(txn_id, unit_id, address)
3752 .unwrap();
3753
3754 let response_adu = [
3757 0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x04, 0x02, 0x12, 0x34,
3758 ];
3759 client_services
3760 .transport
3761 .recv_frames
3762 .borrow_mut()
3763 .push_back(Vec::from_slice(&response_adu).unwrap())
3764 .unwrap();
3765 client_services.poll();
3766
3767 let received_responses = client_services
3768 .app
3769 .received_input_register_responses
3770 .borrow();
3771 assert_eq!(received_responses.len(), 1);
3772 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3773 assert_eq!(*rcv_txn_id, txn_id);
3774 assert_eq!(*rcv_unit_id, unit_id);
3775 assert_eq!(rcv_registers.from_address(), address);
3776 assert_eq!(rcv_registers.quantity(), 1);
3777 assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
3778 assert_eq!(*rcv_quantity, 1);
3779 }
3780
3781 #[test]
3783 fn test_read_write_multiple_registers_sends_valid_adu() {
3784 let transport = MockTransport::default();
3785 let app = MockApp::default();
3786 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3787 let mut client_services =
3788 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3789
3790 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3791 let write_values = [0xAAAA, 0xBBBB];
3792 client_services
3793 .read_write_multiple_registers(11, unit_id, 10, 2, 20, &write_values)
3794 .unwrap();
3795
3796 let sent_frames = client_services.transport.sent_frames.borrow();
3797 assert_eq!(sent_frames.len(), 1);
3798 let sent_adu = sent_frames.front().unwrap();
3799
3800 #[rustfmt::skip]
3801 let expected_adu: [u8; 21] = [
3802 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x17, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x14, 0x00, 0x02, 0x04, 0xAA, 0xAA, 0xBB, 0xBB, ];
3815 assert_eq!(sent_adu.as_slice(), &expected_adu);
3816 }
3817
3818 #[test]
3820 fn test_mask_write_register_sends_valid_adu() {
3821 let transport = MockTransport::default();
3822 let app = MockApp::default();
3823 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3824 let mut client_services =
3825 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3826
3827 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3828 client_services
3829 .mask_write_register(12, unit_id, 30, 0xF0F0, 0x0F0F)
3830 .unwrap();
3831
3832 let sent_frames = client_services.transport.sent_frames.borrow();
3833 assert_eq!(sent_frames.len(), 1);
3834 let sent_adu = sent_frames.front().unwrap();
3835
3836 #[rustfmt::skip]
3837 let expected_adu: [u8; 14] = [
3838 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F, ];
3847 assert_eq!(sent_adu.as_slice(), &expected_adu);
3848 }
3849
3850 #[test]
3852 fn test_client_services_read_write_multiple_registers_e2e_success() {
3853 let transport = MockTransport::default();
3854 let app = MockApp::default();
3855 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3856 let mut client_services =
3857 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3858
3859 let txn_id = 11;
3860 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3861 let read_address = 10;
3862 let read_quantity = 2;
3863 let write_address = 20;
3864 let write_values = [0xAAAA, 0xBBBB];
3865
3866 client_services
3867 .read_write_multiple_registers(
3868 txn_id,
3869 unit_id,
3870 read_address,
3871 read_quantity,
3872 write_address,
3873 &write_values,
3874 )
3875 .unwrap();
3876
3877 let response_adu = [
3879 0x00, 0x0B, 0x00, 0x00, 0x00, 0x07, 0x01, 0x17, 0x04, 0x12, 0x34, 0x56, 0x78,
3880 ];
3881 client_services
3882 .transport
3883 .recv_frames
3884 .borrow_mut()
3885 .push_back(Vec::from_slice(&response_adu).unwrap())
3886 .unwrap();
3887 client_services.poll();
3888
3889 let received_responses = client_services
3890 .app
3891 .received_read_write_multiple_registers_responses
3892 .borrow();
3893 assert_eq!(received_responses.len(), 1);
3894 let (rcv_txn_id, rcv_unit_id, rcv_registers) = &received_responses[0];
3895 assert_eq!(*rcv_txn_id, txn_id);
3896 assert_eq!(*rcv_unit_id, unit_id);
3897 assert_eq!(rcv_registers.from_address(), read_address);
3898 assert_eq!(rcv_registers.quantity(), read_quantity);
3899 assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3900 }
3901
3902 #[test]
3904 fn test_client_services_mask_write_register_e2e_success() {
3905 let transport = MockTransport::default();
3906 let app = MockApp::default();
3907 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3908 let mut client_services =
3909 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3910
3911 let txn_id = 12;
3912 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3913 let address = 30;
3914 let and_mask = 0xF0F0;
3915 let or_mask = 0x0F0F;
3916
3917 client_services
3918 .mask_write_register(txn_id, unit_id, address, and_mask, or_mask)
3919 .unwrap();
3920
3921 let response_adu = [
3923 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F,
3924 ];
3925 client_services
3926 .transport
3927 .recv_frames
3928 .borrow_mut()
3929 .push_back(Vec::from_slice(&response_adu).unwrap())
3930 .unwrap();
3931 client_services.poll();
3932
3933 let received_responses = client_services
3934 .app
3935 .received_mask_write_register_responses
3936 .borrow();
3937 assert_eq!(received_responses.len(), 1);
3938 let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
3939 assert_eq!(*rcv_txn_id, txn_id);
3940 assert_eq!(*rcv_unit_id, unit_id);
3941 }
3942
3943 #[test]
3945 fn test_client_services_read_fifo_queue_e2e_success() {
3946 let transport = MockTransport::default();
3947 let app = MockApp::default();
3948 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3949 let mut client_services =
3950 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3951
3952 let txn_id = 13;
3953 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3954 let address = 40;
3955
3956 client_services
3957 .read_fifo_queue(txn_id, unit_id, address)
3958 .unwrap();
3959
3960 #[rustfmt::skip]
3962 let response_adu = [
3963 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x18, 0x00, 0x06, 0x00, 0x02, 0xAA, 0xAA, 0xBB, 0xBB, ];
3973 client_services
3974 .transport
3975 .recv_frames
3976 .borrow_mut()
3977 .push_back(Vec::from_slice(&response_adu).unwrap())
3978 .unwrap();
3979 client_services.poll();
3980
3981 let received_responses = client_services
3982 .app
3983 .received_read_fifo_queue_responses
3984 .borrow();
3985 assert_eq!(received_responses.len(), 1);
3986 let (rcv_txn_id, rcv_unit_id, rcv_fifo_queue) = &received_responses[0];
3987 assert_eq!(*rcv_txn_id, txn_id);
3988 assert_eq!(*rcv_unit_id, unit_id);
3989 assert_eq!(rcv_fifo_queue.length(), 2);
3990 assert_eq!(&rcv_fifo_queue.queue()[..2], &[0xAAAA, 0xBBBB]);
3991 }
3992
3993 #[test]
3995 fn test_client_services_read_file_record_e2e_success() {
3996 let transport = MockTransport::default();
3997 let app = MockApp::default();
3998 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3999 let mut client_services =
4000 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4001
4002 let txn_id = 14;
4003 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4004 let mut sub_req = SubRequest::new();
4005 sub_req.add_read_sub_request(4, 1, 2).unwrap();
4006
4007 client_services
4008 .read_file_record(txn_id, unit_id, &sub_req)
4009 .unwrap();
4010
4011 let response_adu = [
4017 0x00, 0x0E, 0x00, 0x00, 0x00, 0x09, 0x01, 0x14, 0x06, 0x05, 0x06, 0x12, 0x34, 0x56,
4018 0x78,
4019 ];
4020
4021 client_services
4022 .transport
4023 .recv_frames
4024 .borrow_mut()
4025 .push_back(Vec::from_slice(&response_adu).unwrap())
4026 .unwrap();
4027 client_services.poll();
4028
4029 let received_responses = client_services
4030 .app
4031 .received_read_file_record_responses
4032 .borrow();
4033 assert_eq!(received_responses.len(), 1);
4034 let (rcv_txn_id, rcv_unit_id, rcv_data) = &received_responses[0];
4035 assert_eq!(*rcv_txn_id, txn_id);
4036 assert_eq!(*rcv_unit_id, unit_id);
4037 assert_eq!(rcv_data.len(), 1);
4038 assert_eq!(
4039 rcv_data[0].record_data.as_ref().unwrap().as_slice(),
4040 &[0x1234, 0x5678]
4041 );
4042 }
4043
4044 #[test]
4046 fn test_client_services_write_file_record_e2e_success() {
4047 let transport = MockTransport::default();
4048 let app = MockApp::default();
4049 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4050 let mut client_services =
4051 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4052
4053 let txn_id = 15;
4054 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4055 let mut sub_req = SubRequest::new();
4056 let mut data = Vec::new();
4057 data.push(0x1122).unwrap();
4058 sub_req.add_write_sub_request(4, 1, 1, data).unwrap();
4059
4060 client_services
4061 .write_file_record(txn_id, unit_id, &sub_req)
4062 .unwrap();
4063
4064 let response_adu = [
4067 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0C, 0x01, 0x15, 0x09, 0x06, 0x00, 0x04, 0x00, 0x01,
4068 0x00, 0x01, 0x11, 0x22,
4069 ];
4070
4071 client_services
4072 .transport
4073 .recv_frames
4074 .borrow_mut()
4075 .push_back(Vec::from_slice(&response_adu).unwrap())
4076 .unwrap();
4077 client_services.poll();
4078
4079 let received_responses = client_services
4080 .app
4081 .received_write_file_record_responses
4082 .borrow();
4083 assert_eq!(received_responses.len(), 1);
4084 let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
4085 assert_eq!(*rcv_txn_id, txn_id);
4086 assert_eq!(*rcv_unit_id, unit_id);
4087 }
4088
4089 #[test]
4091 fn test_client_services_read_discrete_inputs_e2e_success() {
4092 let transport = MockTransport::default();
4093 let app = MockApp::default();
4094 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4095 let mut client_services =
4096 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4097
4098 let txn_id = 16;
4099 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4100 let address = 50;
4101 let quantity = 8;
4102
4103 client_services
4104 .read_discrete_inputs(txn_id, unit_id, address, quantity)
4105 .unwrap();
4106
4107 let response_adu = [0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0xAA];
4109
4110 client_services
4111 .transport
4112 .recv_frames
4113 .borrow_mut()
4114 .push_back(Vec::from_slice(&response_adu).unwrap())
4115 .unwrap();
4116 client_services.poll();
4117
4118 let received_responses = client_services
4119 .app
4120 .received_discrete_input_responses
4121 .borrow();
4122 assert_eq!(received_responses.len(), 1);
4123 let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
4124 assert_eq!(*rcv_txn_id, txn_id);
4125 assert_eq!(*rcv_unit_id, unit_id);
4126 assert_eq!(rcv_inputs.from_address(), address);
4127 assert_eq!(rcv_inputs.quantity(), quantity);
4128 assert_eq!(rcv_inputs.values(), &[0xAA]);
4129 assert_eq!(*rcv_quantity, quantity);
4130 }
4131
4132 #[test]
4134 fn test_client_services_read_single_discrete_input_e2e_success() {
4135 let transport = MockTransport::default();
4136 let app = MockApp::default();
4137 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4138 let mut client_services =
4139 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4140
4141 let txn_id = 17;
4142 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4143 let address = 10;
4144
4145 client_services
4146 .read_single_discrete_input(txn_id, unit_id, address)
4147 .unwrap();
4148
4149 let sent_frames = client_services.transport.sent_frames.borrow();
4151 assert_eq!(sent_frames.len(), 1);
4152 let expected_request = [
4156 0x00, 0x11, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x0A, 0x00, 0x01,
4157 ];
4158 assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
4159 drop(sent_frames);
4160
4161 let response_adu = [0x00, 0x11, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0x01];
4163
4164 client_services
4165 .transport
4166 .recv_frames
4167 .borrow_mut()
4168 .push_back(Vec::from_slice(&response_adu).unwrap())
4169 .unwrap();
4170 client_services.poll();
4171
4172 let received_responses = client_services
4173 .app
4174 .received_discrete_input_responses
4175 .borrow();
4176 assert_eq!(received_responses.len(), 1);
4177 let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
4178 assert_eq!(*rcv_txn_id, txn_id);
4179 assert_eq!(*rcv_unit_id, unit_id);
4180 assert_eq!(rcv_inputs.from_address(), address);
4181 assert_eq!(rcv_inputs.quantity(), 1);
4182 assert_eq!(rcv_inputs.value(address).unwrap(), true);
4183 assert_eq!(*rcv_quantity, 1);
4184 }
4185
4186 #[test]
4188 fn test_client_services_read_device_identification_e2e_success() {
4189 let transport = MockTransport::default();
4190 let app = MockApp::default();
4191 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4192 let mut client_services =
4193 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4194
4195 let txn_id = 20;
4196 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4197 let read_code = ReadDeviceIdCode::Basic;
4198 let object_id = ObjectId::from(0x00);
4199
4200 client_services
4201 .read_device_identification(txn_id, unit_id, read_code, object_id)
4202 .unwrap();
4203
4204 let sent_frames = client_services.transport.sent_frames.borrow();
4206 assert_eq!(sent_frames.len(), 1);
4207 let expected_request = [
4211 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2B, 0x0E, 0x01, 0x00,
4212 ];
4213 assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
4214 drop(sent_frames);
4215
4216 let response_adu = [
4221 0x00, 0x14, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x01,
4222 0x00, 0x03, 0x46, 0x6F, 0x6F,
4223 ];
4224
4225 client_services
4226 .transport
4227 .recv_frames
4228 .borrow_mut()
4229 .push_back(Vec::from_slice(&response_adu).unwrap())
4230 .unwrap();
4231 client_services.poll();
4232
4233 let received_responses = client_services
4234 .app
4235 .received_read_device_id_responses
4236 .borrow();
4237 assert_eq!(received_responses.len(), 1);
4238 let (rcv_txn_id, rcv_unit_id, rcv_resp) = &received_responses[0];
4239 assert_eq!(*rcv_txn_id, txn_id);
4240 assert_eq!(*rcv_unit_id, unit_id);
4241 assert_eq!(rcv_resp.read_device_id_code, ReadDeviceIdCode::Basic);
4242 assert_eq!(
4243 rcv_resp.conformity_level,
4244 ConformityLevel::BasicStreamAndIndividual
4245 );
4246 assert_eq!(rcv_resp.number_of_objects, 1);
4247
4248 assert_eq!(&rcv_resp.objects_data[..5], &[0x00, 0x03, 0x46, 0x6F, 0x6F]);
4250 }
4251
4252 #[test]
4254 fn test_client_services_read_device_identification_multi_transaction() {
4255 let transport = MockTransport::default();
4256 let app = MockApp::default();
4257 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4258 let mut client_services =
4259 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4260
4261 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4262 let txn_id_1 = 21;
4264 client_services
4265 .read_device_identification(
4266 txn_id_1,
4267 unit_id,
4268 ReadDeviceIdCode::Basic,
4269 ObjectId::from(0x00),
4270 )
4271 .unwrap();
4272
4273 let txn_id_2 = 22;
4275 client_services
4276 .read_device_identification(
4277 txn_id_2,
4278 unit_id,
4279 ReadDeviceIdCode::Regular,
4280 ObjectId::from(0x00),
4281 )
4282 .unwrap();
4283
4284 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
4285
4286 let response_adu_2 = [
4290 0x00, 0x16, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x82, 0x00, 0x00, 0x00,
4291 ];
4292 client_services
4293 .transport
4294 .recv_frames
4295 .borrow_mut()
4296 .push_back(Vec::from_slice(&response_adu_2).unwrap())
4297 .unwrap();
4298
4299 client_services.poll();
4300
4301 {
4302 let received_responses = client_services
4303 .app
4304 .received_read_device_id_responses
4305 .borrow();
4306 assert_eq!(received_responses.len(), 1);
4307 assert_eq!(received_responses[0].0, txn_id_2);
4308 assert_eq!(
4309 received_responses[0].2.read_device_id_code,
4310 ReadDeviceIdCode::Regular
4311 );
4312 }
4313
4314 let response_adu_1 = [
4317 0x00, 0x15, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x00,
4318 ];
4319 client_services
4320 .transport
4321 .recv_frames
4322 .borrow_mut()
4323 .push_back(Vec::from_slice(&response_adu_1).unwrap())
4324 .unwrap();
4325
4326 client_services.poll();
4327
4328 {
4329 let received_responses = client_services
4330 .app
4331 .received_read_device_id_responses
4332 .borrow();
4333 assert_eq!(received_responses.len(), 2);
4334 assert_eq!(received_responses[1].0, txn_id_1);
4335 assert_eq!(
4336 received_responses[1].2.read_device_id_code,
4337 ReadDeviceIdCode::Basic
4338 );
4339 }
4340 }
4341
4342 #[test]
4344 fn test_client_services_read_device_identification_mismatch_code() {
4345 let transport = MockTransport::default();
4346 let app = MockApp::default();
4347 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4348 let mut client_services =
4349 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4350
4351 let txn_id = 30;
4352 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4353 client_services
4355 .read_device_identification(
4356 txn_id,
4357 unit_id,
4358 ReadDeviceIdCode::Basic,
4359 ObjectId::from(0x00),
4360 )
4361 .unwrap();
4362
4363 let response_adu = [
4366 0x00, 0x1E, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x81, 0x00, 0x00, 0x00,
4367 ];
4368
4369 client_services
4370 .transport
4371 .recv_frames
4372 .borrow_mut()
4373 .push_back(Vec::from_slice(&response_adu).unwrap())
4374 .unwrap();
4375
4376 client_services.poll();
4377
4378 assert!(
4380 client_services
4381 .app
4382 .received_read_device_id_responses
4383 .borrow()
4384 .is_empty()
4385 );
4386
4387 let failed = client_services.app().failed_requests.borrow();
4389 assert_eq!(failed.len(), 1);
4390 assert_eq!(failed[0].2, MbusError::InvalidDeviceIdentification);
4391 }
4392
4393 #[test]
4395 fn test_client_services_read_exception_status_e2e_success() {
4396 let transport = MockTransport::default();
4397 let app = MockApp::default();
4398 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4399 let mut client_services =
4400 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4401
4402 let txn_id = 40;
4403 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4404
4405 let err = client_services.read_exception_status(txn_id, unit_id).err();
4406 assert_eq!(err, Some(MbusError::InvalidTransport));
4408 }
4409
4410 #[test]
4412 fn test_client_services_diagnostics_query_data_success() {
4413 let transport = MockTransport::default();
4414 let app = MockApp::default();
4415 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4416 let mut client_services =
4417 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4418
4419 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4420 let data = [0x1234, 0x5678];
4421 let sub_function = DiagnosticSubFunction::ReturnQueryData;
4422 let err = client_services
4423 .diagnostics(50, unit_id, sub_function, &data)
4424 .err();
4425 assert_eq!(err, Some(MbusError::InvalidTransport));
4426 }
4427
4428 #[test]
4430 fn test_client_services_get_comm_event_counter_success() {
4431 let transport = MockTransport::default();
4432 let app = MockApp::default();
4433 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4434 let mut client_services =
4435 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4436 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4437 let err = client_services.get_comm_event_counter(60, unit_id).err();
4438
4439 assert_eq!(err, Some(MbusError::InvalidTransport));
4440 }
4441
4442 #[test]
4444 fn test_client_services_report_server_id_success() {
4445 let transport = MockTransport::default();
4446 let app = MockApp::default();
4447 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4448 let mut client_services =
4449 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4450
4451 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4452 let err = client_services.report_server_id(70, unit_id).err();
4453
4454 assert_eq!(err, Some(MbusError::InvalidTransport));
4455 }
4456
4457 #[test]
4461 fn test_broadcast_read_multiple_coils_not_allowed() {
4462 let transport = MockTransport::default();
4463 let app = MockApp::default();
4464 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4465 let mut client_services =
4466 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4467
4468 let txn_id = 0x0001;
4469 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4470 let address = 0x0000;
4471 let quantity = 8;
4472 let res = client_services.read_multiple_coils(txn_id, unit_id, address, quantity);
4473 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4474 }
4475
4476 #[test]
4478 fn test_broadcast_write_single_coil_tcp_not_allowed() {
4479 let transport = MockTransport::default();
4480 let app = MockApp::default();
4481 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4482 let mut client_services =
4483 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4484
4485 let txn_id = 0x0002;
4486 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4487 let res = client_services.write_single_coil(txn_id, unit_id, 0x0000, true);
4488 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4489 }
4490
4491 #[test]
4493 fn test_broadcast_write_multiple_coils_tcp_not_allowed() {
4494 let transport = MockTransport::default();
4495 let app = MockApp::default();
4496 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4497 let mut client_services =
4498 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4499
4500 let txn_id = 0x0003;
4501 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4502 let mut values = Coils::new(0x0000, 2).unwrap();
4503 values.set_value(0x0000, true).unwrap();
4504 values.set_value(0x0001, false).unwrap();
4505
4506 let res = client_services.write_multiple_coils(txn_id, unit_id, 0x0000, &values);
4507 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4508 }
4509
4510 #[test]
4512 fn test_broadcast_read_discrete_inputs_not_allowed() {
4513 let transport = MockTransport::default();
4514 let app = MockApp::default();
4515 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4516 let mut client_services =
4517 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4518
4519 let txn_id = 0x0006;
4520 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4521 let res = client_services.read_discrete_inputs(txn_id, unit_id, 0x0000, 2);
4522 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4523 }
4524
4525 #[test]
4528 fn test_client_services_clears_buffer_on_overflow() {
4529 let transport = MockTransport::default();
4530 let app = MockApp::default();
4531 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4532 let mut client_services =
4533 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4534
4535 let initial_garbage = [0xFF; MAX_ADU_FRAME_LEN - 10];
4537 client_services
4538 .rxed_frame
4539 .extend_from_slice(&initial_garbage)
4540 .unwrap();
4541
4542 let chunk = [0xAA; 20];
4544 client_services
4545 .transport
4546 .recv_frames
4547 .borrow_mut()
4548 .push_back(Vec::from_slice(&chunk).unwrap())
4549 .unwrap();
4550
4551 client_services.poll();
4553
4554 assert!(
4555 client_services.rxed_frame.is_empty(),
4556 "Buffer should be cleared on overflow to prevent crashing and recover from stream noise."
4557 );
4558 }
4559}