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::checksum;
1422 use mbus_core::transport::TransportType;
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(transport, app, make_serial_config())
1461 .unwrap()
1462 }
1463
1464 fn make_rtu_exception_adu(
1465 unit_id: UnitIdOrSlaveAddr,
1466 function_code: u8,
1467 exception_code: u8,
1468 ) -> Vec<u8, MAX_ADU_FRAME_LEN> {
1469 let mut frame = Vec::new();
1470 frame.push(unit_id.get()).unwrap();
1471 frame.push(function_code | 0x80).unwrap();
1472 frame.push(exception_code).unwrap();
1473 let crc = checksum::crc16(frame.as_slice()).to_le_bytes();
1474 frame.extend_from_slice(&crc).unwrap();
1475 frame
1476 }
1477
1478 #[derive(Debug, Default)]
1480 struct MockTransport {
1481 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>>,
1484 pub connect_should_fail: bool,
1485 pub send_should_fail: bool,
1486 pub is_connected_flag: RefCell<bool>,
1487 pub transport_type: Option<TransportType>,
1488 }
1489
1490 impl Transport for MockTransport {
1491 type Error = MbusError;
1492
1493 fn connect(&mut self, _config: &ModbusConfig) -> Result<(), Self::Error> {
1494 if self.connect_should_fail {
1495 return Err(MbusError::ConnectionFailed);
1496 }
1497 *self.is_connected_flag.borrow_mut() = true;
1498 Ok(())
1499 }
1500
1501 fn disconnect(&mut self) -> Result<(), Self::Error> {
1502 *self.is_connected_flag.borrow_mut() = false;
1503 Ok(())
1504 }
1505
1506 fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
1507 if self.send_should_fail {
1508 return Err(MbusError::SendFailed);
1509 }
1510 let mut vec_adu = Vec::new();
1511 vec_adu
1512 .extend_from_slice(adu)
1513 .map_err(|_| MbusError::BufferLenMissmatch)?;
1514 self.sent_frames
1515 .borrow_mut()
1516 .push_back(vec_adu)
1517 .map_err(|_| MbusError::BufferLenMissmatch)?;
1518 Ok(())
1519 }
1520
1521 fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
1522 if let Some(err) = self.recv_error.borrow_mut().take() {
1523 return Err(err);
1524 }
1525 self.recv_frames
1526 .borrow_mut()
1527 .pop_front()
1528 .ok_or(MbusError::Timeout)
1529 }
1530
1531 fn is_connected(&self) -> bool {
1532 *self.is_connected_flag.borrow()
1533 }
1534
1535 fn transport_type(&self) -> TransportType {
1536 self.transport_type.unwrap_or(TransportType::StdTcp)
1537 }
1538 }
1539
1540 #[derive(Debug, Default)]
1542 struct MockApp {
1543 pub received_coil_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr, Coils), 10>>, pub received_write_single_coil_responses:
1545 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, bool), 10>>,
1546 pub received_write_multiple_coils_responses:
1547 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1548 pub received_discrete_input_responses:
1549 RefCell<Vec<(u16, UnitIdOrSlaveAddr, DiscreteInputs, u16), 10>>,
1550 pub received_holding_register_responses:
1551 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1552 pub received_input_register_responses:
1553 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1554 pub received_write_single_register_responses:
1555 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1556 pub received_write_multiple_register_responses:
1557 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1558 pub received_read_write_multiple_registers_responses:
1559 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers), 10>>,
1560 pub received_mask_write_register_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1561 pub received_read_fifo_queue_responses:
1562 RefCell<Vec<(u16, UnitIdOrSlaveAddr, FifoQueue), 10>>,
1563 pub received_read_file_record_responses: RefCell<
1564 Vec<
1565 (
1566 u16,
1567 UnitIdOrSlaveAddr,
1568 Vec<SubRequestParams, MAX_SUB_REQUESTS_PER_PDU>,
1569 ),
1570 10,
1571 >,
1572 >,
1573 pub received_write_file_record_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1574 pub received_read_device_id_responses:
1575 RefCell<Vec<(u16, UnitIdOrSlaveAddr, DeviceIdentificationResponse), 10>>,
1576 pub failed_requests: RefCell<Vec<(u16, UnitIdOrSlaveAddr, MbusError), 10>>,
1577
1578 pub current_time: RefCell<u64>, }
1580
1581 impl CoilResponse for MockApp {
1582 fn read_coils_response(
1583 &mut self,
1584 txn_id: u16,
1585 unit_id_slave_addr: UnitIdOrSlaveAddr,
1586 coils: &Coils,
1587 ) {
1588 self.received_coil_responses
1589 .borrow_mut()
1590 .push((txn_id, unit_id_slave_addr, coils.clone()))
1591 .unwrap();
1592 }
1593
1594 fn read_single_coil_response(
1595 &mut self,
1596 txn_id: u16,
1597 unit_id_slave_addr: UnitIdOrSlaveAddr,
1598 address: u16,
1599 value: bool,
1600 ) {
1601 let mut values_vec = [0x00, 1];
1603 values_vec[0] = if value { 0x01 } else { 0x00 }; let coils = Coils::new(address, 1)
1605 .unwrap()
1606 .with_values(&values_vec, 1)
1607 .unwrap();
1608 self.received_coil_responses
1609 .borrow_mut()
1610 .push((txn_id, unit_id_slave_addr, coils))
1611 .unwrap();
1612 }
1613
1614 fn write_single_coil_response(
1615 &mut self,
1616 txn_id: u16,
1617 unit_id_slave_addr: UnitIdOrSlaveAddr,
1618 address: u16,
1619 value: bool,
1620 ) {
1621 self.received_write_single_coil_responses
1622 .borrow_mut()
1623 .push((txn_id, unit_id_slave_addr, address, value))
1624 .unwrap();
1625 }
1626
1627 fn write_multiple_coils_response(
1628 &mut self,
1629 txn_id: u16,
1630 unit_id_slave_addr: UnitIdOrSlaveAddr,
1631 address: u16,
1632 quantity: u16,
1633 ) {
1634 self.received_write_multiple_coils_responses
1635 .borrow_mut()
1636 .push((txn_id, unit_id_slave_addr, address, quantity))
1637 .unwrap();
1638 }
1639 }
1640
1641 impl DiscreteInputResponse for MockApp {
1642 fn read_multiple_discrete_inputs_response(
1643 &mut self,
1644 txn_id: u16,
1645 unit_id_slave_addr: UnitIdOrSlaveAddr,
1646 inputs: &DiscreteInputs,
1647 ) {
1648 self.received_discrete_input_responses
1649 .borrow_mut()
1650 .push((
1651 txn_id,
1652 unit_id_slave_addr,
1653 inputs.clone(),
1654 inputs.quantity(),
1655 ))
1656 .unwrap();
1657 }
1658
1659 fn read_single_discrete_input_response(
1660 &mut self,
1661 txn_id: u16,
1662 unit_id_slave_addr: UnitIdOrSlaveAddr,
1663 address: u16,
1664 value: bool,
1665 ) {
1666 let mut values = [0u8; mbus_core::models::discrete_input::MAX_DISCRETE_INPUT_BYTES];
1667 values[0] = if value { 0x01 } else { 0x00 };
1668 let inputs = DiscreteInputs::new(address, 1)
1669 .unwrap()
1670 .with_values(&values, 1)
1671 .unwrap();
1672 self.received_discrete_input_responses
1673 .borrow_mut()
1674 .push((txn_id, unit_id_slave_addr, inputs, 1))
1675 .unwrap();
1676 }
1677 }
1678
1679 impl RequestErrorNotifier for MockApp {
1680 fn request_failed(
1681 &mut self,
1682 txn_id: u16,
1683 unit_id_slave_addr: UnitIdOrSlaveAddr,
1684 error: MbusError,
1685 ) {
1686 self.failed_requests
1687 .borrow_mut()
1688 .push((txn_id, unit_id_slave_addr, error))
1689 .unwrap();
1690 }
1691 }
1692
1693 impl RegisterResponse for MockApp {
1694 fn read_multiple_holding_registers_response(
1695 &mut self,
1696 txn_id: u16,
1697 unit_id_slave_addr: UnitIdOrSlaveAddr,
1698 registers: &Registers,
1699 ) {
1700 let quantity = registers.quantity();
1701 self.received_holding_register_responses
1702 .borrow_mut()
1703 .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
1704 .unwrap();
1705 }
1706
1707 fn read_single_input_register_response(
1708 &mut self,
1709 txn_id: u16,
1710 unit_id_slave_addr: UnitIdOrSlaveAddr,
1711 address: u16,
1712 value: u16,
1713 ) {
1714 let values = [value];
1716 let registers = Registers::new(address, 1)
1717 .unwrap()
1718 .with_values(&values, 1)
1719 .unwrap();
1720 self.received_input_register_responses
1721 .borrow_mut()
1722 .push((txn_id, unit_id_slave_addr, registers, 1))
1723 .unwrap();
1724 }
1725
1726 fn read_single_holding_register_response(
1727 &mut self,
1728 txn_id: u16,
1729 unit_id_slave_addr: UnitIdOrSlaveAddr,
1730 address: u16,
1731 value: u16,
1732 ) {
1733 let data = [value];
1735 let registers = Registers::new(address, 1)
1737 .unwrap()
1738 .with_values(&data, 1)
1739 .unwrap();
1740
1741 self.received_holding_register_responses
1742 .borrow_mut()
1743 .push((txn_id, unit_id_slave_addr, registers, 1))
1744 .unwrap();
1745 }
1746
1747 fn read_multiple_input_registers_response(
1748 &mut self,
1749 txn_id: u16,
1750 unit_id_slave_addr: UnitIdOrSlaveAddr,
1751 registers: &Registers,
1752 ) {
1753 let quantity = registers.quantity();
1754 self.received_input_register_responses
1755 .borrow_mut()
1756 .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
1757 .unwrap();
1758 }
1759
1760 fn write_single_register_response(
1761 &mut self,
1762 txn_id: u16,
1763 unit_id_slave_addr: UnitIdOrSlaveAddr,
1764 address: u16,
1765 value: u16,
1766 ) {
1767 self.received_write_single_register_responses
1768 .borrow_mut()
1769 .push((txn_id, unit_id_slave_addr, address, value))
1770 .unwrap();
1771 }
1772
1773 fn write_multiple_registers_response(
1774 &mut self,
1775 txn_id: u16,
1776 unit_id_slave_addr: UnitIdOrSlaveAddr,
1777 address: u16,
1778 quantity: u16,
1779 ) {
1780 self.received_write_multiple_register_responses
1781 .borrow_mut()
1782 .push((txn_id, unit_id_slave_addr, address, quantity))
1783 .unwrap();
1784 }
1785
1786 fn read_write_multiple_registers_response(
1787 &mut self,
1788 txn_id: u16,
1789 unit_id_slave_addr: UnitIdOrSlaveAddr,
1790 registers: &Registers,
1791 ) {
1792 self.received_read_write_multiple_registers_responses
1793 .borrow_mut()
1794 .push((txn_id, unit_id_slave_addr, registers.clone()))
1795 .unwrap();
1796 }
1797
1798 fn mask_write_register_response(
1799 &mut self,
1800 txn_id: u16,
1801 unit_id_slave_addr: UnitIdOrSlaveAddr,
1802 ) {
1803 self.received_mask_write_register_responses
1804 .borrow_mut()
1805 .push((txn_id, unit_id_slave_addr))
1806 .unwrap();
1807 }
1808
1809 fn read_single_register_response(
1810 &mut self,
1811 txn_id: u16,
1812 unit_id_slave_addr: UnitIdOrSlaveAddr,
1813 address: u16,
1814 value: u16,
1815 ) {
1816 let data = [value];
1818 let registers = Registers::new(address, 1)
1820 .unwrap()
1821 .with_values(&data, 1)
1822 .unwrap();
1823
1824 self.received_holding_register_responses
1825 .borrow_mut()
1826 .push((txn_id, unit_id_slave_addr, registers, 1))
1827 .unwrap();
1828 }
1829 }
1830
1831 impl FifoQueueResponse for MockApp {
1832 fn read_fifo_queue_response(
1833 &mut self,
1834 txn_id: u16,
1835 unit_id_slave_addr: UnitIdOrSlaveAddr,
1836 fifo_queue: &FifoQueue,
1837 ) {
1838 self.received_read_fifo_queue_responses
1839 .borrow_mut()
1840 .push((txn_id, unit_id_slave_addr, fifo_queue.clone()))
1841 .unwrap();
1842 }
1843 }
1844
1845 impl FileRecordResponse for MockApp {
1846 fn read_file_record_response(
1847 &mut self,
1848 txn_id: u16,
1849 unit_id_slave_addr: UnitIdOrSlaveAddr,
1850 data: &[SubRequestParams],
1851 ) {
1852 let mut vec = Vec::new();
1853 vec.extend_from_slice(data).unwrap();
1854 self.received_read_file_record_responses
1855 .borrow_mut()
1856 .push((txn_id, unit_id_slave_addr, vec))
1857 .unwrap();
1858 }
1859 fn write_file_record_response(
1860 &mut self,
1861 txn_id: u16,
1862 unit_id_slave_addr: UnitIdOrSlaveAddr,
1863 ) {
1864 self.received_write_file_record_responses
1865 .borrow_mut()
1866 .push((txn_id, unit_id_slave_addr))
1867 .unwrap();
1868 }
1869 }
1870
1871 impl DiagnosticsResponse for MockApp {
1872 fn read_device_identification_response(
1873 &mut self,
1874 txn_id: u16,
1875 unit_id_slave_addr: UnitIdOrSlaveAddr,
1876 response: &DeviceIdentificationResponse,
1877 ) {
1878 self.received_read_device_id_responses
1879 .borrow_mut()
1880 .push((txn_id, unit_id_slave_addr, response.clone()))
1881 .unwrap();
1882 }
1883
1884 fn encapsulated_interface_transport_response(
1885 &mut self,
1886 _: u16,
1887 _: UnitIdOrSlaveAddr,
1888 _: EncapsulatedInterfaceType,
1889 _: &[u8],
1890 ) {
1891 }
1892
1893 fn diagnostics_response(
1894 &mut self,
1895 _: u16,
1896 _: UnitIdOrSlaveAddr,
1897 _: DiagnosticSubFunction,
1898 _: &[u16],
1899 ) {
1900 }
1901
1902 fn get_comm_event_counter_response(
1903 &mut self,
1904 _: u16,
1905 _: UnitIdOrSlaveAddr,
1906 _: u16,
1907 _: u16,
1908 ) {
1909 }
1910
1911 fn get_comm_event_log_response(
1912 &mut self,
1913 _: u16,
1914 _: UnitIdOrSlaveAddr,
1915 _: u16,
1916 _: u16,
1917 _: u16,
1918 _: &[u8],
1919 ) {
1920 }
1921
1922 fn read_exception_status_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: u8) {}
1923
1924 fn report_server_id_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: &[u8]) {}
1925 }
1926
1927 impl TimeKeeper for MockApp {
1928 fn current_millis(&self) -> u64 {
1929 *self.current_time.borrow()
1930 }
1931 }
1932
1933 #[test]
1937 fn test_client_services_new_success() {
1938 let transport = MockTransport::default();
1939 let app = MockApp::default();
1940 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1941
1942 let client_services =
1943 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
1944 assert!(client_services.is_ok());
1945 assert!(client_services.unwrap().transport.is_connected());
1946 }
1947
1948 #[test]
1950 fn test_client_services_new_connection_failure() {
1951 let mut transport = MockTransport::default();
1952 transport.connect_should_fail = true;
1953 let app = MockApp::default();
1954 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1955
1956 let client_services =
1957 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
1958 assert!(client_services.is_err());
1959 assert_eq!(client_services.unwrap_err(), MbusError::ConnectionFailed);
1960 }
1961
1962 #[test]
1963 fn test_client_services_new_serial_success() {
1964 let transport = MockTransport {
1965 transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
1966 ..Default::default()
1967 };
1968 let app = MockApp::default();
1969 let serial_config = ModbusSerialConfig {
1970 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
1971 mode: SerialMode::Rtu,
1972 baud_rate: BaudRate::Baud19200,
1973 data_bits: mbus_core::transport::DataBits::Eight,
1974 stop_bits: 1,
1975 parity: Parity::Even,
1976 response_timeout_ms: 1000,
1977 retry_attempts: 1,
1978 retry_backoff_strategy: BackoffStrategy::Immediate,
1979 retry_jitter_strategy: JitterStrategy::None,
1980 retry_random_fn: None,
1981 };
1982
1983 let client_services =
1984 ClientServices::<MockTransport, MockApp, 1>::new_serial(transport, app, serial_config);
1985 assert!(client_services.is_ok());
1986 }
1987
1988 #[test]
1989 fn test_reconnect_success_flushes_pending_requests() {
1990 let transport = MockTransport::default();
1991 let app = MockApp::default();
1992 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1993 let mut client_services =
1994 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
1995
1996 let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
1997 client_services.read_single_coil(10, unit_id, 0).unwrap();
1998 assert_eq!(client_services.expected_responses.len(), 1);
1999
2000 let reconnect_result = client_services.reconnect();
2001 assert!(reconnect_result.is_ok());
2002 assert!(client_services.is_connected());
2003 assert!(client_services.expected_responses.is_empty());
2004
2005 let failed_requests = client_services.app().failed_requests.borrow();
2006 assert_eq!(failed_requests.len(), 1);
2007 assert_eq!(failed_requests[0].0, 10);
2008 assert_eq!(failed_requests[0].2, MbusError::ConnectionLost);
2009 }
2010
2011 #[test]
2012 fn test_reconnect_failure_propagates_connect_error() {
2013 let transport = MockTransport::default();
2014 let app = MockApp::default();
2015 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2016 let mut client_services =
2017 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2018
2019 client_services.transport.connect_should_fail = true;
2020 let reconnect_result = client_services.reconnect();
2021
2022 assert!(reconnect_result.is_err());
2023 assert_eq!(reconnect_result.unwrap_err(), MbusError::ConnectionFailed);
2024 assert!(!client_services.is_connected());
2025 }
2026
2027 #[test]
2029 fn test_read_multiple_coils_sends_valid_adu() {
2030 let transport = MockTransport::default();
2031 let app = MockApp::default();
2032 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2033 let mut client_services =
2034 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2035
2036 let txn_id = 0x0001;
2037 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2038 let address = 0x0000;
2039 let quantity = 8;
2040 client_services
2041 .read_multiple_coils(txn_id, unit_id, address, quantity)
2042 .unwrap();
2043
2044 let sent_frames = client_services.transport.sent_frames.borrow();
2045 assert_eq!(sent_frames.len(), 1);
2046 let sent_adu = sent_frames.front().unwrap();
2047
2048 #[rustfmt::skip]
2050 let expected_adu: [u8; 12] = [
2051 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, ];
2059 assert_eq!(sent_adu.as_slice(), &expected_adu);
2060 }
2061
2062 #[test]
2064 fn test_read_multiple_coils_invalid_quantity() {
2065 let transport = MockTransport::default();
2066 let app = MockApp::default();
2067 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2068 let mut client_services =
2069 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2070
2071 let txn_id = 0x0001;
2072 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2073 let address = 0x0000;
2074 let quantity = 0; let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); assert_eq!(result.unwrap_err(), MbusError::InvalidQuantity);
2078 }
2079
2080 #[test]
2082 fn test_read_multiple_coils_send_failure() {
2083 let mut transport = MockTransport::default();
2084 transport.send_should_fail = true;
2085 let app = MockApp::default();
2086 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2087 let mut client_services =
2088 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2089
2090 let txn_id = 0x0001;
2091 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2092 let address = 0x0000;
2093 let quantity = 8;
2094
2095 let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); assert_eq!(result.unwrap_err(), MbusError::SendFailed);
2097 }
2098
2099 #[test]
2101 fn test_ingest_frame_wrong_fc() {
2102 let transport = MockTransport::default();
2103 let app = MockApp::default();
2104 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2105 let mut client_services =
2106 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2107
2108 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x03, 0x01, 0xB3];
2110
2111 client_services
2112 .transport
2113 .recv_frames
2114 .borrow_mut()
2115 .push_back(Vec::from_slice(&response_adu).unwrap())
2116 .unwrap();
2117 client_services.poll();
2118
2119 let received_responses = client_services.app().received_coil_responses.borrow();
2120 assert!(received_responses.is_empty());
2121 }
2122
2123 #[test]
2125 fn test_ingest_frame_malformed_adu() {
2126 let transport = MockTransport::default();
2127 let app = MockApp::default();
2128 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2129 let mut client_services =
2130 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2131
2132 let malformed_adu = [0x01, 0x02, 0x03];
2134
2135 client_services
2136 .transport
2137 .recv_frames
2138 .borrow_mut()
2139 .push_back(Vec::from_slice(&malformed_adu).unwrap())
2140 .unwrap();
2141 client_services.poll();
2142
2143 let received_responses = client_services.app().received_coil_responses.borrow();
2144 assert!(received_responses.is_empty());
2145 }
2146
2147 #[test]
2149 fn test_ingest_frame_unknown_txn_id() {
2150 let transport = MockTransport::default();
2151 let app = MockApp::default();
2152 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2153 let mut client_services =
2154 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2155
2156 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2158
2159 client_services
2160 .transport
2161 .recv_frames
2162 .borrow_mut()
2163 .push_back(Vec::from_slice(&response_adu).unwrap())
2164 .unwrap();
2165 client_services.poll();
2166
2167 let received_responses = client_services.app().received_coil_responses.borrow();
2168 assert!(received_responses.is_empty());
2169 }
2170
2171 #[test]
2173 fn test_ingest_frame_pdu_parse_failure() {
2174 let transport = MockTransport::default();
2175 let app = MockApp::default();
2176 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2177 let mut client_services =
2178 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2179
2180 let txn_id = 0x0001;
2181 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2182 let address = 0x0000;
2183 let quantity = 8;
2184 client_services
2185 .read_multiple_coils(txn_id, unit_id, address, quantity) .unwrap();
2187
2188 let response_adu = [
2192 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x01, 0xB3, 0x00,
2193 ]; client_services
2196 .transport
2197 .recv_frames
2198 .borrow_mut()
2199 .push_back(Vec::from_slice(&response_adu).unwrap())
2200 .unwrap();
2201 client_services.poll();
2202
2203 let received_responses = client_services.app().received_coil_responses.borrow();
2204 assert!(received_responses.is_empty());
2205 assert!(client_services.expected_responses.is_empty());
2207 }
2208
2209 #[test]
2211 fn test_client_services_read_single_coil_e2e_success() {
2212 let transport = MockTransport::default();
2213 let app = MockApp::default();
2214 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2215 let mut client_services =
2216 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2217
2218 let txn_id = 0x0002;
2219 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2220 let address = 0x0005;
2221
2222 client_services .read_single_coil(txn_id, unit_id, address)
2225 .unwrap();
2226
2227 let sent_adu = client_services
2229 .transport
2230 .sent_frames
2231 .borrow_mut()
2232 .pop_front()
2233 .unwrap();
2234 #[rustfmt::skip]
2236 let expected_adu: [u8; 12] = [
2237 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x05, 0x00, 0x01, ];
2245 assert_eq!(sent_adu.as_slice(), &expected_adu);
2246
2247 let response_adu = [0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x01];
2251
2252 client_services
2254 .transport
2255 .recv_frames
2256 .borrow_mut()
2257 .push_back(Vec::from_slice(&response_adu).unwrap())
2258 .unwrap();
2259 client_services.poll();
2260
2261 let received_responses = client_services.app().received_coil_responses.borrow();
2263 assert_eq!(received_responses.len(), 1);
2264
2265 let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2266 let rcv_quantity = rcv_coils.quantity();
2267 assert_eq!(*rcv_txn_id, txn_id);
2268 assert_eq!(*rcv_unit_id, unit_id);
2269 assert_eq!(rcv_coils.from_address(), address);
2270 assert_eq!(rcv_coils.quantity(), 1); assert_eq!(&rcv_coils.values()[..1], &[0x01]); assert_eq!(rcv_quantity, 1);
2273
2274 assert!(client_services.expected_responses.is_empty());
2276 }
2277
2278 #[test]
2280 fn test_read_single_coil_request_sends_valid_adu() {
2281 let transport = MockTransport::default();
2282 let app = MockApp::default();
2283 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2284 let mut client_services =
2285 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2286
2287 let txn_id = 0x0002;
2288 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2289 let address = 0x0005;
2290
2291 client_services
2292 .read_single_coil(txn_id, unit_id, address) .unwrap();
2294
2295 let sent_frames = client_services.transport.sent_frames.borrow();
2296 assert_eq!(sent_frames.len(), 1);
2297 let sent_adu = sent_frames.front().unwrap();
2298
2299 #[rustfmt::skip]
2301 let expected_adu: [u8; 12] = [
2302 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x05, 0x00, 0x01, ];
2310 assert_eq!(sent_adu.as_slice(), &expected_adu);
2311
2312 assert_eq!(client_services.expected_responses.len(), 1); let single_read = client_services.expected_responses[0]
2315 .operation_meta
2316 .is_single();
2317 assert!(single_read);
2318 }
2319
2320 #[test]
2322 fn test_write_single_coil_sends_valid_adu() {
2323 let transport = MockTransport::default();
2324 let app = MockApp::default();
2325 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2326 let mut client_services =
2327 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2328
2329 let txn_id = 0x0003;
2330 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2331 let address = 0x000A;
2332 let value = true;
2333
2334 client_services
2335 .write_single_coil(txn_id, unit_id, address, value) .unwrap();
2337
2338 let sent_frames = client_services.transport.sent_frames.borrow();
2339 assert_eq!(sent_frames.len(), 1);
2340 let sent_adu = sent_frames.front().unwrap();
2341
2342 #[rustfmt::skip]
2344 let expected_adu: [u8; 12] = [
2345 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, ];
2353 assert_eq!(sent_adu.as_slice(), &expected_adu);
2354
2355 assert_eq!(client_services.expected_responses.len(), 1);
2357 let expected_address = client_services.expected_responses[0]
2358 .operation_meta
2359 .address();
2360 let expected_value = client_services.expected_responses[0].operation_meta.value() != 0;
2361
2362 assert_eq!(expected_address, address);
2363 assert_eq!(expected_value, value);
2364 }
2365
2366 #[test]
2368 fn test_client_services_write_single_coil_e2e_success() {
2369 let transport = MockTransport::default();
2370 let app = MockApp::default();
2371 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2372 let mut client_services =
2373 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2374
2375 let txn_id = 0x0003;
2376 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2377 let address = 0x000A;
2378 let value = true;
2379
2380 client_services .write_single_coil(txn_id, unit_id, address, value)
2383 .unwrap();
2384
2385 let sent_adu = client_services
2387 .transport
2388 .sent_frames
2389 .borrow_mut()
2390 .pop_front()
2391 .unwrap();
2392 #[rustfmt::skip]
2393 let expected_request_adu: [u8; 12] = [
2394 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, ];
2402 assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2403
2404 let response_adu = [
2407 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00,
2408 ];
2409
2410 client_services
2412 .transport
2413 .recv_frames
2414 .borrow_mut()
2415 .push_back(Vec::from_slice(&response_adu).unwrap())
2416 .unwrap();
2417 client_services.poll();
2418
2419 let received_responses = client_services
2421 .app
2422 .received_write_single_coil_responses
2423 .borrow();
2424 assert_eq!(received_responses.len(), 1);
2425
2426 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
2427 assert_eq!(*rcv_txn_id, txn_id);
2428 assert_eq!(*rcv_unit_id, unit_id);
2429 assert_eq!(*rcv_address, address);
2430 assert_eq!(*rcv_value, value);
2431
2432 assert!(client_services.expected_responses.is_empty());
2434 }
2435
2436 #[test]
2438 fn test_write_multiple_coils_sends_valid_adu() {
2439 let transport = MockTransport::default();
2440 let app = MockApp::default();
2441 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2442 let mut client_services =
2443 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2444
2445 let txn_id = 0x0004;
2446 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2447 let address = 0x0000;
2448 let quantity = 10;
2449
2450 let mut values = Coils::new(address, quantity).unwrap();
2452 for i in 0..quantity {
2453 values.set_value(address + i, i % 2 == 0).unwrap();
2454 }
2455
2456 client_services
2457 .write_multiple_coils(txn_id, unit_id, address, &values) .unwrap();
2459
2460 let sent_frames = client_services.transport.sent_frames.borrow();
2461 assert_eq!(sent_frames.len(), 1);
2462 let sent_adu = sent_frames.front().unwrap();
2463
2464 #[rustfmt::skip]
2466 let expected_adu: [u8; 15] = [
2467 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0x55, 0x01, ];
2477 assert_eq!(sent_adu.as_slice(), &expected_adu);
2478
2479 assert_eq!(client_services.expected_responses.len(), 1);
2481 let expected_address = client_services.expected_responses[0]
2482 .operation_meta
2483 .address();
2484 let expected_quantity = client_services.expected_responses[0]
2485 .operation_meta
2486 .quantity();
2487 assert_eq!(expected_address, address);
2488 assert_eq!(expected_quantity, quantity);
2489 }
2490
2491 #[test]
2493 fn test_client_services_write_multiple_coils_e2e_success() {
2494 let transport = MockTransport::default();
2495 let app = MockApp::default();
2496 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2497 let mut client_services =
2498 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2499
2500 let txn_id = 0x0004;
2501 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2502 let address = 0x0000;
2503 let quantity = 10;
2504
2505 let mut values = Coils::new(address, quantity).unwrap();
2507 for i in 0..quantity {
2508 values.set_value(address + i, i % 2 == 0).unwrap();
2509 }
2510
2511 client_services .write_multiple_coils(txn_id, unit_id, address, &values)
2514 .unwrap();
2515
2516 let sent_adu = client_services
2518 .transport
2519 .sent_frames
2520 .borrow_mut()
2521 .pop_front()
2522 .unwrap();
2523 #[rustfmt::skip]
2524 let expected_request_adu: [u8; 15] = [
2525 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0x55, 0x01, ];
2535 assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2536
2537 let response_adu = [
2540 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A,
2541 ];
2542
2543 client_services
2545 .transport
2546 .recv_frames
2547 .borrow_mut()
2548 .push_back(Vec::from_slice(&response_adu).unwrap())
2549 .unwrap();
2550 client_services.poll();
2551
2552 let received_responses = client_services
2554 .app
2555 .received_write_multiple_coils_responses
2556 .borrow();
2557 assert_eq!(received_responses.len(), 1);
2558
2559 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
2560 assert_eq!(*rcv_txn_id, txn_id);
2561 assert_eq!(*rcv_unit_id, unit_id);
2562 assert_eq!(*rcv_address, address);
2563 assert_eq!(*rcv_quantity, quantity);
2564
2565 assert!(client_services.expected_responses.is_empty());
2567 }
2568
2569 #[test]
2571 fn test_client_services_read_coils_e2e_success() {
2572 let transport = MockTransport::default();
2573 let app = MockApp::default();
2574 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2575 let mut client_services =
2576 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2577
2578 let txn_id = 0x0001;
2579 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2580 let address = 0x0000;
2581 let quantity = 8;
2582 client_services
2583 .read_multiple_coils(txn_id, unit_id, address, quantity) .unwrap();
2585
2586 let sent_adu = client_services
2588 .transport
2589 .sent_frames
2590 .borrow_mut()
2591 .pop_front()
2592 .unwrap(); assert_eq!(
2595 sent_adu.as_slice(),
2596 &[
2597 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08
2598 ]
2599 );
2600
2601 assert_eq!(client_services.expected_responses.len(), 1); let from_address = client_services.expected_responses[0]
2604 .operation_meta
2605 .address();
2606 let expected_quantity = client_services.expected_responses[0]
2607 .operation_meta
2608 .quantity();
2609
2610 assert_eq!(expected_quantity, quantity);
2611 assert_eq!(from_address, address);
2612
2613 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2617
2618 client_services
2620 .transport
2621 .recv_frames
2622 .borrow_mut()
2623 .push_back(Vec::from_slice(&response_adu).unwrap())
2624 .unwrap();
2625 client_services.poll(); let received_responses = client_services.app().received_coil_responses.borrow();
2631 assert_eq!(received_responses.len(), 1);
2632
2633 let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2634 let rcv_quantity = rcv_coils.quantity();
2635 assert_eq!(*rcv_txn_id, txn_id);
2636 assert_eq!(*rcv_unit_id, unit_id);
2637 assert_eq!(rcv_coils.from_address(), address);
2638 assert_eq!(rcv_coils.quantity(), quantity);
2639 assert_eq!(&rcv_coils.values()[..1], &[0xB3]);
2640 assert_eq!(rcv_quantity, quantity);
2641
2642 assert!(client_services.expected_responses.is_empty());
2644 }
2645
2646 #[test]
2648 fn test_client_services_timeout_with_retry() {
2649 let transport = MockTransport::default();
2650 transport.recv_frames.borrow_mut().clear();
2652 let app = MockApp::default();
2653 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2654 tcp_config.response_timeout_ms = 100; tcp_config.retry_attempts = 1; let config = ModbusConfig::Tcp(tcp_config);
2657
2658 let mut client_services =
2659 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2660
2661 let txn_id = 0x0005;
2662 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2663 let address = 0x0000;
2664
2665 client_services
2666 .read_single_coil(txn_id, unit_id, address)
2667 .unwrap();
2668
2669 *client_services.app().current_time.borrow_mut() = 150;
2671 client_services.poll(); assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2676 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;
2681 client_services.poll(); assert!(client_services.expected_responses.is_empty());
2686 }
2688
2689 #[test]
2691 fn test_client_services_concurrent_timeouts() {
2692 let transport = MockTransport::default();
2693 let app = MockApp::default();
2694
2695 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2697 tcp_config.response_timeout_ms = 100;
2698 tcp_config.retry_attempts = 1;
2699 let config = ModbusConfig::Tcp(tcp_config);
2700
2701 let mut client_services =
2702 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2703
2704 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2705
2706 client_services
2708 .read_single_coil(1, unit_id, 0x0000)
2709 .unwrap();
2710 client_services
2711 .read_single_coil(2, unit_id, 0x0001)
2712 .unwrap();
2713
2714 assert_eq!(client_services.expected_responses.len(), 2);
2716 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2717
2718 *client_services.app().current_time.borrow_mut() = 150;
2720
2721 client_services.poll();
2723
2724 assert_eq!(client_services.expected_responses.len(), 2);
2726 assert_eq!(client_services.expected_responses[0].retries_left, 0);
2727 assert_eq!(client_services.expected_responses[1].retries_left, 0);
2728
2729 assert_eq!(client_services.transport.sent_frames.borrow().len(), 4);
2731
2732 *client_services.app().current_time.borrow_mut() = 300;
2734
2735 client_services.poll();
2737
2738 assert!(client_services.expected_responses.is_empty());
2740
2741 let failed_requests = client_services.app().failed_requests.borrow();
2743 assert_eq!(failed_requests.len(), 2);
2744
2745 let has_txn_1 = failed_requests
2747 .iter()
2748 .any(|(txn, _, err)| *txn == 1 && *err == MbusError::NoRetriesLeft);
2749 let has_txn_2 = failed_requests
2750 .iter()
2751 .any(|(txn, _, err)| *txn == 2 && *err == MbusError::NoRetriesLeft);
2752 assert!(has_txn_1, "Transaction 1 should have failed");
2753 assert!(has_txn_2, "Transaction 2 should have failed");
2754 }
2755
2756 #[test]
2757 fn test_poll_connection_loss_flushes_pending_requests() {
2758 let transport = MockTransport::default();
2759 let app = MockApp::default();
2760 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2761 let mut client_services =
2762 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2763
2764 let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
2765 client_services.read_single_coil(1, unit_id, 0).unwrap();
2766 client_services.read_single_coil(2, unit_id, 1).unwrap();
2767 assert_eq!(client_services.expected_responses.len(), 2);
2768
2769 *client_services.transport.is_connected_flag.borrow_mut() = false;
2770 *client_services.transport.recv_error.borrow_mut() = Some(MbusError::ConnectionClosed);
2771
2772 client_services.poll();
2773
2774 assert!(client_services.expected_responses.is_empty());
2775 assert_eq!(client_services.next_timeout_check, None);
2776
2777 let failed_requests = client_services.app().failed_requests.borrow();
2778 assert_eq!(failed_requests.len(), 2);
2779 assert!(
2780 failed_requests
2781 .iter()
2782 .all(|(txn, _, err)| (*txn == 1 || *txn == 2) && *err == MbusError::ConnectionLost)
2783 );
2784 }
2785
2786 #[test]
2787 fn test_fixed_backoff_schedules_and_does_not_retry_early() {
2788 let transport = MockTransport::default();
2789 let app = MockApp::default();
2790 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2791 tcp_config.response_timeout_ms = 100;
2792 tcp_config.retry_attempts = 1;
2793 tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 50 };
2794 let config = ModbusConfig::Tcp(tcp_config);
2795
2796 let mut client_services =
2797 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2798
2799 client_services
2800 .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2801 .unwrap();
2802 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2803
2804 *client_services.app().current_time.borrow_mut() = 101;
2805 client_services.poll();
2806 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2807 assert_eq!(
2808 client_services.expected_responses[0].next_retry_timestamp,
2809 Some(151)
2810 );
2811
2812 *client_services.app().current_time.borrow_mut() = 150;
2813 client_services.poll();
2814 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2815
2816 *client_services.app().current_time.borrow_mut() = 151;
2817 client_services.poll();
2818 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2819 }
2820
2821 #[test]
2822 fn test_exponential_backoff_growth() {
2823 let transport = MockTransport::default();
2824 let app = MockApp::default();
2825 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2826 tcp_config.response_timeout_ms = 100;
2827 tcp_config.retry_attempts = 2;
2828 tcp_config.retry_backoff_strategy = BackoffStrategy::Exponential {
2829 base_delay_ms: 50,
2830 max_delay_ms: 500,
2831 };
2832 let config = ModbusConfig::Tcp(tcp_config);
2833
2834 let mut client_services =
2835 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2836
2837 client_services
2838 .read_single_coil(7, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2839 .unwrap();
2840
2841 *client_services.app().current_time.borrow_mut() = 101;
2842 client_services.poll();
2843 assert_eq!(
2844 client_services.expected_responses[0].next_retry_timestamp,
2845 Some(151)
2846 );
2847
2848 *client_services.app().current_time.borrow_mut() = 151;
2849 client_services.poll();
2850 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2851
2852 *client_services.app().current_time.borrow_mut() = 252;
2853 client_services.poll();
2854 assert_eq!(
2855 client_services.expected_responses[0].next_retry_timestamp,
2856 Some(352)
2857 );
2858
2859 *client_services.app().current_time.borrow_mut() = 352;
2860 client_services.poll();
2861 assert_eq!(client_services.transport.sent_frames.borrow().len(), 3);
2862 }
2863
2864 #[test]
2865 fn test_jitter_bounds_with_random_source_lower_bound() {
2866 let transport = MockTransport::default();
2867 let app = MockApp::default();
2868 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2869 tcp_config.response_timeout_ms = 100;
2870 tcp_config.retry_attempts = 1;
2871 tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2872 tcp_config.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2873 tcp_config.retry_random_fn = Some(rand_zero);
2874 let config = ModbusConfig::Tcp(tcp_config);
2875
2876 let mut client_services =
2877 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2878 client_services
2879 .read_single_coil(10, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2880 .unwrap();
2881
2882 *client_services.app().current_time.borrow_mut() = 101;
2883 client_services.poll();
2884 assert_eq!(
2885 client_services.expected_responses[0].next_retry_timestamp,
2886 Some(181)
2887 );
2888 }
2889
2890 #[test]
2891 fn test_jitter_bounds_with_random_source_upper_bound() {
2892 let transport3 = MockTransport::default();
2893 let app3 = MockApp::default();
2894 let mut tcp_config3 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2895 tcp_config3.response_timeout_ms = 100;
2896 tcp_config3.retry_attempts = 1;
2897 tcp_config3.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2898 tcp_config3.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2899 tcp_config3.retry_random_fn = Some(rand_upper_percent_20);
2900 let config3 = ModbusConfig::Tcp(tcp_config3);
2901
2902 let mut client_services3 =
2903 ClientServices::<MockTransport, MockApp, 10>::new(transport3, app3, config3).unwrap();
2904 client_services3
2905 .read_single_coil(12, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2906 .unwrap();
2907
2908 *client_services3.app.current_time.borrow_mut() = 101;
2909 client_services3.poll();
2910 assert_eq!(
2911 client_services3.expected_responses[0].next_retry_timestamp,
2912 Some(221)
2913 );
2914 }
2915
2916 #[test]
2917 fn test_jitter_falls_back_without_random_source() {
2918 let transport2 = MockTransport::default();
2919 let app2 = MockApp::default();
2920 let mut tcp_config2 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2921 tcp_config2.response_timeout_ms = 100;
2922 tcp_config2.retry_attempts = 1;
2923 tcp_config2.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2924 tcp_config2.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2925 tcp_config2.retry_random_fn = None;
2926 let config2 = ModbusConfig::Tcp(tcp_config2);
2927
2928 let mut client_services2 =
2929 ClientServices::<MockTransport, MockApp, 10>::new(transport2, app2, config2).unwrap();
2930 client_services2
2931 .read_single_coil(11, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2932 .unwrap();
2933
2934 *client_services2.app.current_time.borrow_mut() = 101;
2935 client_services2.poll();
2936 assert_eq!(
2937 client_services2.expected_responses[0].next_retry_timestamp,
2938 Some(201)
2939 );
2940 }
2941
2942 #[test]
2943 fn test_serial_retry_scheduling_uses_backoff() {
2944 let transport = MockTransport {
2945 transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
2946 ..Default::default()
2947 };
2948 let app = MockApp::default();
2949
2950 let serial_config = ModbusSerialConfig {
2951 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
2952 mode: SerialMode::Rtu,
2953 baud_rate: BaudRate::Baud9600,
2954 data_bits: mbus_core::transport::DataBits::Eight,
2955 stop_bits: 1,
2956 parity: Parity::None,
2957 response_timeout_ms: 100,
2958 retry_attempts: 1,
2959 retry_backoff_strategy: BackoffStrategy::Fixed { delay_ms: 25 },
2960 retry_jitter_strategy: JitterStrategy::None,
2961 retry_random_fn: None,
2962 };
2963
2964 let mut client_services = ClientServices::<MockTransport, MockApp, 1>::new(
2965 transport,
2966 app,
2967 ModbusConfig::Serial(serial_config),
2968 )
2969 .unwrap();
2970
2971 client_services
2972 .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2973 .unwrap();
2974
2975 *client_services.app().current_time.borrow_mut() = 101;
2976 client_services.poll();
2977 assert_eq!(
2978 client_services.expected_responses[0].next_retry_timestamp,
2979 Some(126)
2980 );
2981
2982 *client_services.app().current_time.borrow_mut() = 126;
2983 client_services.poll();
2984 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2985 }
2986
2987 #[test]
2989 fn test_too_many_requests_error() {
2990 let transport = MockTransport::default();
2991 let app = MockApp::default();
2992 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2993 let mut client_services =
2995 ClientServices::<MockTransport, MockApp, 1>::new(transport, app, config).unwrap();
2996
2997 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2998 client_services
3000 .read_multiple_coils(1, unit_id, 0, 1)
3001 .unwrap();
3002 assert_eq!(client_services.expected_responses.len(), 1);
3003
3004 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3005 let result = client_services.read_multiple_coils(2, unit_id, 0, 1);
3007 assert!(result.is_err());
3008 assert_eq!(result.unwrap_err(), MbusError::TooManyRequests);
3009 assert_eq!(client_services.expected_responses.len(), 1); }
3011
3012 #[test]
3014 fn test_read_holding_registers_sends_valid_adu() {
3015 let transport = MockTransport::default();
3016 let app = MockApp::default();
3017 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3018 let mut client_services =
3019 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3020
3021 let txn_id = 0x0005;
3022 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3023 let address = 0x0000;
3024 let quantity = 2;
3025 client_services
3026 .read_holding_registers(txn_id, unit_id, address, quantity)
3027 .unwrap();
3028
3029 let sent_frames = client_services.transport.sent_frames.borrow();
3030 assert_eq!(sent_frames.len(), 1);
3031 let sent_adu = sent_frames.front().unwrap();
3032
3033 #[rustfmt::skip]
3035 let expected_adu: [u8; 12] = [
3036 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, ];
3044 assert_eq!(sent_adu.as_slice(), &expected_adu);
3045 }
3046
3047 #[test]
3049 fn test_client_services_read_holding_registers_e2e_success() {
3050 let transport = MockTransport::default();
3051 let app = MockApp::default();
3052 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3053 let mut client_services =
3054 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3055
3056 let txn_id = 0x0005;
3057 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3058 let address = 0x0000;
3059 let quantity = 2;
3060 client_services
3061 .read_holding_registers(txn_id, unit_id, address, quantity)
3062 .unwrap();
3063
3064 let response_adu = [
3067 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x01, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78,
3068 ];
3069 client_services
3070 .transport
3071 .recv_frames
3072 .borrow_mut()
3073 .push_back(Vec::from_slice(&response_adu).unwrap())
3074 .unwrap();
3075 client_services.poll();
3076
3077 let received_responses = client_services
3078 .app
3079 .received_holding_register_responses
3080 .borrow();
3081 assert_eq!(received_responses.len(), 1);
3082 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3083 assert_eq!(*rcv_txn_id, txn_id);
3084 assert_eq!(*rcv_unit_id, unit_id);
3085 assert_eq!(rcv_registers.from_address(), address);
3086 assert_eq!(rcv_registers.quantity(), quantity);
3087 assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3088 assert_eq!(*rcv_quantity, quantity);
3089 assert!(client_services.expected_responses.is_empty());
3090 }
3091
3092 #[test]
3094 fn test_read_input_registers_sends_valid_adu() {
3095 let transport = MockTransport::default();
3096 let app = MockApp::default();
3097 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3098 let mut client_services =
3099 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3100
3101 let txn_id = 0x0006;
3102 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3103 let address = 0x0000;
3104 let quantity = 2;
3105 client_services
3106 .read_input_registers(txn_id, unit_id, address, quantity)
3107 .unwrap();
3108
3109 let sent_frames = client_services.transport.sent_frames.borrow();
3110 assert_eq!(sent_frames.len(), 1);
3111 let sent_adu = sent_frames.front().unwrap();
3112
3113 #[rustfmt::skip]
3115 let expected_adu: [u8; 12] = [
3116 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x00, 0x00, 0x02, ];
3124 assert_eq!(sent_adu.as_slice(), &expected_adu);
3125 }
3126
3127 #[test]
3129 fn test_client_services_read_input_registers_e2e_success() {
3130 let transport = MockTransport::default();
3131 let app = MockApp::default();
3132 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3133 let mut client_services =
3134 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3135
3136 let txn_id = 0x0006;
3137 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3138 let address = 0x0000;
3139 let quantity = 2;
3140 client_services
3141 .read_input_registers(txn_id, unit_id, address, quantity)
3142 .unwrap();
3143
3144 let response_adu = [
3147 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x01, 0x04, 0x04, 0xAA, 0xBB, 0xCC, 0xDD,
3148 ];
3149 client_services
3150 .transport
3151 .recv_frames
3152 .borrow_mut()
3153 .push_back(Vec::from_slice(&response_adu).unwrap())
3154 .unwrap();
3155 client_services.poll();
3156
3157 let received_responses = client_services
3158 .app
3159 .received_input_register_responses
3160 .borrow();
3161 assert_eq!(received_responses.len(), 1);
3162 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3163 assert_eq!(*rcv_txn_id, txn_id);
3164 assert_eq!(*rcv_unit_id, unit_id);
3165 assert_eq!(rcv_registers.from_address(), address);
3166 assert_eq!(rcv_registers.quantity(), quantity);
3167 assert_eq!(&rcv_registers.values()[..2], &[0xAABB, 0xCCDD]);
3168 assert_eq!(*rcv_quantity, quantity);
3169 assert!(client_services.expected_responses.is_empty());
3170 }
3171
3172 #[test]
3174 fn test_write_single_register_sends_valid_adu() {
3175 let transport = MockTransport::default();
3176 let app = MockApp::default();
3177 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3178 let mut client_services =
3179 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3180
3181 let txn_id = 0x0007;
3182 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3183 let address = 0x0001;
3184 let value = 0x1234;
3185 client_services
3186 .write_single_register(txn_id, unit_id, address, value)
3187 .unwrap();
3188
3189 let sent_frames = client_services.transport.sent_frames.borrow();
3190 assert_eq!(sent_frames.len(), 1);
3191 let sent_adu = sent_frames.front().unwrap();
3192
3193 #[rustfmt::skip]
3195 let expected_adu: [u8; 12] = [
3196 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34, ];
3204 assert_eq!(sent_adu.as_slice(), &expected_adu);
3205 }
3206
3207 #[test]
3209 fn test_client_services_write_single_register_e2e_success() {
3210 let transport = MockTransport::default();
3211 let app = MockApp::default();
3212 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3213 let mut client_services =
3214 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3215
3216 let txn_id = 0x0007;
3217 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3218 let address = 0x0001;
3219 let value = 0x1234;
3220 client_services
3221 .write_single_register(txn_id, unit_id, address, value)
3222 .unwrap();
3223
3224 let response_adu = [
3227 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34,
3228 ];
3229 client_services
3230 .transport
3231 .recv_frames
3232 .borrow_mut()
3233 .push_back(Vec::from_slice(&response_adu).unwrap())
3234 .unwrap();
3235 client_services.poll();
3236
3237 let received_responses = client_services
3238 .app
3239 .received_write_single_register_responses
3240 .borrow();
3241 assert_eq!(received_responses.len(), 1);
3242 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
3243 assert_eq!(*rcv_txn_id, txn_id);
3244 assert_eq!(*rcv_unit_id, unit_id);
3245 assert_eq!(*rcv_address, address);
3246 assert_eq!(*rcv_value, value);
3247 assert!(client_services.expected_responses.is_empty());
3248 }
3249
3250 #[test]
3252 fn test_write_multiple_registers_sends_valid_adu() {
3253 let transport = MockTransport::default();
3254 let app = MockApp::default();
3255 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3256 let mut client_services =
3257 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3258
3259 let txn_id = 0x0008;
3260 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3261 let address = 0x0001;
3262 let quantity = 2;
3263 let values = [0x1234, 0x5678];
3264 client_services
3265 .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3266 .unwrap();
3267
3268 let sent_frames = client_services.transport.sent_frames.borrow();
3269 assert_eq!(sent_frames.len(), 1);
3270 let sent_adu = sent_frames.front().unwrap();
3271
3272 #[rustfmt::skip]
3274 let expected_adu: [u8; 17] = [ 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x34, 0x56, 0x78, ];
3285 assert_eq!(sent_adu.as_slice(), &expected_adu);
3286 }
3287
3288 #[test]
3290 fn test_client_services_write_multiple_registers_e2e_success() {
3291 let transport = MockTransport::default();
3292 let app = MockApp::default();
3293 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3294 let mut client_services =
3295 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3296
3297 let txn_id = 0x0008;
3298 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3299 let address = 0x0001;
3300 let quantity = 2;
3301 let values = [0x1234, 0x5678];
3302 client_services
3303 .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3304 .unwrap();
3305
3306 let response_adu = [
3309 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02,
3310 ];
3311 client_services
3312 .transport
3313 .recv_frames
3314 .borrow_mut()
3315 .push_back(Vec::from_slice(&response_adu).unwrap())
3316 .unwrap();
3317 client_services.poll();
3318
3319 let received_responses = client_services
3320 .app
3321 .received_write_multiple_register_responses
3322 .borrow();
3323 assert_eq!(received_responses.len(), 1);
3324 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
3325 assert_eq!(*rcv_txn_id, txn_id);
3326 assert_eq!(*rcv_unit_id, unit_id);
3327 assert_eq!(*rcv_address, address);
3328 assert_eq!(*rcv_quantity, quantity);
3329 assert!(client_services.expected_responses.is_empty());
3330 }
3331
3332 #[test]
3334 fn test_client_services_handles_exception_response() {
3335 let transport = MockTransport::default();
3336 let app = MockApp::default();
3337 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3338 let mut client_services =
3339 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3340
3341 let txn_id = 0x0009;
3342 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3343 let address = 0x0000;
3344 let quantity = 1;
3345
3346 client_services
3347 .read_holding_registers(txn_id, unit_id, address, quantity)
3348 .unwrap();
3349
3350 let exception_adu = [
3353 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x01, 0x83, 0x02, ];
3360 client_services
3361 .transport
3362 .recv_frames
3363 .borrow_mut()
3364 .push_back(Vec::from_slice(&exception_adu).unwrap())
3365 .unwrap();
3366 client_services.poll();
3367
3368 assert!(
3370 client_services
3371 .app
3372 .received_holding_register_responses
3373 .borrow()
3374 .is_empty()
3375 );
3376 assert_eq!(client_services.app().failed_requests.borrow().len(), 1);
3378 let (failed_txn, failed_unit, failed_err) =
3379 &client_services.app().failed_requests.borrow()[0];
3380 assert_eq!(*failed_txn, txn_id);
3381 assert_eq!(*failed_unit, unit_id);
3382 assert_eq!(*failed_err, MbusError::ModbusException(0x02));
3383 }
3384
3385 #[test]
3386 fn test_serial_exception_coil_response_fails_immediately_with_request_txn_id() {
3387 let mut client_services = make_serial_client();
3388
3389 let txn_id = 0x2001;
3390 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3391 let mut values = Coils::new(0x0000, 10).unwrap();
3392 values.set_value(0x0000, true).unwrap();
3393 values.set_value(0x0001, false).unwrap();
3394 values.set_value(0x0002, true).unwrap();
3395 values.set_value(0x0003, false).unwrap();
3396 values.set_value(0x0004, true).unwrap();
3397 values.set_value(0x0005, false).unwrap();
3398 values.set_value(0x0006, true).unwrap();
3399 values.set_value(0x0007, false).unwrap();
3400 values.set_value(0x0008, true).unwrap();
3401 values.set_value(0x0009, false).unwrap();
3402
3403 client_services
3404 .write_multiple_coils(txn_id, unit_id, 0x0000, &values)
3405 .unwrap();
3406
3407 let exception_adu = make_rtu_exception_adu(unit_id, 0x0F, 0x01);
3408 client_services
3409 .transport
3410 .recv_frames
3411 .borrow_mut()
3412 .push_back(exception_adu)
3413 .unwrap();
3414
3415 client_services.poll();
3416
3417 let failed = client_services.app().failed_requests.borrow();
3418 assert_eq!(failed.len(), 1);
3419 assert_eq!(failed[0].0, txn_id);
3420 assert_eq!(failed[0].1, unit_id);
3421 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
3422 assert!(
3423 client_services
3424 .app
3425 .received_write_multiple_coils_responses
3426 .borrow()
3427 .is_empty()
3428 );
3429 assert!(client_services.expected_responses.is_empty());
3430 }
3431
3432 #[test]
3433 fn test_serial_exception_register_response_fails_immediately_with_request_txn_id() {
3434 let mut client_services = make_serial_client();
3435
3436 let txn_id = 0x2002;
3437 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3438 client_services
3439 .read_holding_registers(txn_id, unit_id, 0x0000, 1)
3440 .unwrap();
3441
3442 let exception_adu = make_rtu_exception_adu(unit_id, 0x03, 0x02);
3443 client_services
3444 .transport
3445 .recv_frames
3446 .borrow_mut()
3447 .push_back(exception_adu)
3448 .unwrap();
3449
3450 client_services.poll();
3451
3452 let failed = client_services.app().failed_requests.borrow();
3453 assert_eq!(failed.len(), 1);
3454 assert_eq!(failed[0].0, txn_id);
3455 assert_eq!(failed[0].1, unit_id);
3456 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
3457 assert!(
3458 client_services
3459 .app
3460 .received_holding_register_responses
3461 .borrow()
3462 .is_empty()
3463 );
3464 assert!(client_services.expected_responses.is_empty());
3465 }
3466
3467 #[test]
3468 fn test_serial_exception_discrete_input_response_fails_immediately_with_request_txn_id() {
3469 let mut client_services = make_serial_client();
3470
3471 let txn_id = 0x2003;
3472 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3473 client_services
3474 .read_discrete_inputs(txn_id, unit_id, 0x0000, 8)
3475 .unwrap();
3476
3477 let exception_adu = make_rtu_exception_adu(unit_id, 0x02, 0x02);
3478 client_services
3479 .transport
3480 .recv_frames
3481 .borrow_mut()
3482 .push_back(exception_adu)
3483 .unwrap();
3484
3485 client_services.poll();
3486
3487 let failed = client_services.app().failed_requests.borrow();
3488 assert_eq!(failed.len(), 1);
3489 assert_eq!(failed[0].0, txn_id);
3490 assert_eq!(failed[0].1, unit_id);
3491 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
3492 assert!(
3493 client_services
3494 .app
3495 .received_discrete_input_responses
3496 .borrow()
3497 .is_empty()
3498 );
3499 assert!(client_services.expected_responses.is_empty());
3500 }
3501
3502 #[test]
3503 fn test_serial_exception_fifo_response_fails_immediately_with_request_txn_id() {
3504 let mut client_services = make_serial_client();
3505
3506 let txn_id = 0x2004;
3507 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3508 client_services
3509 .read_fifo_queue(txn_id, unit_id, 0x0001)
3510 .unwrap();
3511
3512 let exception_adu = make_rtu_exception_adu(unit_id, 0x18, 0x01);
3513 client_services
3514 .transport
3515 .recv_frames
3516 .borrow_mut()
3517 .push_back(exception_adu)
3518 .unwrap();
3519
3520 client_services.poll();
3521
3522 let failed = client_services.app().failed_requests.borrow();
3523 assert_eq!(failed.len(), 1);
3524 assert_eq!(failed[0].0, txn_id);
3525 assert_eq!(failed[0].1, unit_id);
3526 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
3527 assert!(
3528 client_services
3529 .app
3530 .received_read_fifo_queue_responses
3531 .borrow()
3532 .is_empty()
3533 );
3534 assert!(client_services.expected_responses.is_empty());
3535 }
3536
3537 #[test]
3538 fn test_serial_exception_file_record_response_fails_immediately_with_request_txn_id() {
3539 let mut client_services = make_serial_client();
3540
3541 let txn_id = 0x2005;
3542 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3543 let mut sub_req = SubRequest::new();
3544 sub_req.add_read_sub_request(4, 1, 2).unwrap();
3545 client_services
3546 .read_file_record(txn_id, unit_id, &sub_req)
3547 .unwrap();
3548
3549 let exception_adu = make_rtu_exception_adu(unit_id, 0x14, 0x02);
3550 client_services
3551 .transport
3552 .recv_frames
3553 .borrow_mut()
3554 .push_back(exception_adu)
3555 .unwrap();
3556
3557 client_services.poll();
3558
3559 let failed = client_services.app().failed_requests.borrow();
3560 assert_eq!(failed.len(), 1);
3561 assert_eq!(failed[0].0, txn_id);
3562 assert_eq!(failed[0].1, unit_id);
3563 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
3564 assert!(
3565 client_services
3566 .app
3567 .received_read_file_record_responses
3568 .borrow()
3569 .is_empty()
3570 );
3571 assert!(client_services.expected_responses.is_empty());
3572 }
3573
3574 #[test]
3575 fn test_serial_exception_diagnostic_response_fails_immediately_with_request_txn_id() {
3576 let mut client_services = make_serial_client();
3577
3578 let txn_id = 0x2006;
3579 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3580 client_services
3581 .read_device_identification(
3582 txn_id,
3583 unit_id,
3584 ReadDeviceIdCode::Basic,
3585 ObjectId::from(0x00),
3586 )
3587 .unwrap();
3588
3589 let exception_adu = make_rtu_exception_adu(unit_id, 0x2B, 0x01);
3590 client_services
3591 .transport
3592 .recv_frames
3593 .borrow_mut()
3594 .push_back(exception_adu)
3595 .unwrap();
3596
3597 client_services.poll();
3598
3599 let failed = client_services.app().failed_requests.borrow();
3600 assert_eq!(failed.len(), 1);
3601 assert_eq!(failed[0].0, txn_id);
3602 assert_eq!(failed[0].1, unit_id);
3603 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
3604 assert!(
3605 client_services
3606 .app
3607 .received_read_device_id_responses
3608 .borrow()
3609 .is_empty()
3610 );
3611 assert!(client_services.expected_responses.is_empty());
3612 }
3613
3614 #[test]
3616 fn test_read_single_holding_register_sends_valid_adu() {
3617 let transport = MockTransport::default();
3618 let app = MockApp::default();
3619 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3620 let mut client_services =
3621 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3622
3623 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3624 client_services
3625 .read_single_holding_register(10, unit_id, 100)
3626 .unwrap();
3627
3628 let sent_frames = client_services.transport.sent_frames.borrow();
3629 assert_eq!(sent_frames.len(), 1);
3630 let sent_adu = sent_frames.front().unwrap();
3631
3632 #[rustfmt::skip]
3633 let expected_adu: [u8; 12] = [
3634 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x64, 0x00, 0x01, ];
3642 assert_eq!(sent_adu.as_slice(), &expected_adu);
3643
3644 assert_eq!(client_services.expected_responses.len(), 1);
3646 let single_read = client_services.expected_responses[0]
3647 .operation_meta
3648 .is_single();
3649 assert!(single_read);
3650 }
3651
3652 #[test]
3654 fn test_client_services_read_single_holding_register_e2e_success() {
3655 let transport = MockTransport::default();
3656 let app = MockApp::default();
3657 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3658 let mut client_services =
3659 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3660
3661 let txn_id = 10;
3662 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3663 let address = 100;
3664
3665 client_services
3666 .read_single_holding_register(txn_id, unit_id, address)
3667 .unwrap();
3668
3669 let response_adu = [
3671 0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x03, 0x02, 0x12, 0x34,
3672 ];
3673 client_services
3674 .transport
3675 .recv_frames
3676 .borrow_mut()
3677 .push_back(Vec::from_slice(&response_adu).unwrap())
3678 .unwrap();
3679 client_services.poll();
3680
3681 let received_responses = client_services
3682 .app
3683 .received_holding_register_responses
3684 .borrow();
3685 assert_eq!(received_responses.len(), 1);
3686 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3687 assert_eq!(*rcv_txn_id, txn_id);
3688 assert_eq!(*rcv_unit_id, unit_id);
3689 assert_eq!(rcv_registers.from_address(), address);
3690 assert_eq!(rcv_registers.quantity(), 1);
3691 assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
3692 assert_eq!(*rcv_quantity, 1);
3693 }
3694
3695 #[test]
3697 fn test_read_single_input_register_sends_valid_adu() {
3698 let transport = MockTransport::default();
3699 let app = MockApp::default();
3700 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3701 let mut client_services =
3702 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3703
3704 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3705 client_services
3706 .read_single_input_register(10, unit_id, 100)
3707 .unwrap();
3708
3709 let sent_frames = client_services.transport.sent_frames.borrow();
3710 assert_eq!(sent_frames.len(), 1);
3711 let sent_adu = sent_frames.front().unwrap();
3712
3713 #[rustfmt::skip]
3714 let expected_adu: [u8; 12] = [
3715 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x64, 0x00, 0x01, ];
3723 assert_eq!(sent_adu.as_slice(), &expected_adu);
3724
3725 assert_eq!(client_services.expected_responses.len(), 1);
3727 let single_read = client_services.expected_responses[0]
3728 .operation_meta
3729 .is_single();
3730 assert!(single_read);
3731 }
3732
3733 #[test]
3735 fn test_client_services_read_single_input_register_e2e_success() {
3736 let transport = MockTransport::default();
3737 let app = MockApp::default();
3738 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3739 let mut client_services =
3740 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3741
3742 let txn_id = 10;
3743 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3744 let address = 100;
3745
3746 client_services
3747 .read_single_input_register(txn_id, unit_id, address)
3748 .unwrap();
3749
3750 let response_adu = [
3753 0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x04, 0x02, 0x12, 0x34,
3754 ];
3755 client_services
3756 .transport
3757 .recv_frames
3758 .borrow_mut()
3759 .push_back(Vec::from_slice(&response_adu).unwrap())
3760 .unwrap();
3761 client_services.poll();
3762
3763 let received_responses = client_services
3764 .app
3765 .received_input_register_responses
3766 .borrow();
3767 assert_eq!(received_responses.len(), 1);
3768 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3769 assert_eq!(*rcv_txn_id, txn_id);
3770 assert_eq!(*rcv_unit_id, unit_id);
3771 assert_eq!(rcv_registers.from_address(), address);
3772 assert_eq!(rcv_registers.quantity(), 1);
3773 assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
3774 assert_eq!(*rcv_quantity, 1);
3775 }
3776
3777 #[test]
3779 fn test_read_write_multiple_registers_sends_valid_adu() {
3780 let transport = MockTransport::default();
3781 let app = MockApp::default();
3782 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3783 let mut client_services =
3784 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3785
3786 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3787 let write_values = [0xAAAA, 0xBBBB];
3788 client_services
3789 .read_write_multiple_registers(11, unit_id, 10, 2, 20, &write_values)
3790 .unwrap();
3791
3792 let sent_frames = client_services.transport.sent_frames.borrow();
3793 assert_eq!(sent_frames.len(), 1);
3794 let sent_adu = sent_frames.front().unwrap();
3795
3796 #[rustfmt::skip]
3797 let expected_adu: [u8; 21] = [
3798 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x17, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x14, 0x00, 0x02, 0x04, 0xAA, 0xAA, 0xBB, 0xBB, ];
3811 assert_eq!(sent_adu.as_slice(), &expected_adu);
3812 }
3813
3814 #[test]
3816 fn test_mask_write_register_sends_valid_adu() {
3817 let transport = MockTransport::default();
3818 let app = MockApp::default();
3819 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3820 let mut client_services =
3821 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3822
3823 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3824 client_services
3825 .mask_write_register(12, unit_id, 30, 0xF0F0, 0x0F0F)
3826 .unwrap();
3827
3828 let sent_frames = client_services.transport.sent_frames.borrow();
3829 assert_eq!(sent_frames.len(), 1);
3830 let sent_adu = sent_frames.front().unwrap();
3831
3832 #[rustfmt::skip]
3833 let expected_adu: [u8; 14] = [
3834 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F, ];
3843 assert_eq!(sent_adu.as_slice(), &expected_adu);
3844 }
3845
3846 #[test]
3848 fn test_client_services_read_write_multiple_registers_e2e_success() {
3849 let transport = MockTransport::default();
3850 let app = MockApp::default();
3851 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3852 let mut client_services =
3853 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3854
3855 let txn_id = 11;
3856 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3857 let read_address = 10;
3858 let read_quantity = 2;
3859 let write_address = 20;
3860 let write_values = [0xAAAA, 0xBBBB];
3861
3862 client_services
3863 .read_write_multiple_registers(
3864 txn_id,
3865 unit_id,
3866 read_address,
3867 read_quantity,
3868 write_address,
3869 &write_values,
3870 )
3871 .unwrap();
3872
3873 let response_adu = [
3875 0x00, 0x0B, 0x00, 0x00, 0x00, 0x07, 0x01, 0x17, 0x04, 0x12, 0x34, 0x56, 0x78,
3876 ];
3877 client_services
3878 .transport
3879 .recv_frames
3880 .borrow_mut()
3881 .push_back(Vec::from_slice(&response_adu).unwrap())
3882 .unwrap();
3883 client_services.poll();
3884
3885 let received_responses = client_services
3886 .app
3887 .received_read_write_multiple_registers_responses
3888 .borrow();
3889 assert_eq!(received_responses.len(), 1);
3890 let (rcv_txn_id, rcv_unit_id, rcv_registers) = &received_responses[0];
3891 assert_eq!(*rcv_txn_id, txn_id);
3892 assert_eq!(*rcv_unit_id, unit_id);
3893 assert_eq!(rcv_registers.from_address(), read_address);
3894 assert_eq!(rcv_registers.quantity(), read_quantity);
3895 assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3896 }
3897
3898 #[test]
3900 fn test_client_services_mask_write_register_e2e_success() {
3901 let transport = MockTransport::default();
3902 let app = MockApp::default();
3903 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3904 let mut client_services =
3905 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3906
3907 let txn_id = 12;
3908 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3909 let address = 30;
3910 let and_mask = 0xF0F0;
3911 let or_mask = 0x0F0F;
3912
3913 client_services
3914 .mask_write_register(txn_id, unit_id, address, and_mask, or_mask)
3915 .unwrap();
3916
3917 let response_adu = [
3919 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F,
3920 ];
3921 client_services
3922 .transport
3923 .recv_frames
3924 .borrow_mut()
3925 .push_back(Vec::from_slice(&response_adu).unwrap())
3926 .unwrap();
3927 client_services.poll();
3928
3929 let received_responses = client_services
3930 .app
3931 .received_mask_write_register_responses
3932 .borrow();
3933 assert_eq!(received_responses.len(), 1);
3934 let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
3935 assert_eq!(*rcv_txn_id, txn_id);
3936 assert_eq!(*rcv_unit_id, unit_id);
3937 }
3938
3939 #[test]
3941 fn test_client_services_read_fifo_queue_e2e_success() {
3942 let transport = MockTransport::default();
3943 let app = MockApp::default();
3944 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3945 let mut client_services =
3946 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3947
3948 let txn_id = 13;
3949 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3950 let address = 40;
3951
3952 client_services
3953 .read_fifo_queue(txn_id, unit_id, address)
3954 .unwrap();
3955
3956 #[rustfmt::skip]
3958 let response_adu = [
3959 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x18, 0x00, 0x06, 0x00, 0x02, 0xAA, 0xAA, 0xBB, 0xBB, ];
3969 client_services
3970 .transport
3971 .recv_frames
3972 .borrow_mut()
3973 .push_back(Vec::from_slice(&response_adu).unwrap())
3974 .unwrap();
3975 client_services.poll();
3976
3977 let received_responses = client_services
3978 .app
3979 .received_read_fifo_queue_responses
3980 .borrow();
3981 assert_eq!(received_responses.len(), 1);
3982 let (rcv_txn_id, rcv_unit_id, rcv_fifo_queue) = &received_responses[0];
3983 assert_eq!(*rcv_txn_id, txn_id);
3984 assert_eq!(*rcv_unit_id, unit_id);
3985 assert_eq!(rcv_fifo_queue.length(), 2);
3986 assert_eq!(&rcv_fifo_queue.queue()[..2], &[0xAAAA, 0xBBBB]);
3987 }
3988
3989 #[test]
3991 fn test_client_services_read_file_record_e2e_success() {
3992 let transport = MockTransport::default();
3993 let app = MockApp::default();
3994 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3995 let mut client_services =
3996 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3997
3998 let txn_id = 14;
3999 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4000 let mut sub_req = SubRequest::new();
4001 sub_req.add_read_sub_request(4, 1, 2).unwrap();
4002
4003 client_services
4004 .read_file_record(txn_id, unit_id, &sub_req)
4005 .unwrap();
4006
4007 let response_adu = [
4013 0x00, 0x0E, 0x00, 0x00, 0x00, 0x09, 0x01, 0x14, 0x06, 0x05, 0x06, 0x12, 0x34, 0x56,
4014 0x78,
4015 ];
4016
4017 client_services
4018 .transport
4019 .recv_frames
4020 .borrow_mut()
4021 .push_back(Vec::from_slice(&response_adu).unwrap())
4022 .unwrap();
4023 client_services.poll();
4024
4025 let received_responses = client_services
4026 .app
4027 .received_read_file_record_responses
4028 .borrow();
4029 assert_eq!(received_responses.len(), 1);
4030 let (rcv_txn_id, rcv_unit_id, rcv_data) = &received_responses[0];
4031 assert_eq!(*rcv_txn_id, txn_id);
4032 assert_eq!(*rcv_unit_id, unit_id);
4033 assert_eq!(rcv_data.len(), 1);
4034 assert_eq!(
4035 rcv_data[0].record_data.as_ref().unwrap().as_slice(),
4036 &[0x1234, 0x5678]
4037 );
4038 }
4039
4040 #[test]
4042 fn test_client_services_write_file_record_e2e_success() {
4043 let transport = MockTransport::default();
4044 let app = MockApp::default();
4045 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4046 let mut client_services =
4047 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4048
4049 let txn_id = 15;
4050 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4051 let mut sub_req = SubRequest::new();
4052 let mut data = Vec::new();
4053 data.push(0x1122).unwrap();
4054 sub_req.add_write_sub_request(4, 1, 1, data).unwrap();
4055
4056 client_services
4057 .write_file_record(txn_id, unit_id, &sub_req)
4058 .unwrap();
4059
4060 let response_adu = [
4063 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0C, 0x01, 0x15, 0x09, 0x06, 0x00, 0x04, 0x00, 0x01,
4064 0x00, 0x01, 0x11, 0x22,
4065 ];
4066
4067 client_services
4068 .transport
4069 .recv_frames
4070 .borrow_mut()
4071 .push_back(Vec::from_slice(&response_adu).unwrap())
4072 .unwrap();
4073 client_services.poll();
4074
4075 let received_responses = client_services
4076 .app
4077 .received_write_file_record_responses
4078 .borrow();
4079 assert_eq!(received_responses.len(), 1);
4080 let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
4081 assert_eq!(*rcv_txn_id, txn_id);
4082 assert_eq!(*rcv_unit_id, unit_id);
4083 }
4084
4085 #[test]
4087 fn test_client_services_read_discrete_inputs_e2e_success() {
4088 let transport = MockTransport::default();
4089 let app = MockApp::default();
4090 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4091 let mut client_services =
4092 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4093
4094 let txn_id = 16;
4095 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4096 let address = 50;
4097 let quantity = 8;
4098
4099 client_services
4100 .read_discrete_inputs(txn_id, unit_id, address, quantity)
4101 .unwrap();
4102
4103 let response_adu = [0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0xAA];
4105
4106 client_services
4107 .transport
4108 .recv_frames
4109 .borrow_mut()
4110 .push_back(Vec::from_slice(&response_adu).unwrap())
4111 .unwrap();
4112 client_services.poll();
4113
4114 let received_responses = client_services
4115 .app
4116 .received_discrete_input_responses
4117 .borrow();
4118 assert_eq!(received_responses.len(), 1);
4119 let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
4120 assert_eq!(*rcv_txn_id, txn_id);
4121 assert_eq!(*rcv_unit_id, unit_id);
4122 assert_eq!(rcv_inputs.from_address(), address);
4123 assert_eq!(rcv_inputs.quantity(), quantity);
4124 assert_eq!(rcv_inputs.values(), &[0xAA]);
4125 assert_eq!(*rcv_quantity, quantity);
4126 }
4127
4128 #[test]
4130 fn test_client_services_read_single_discrete_input_e2e_success() {
4131 let transport = MockTransport::default();
4132 let app = MockApp::default();
4133 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4134 let mut client_services =
4135 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4136
4137 let txn_id = 17;
4138 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4139 let address = 10;
4140
4141 client_services
4142 .read_single_discrete_input(txn_id, unit_id, address)
4143 .unwrap();
4144
4145 let sent_frames = client_services.transport.sent_frames.borrow();
4147 assert_eq!(sent_frames.len(), 1);
4148 let expected_request = [
4152 0x00, 0x11, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x0A, 0x00, 0x01,
4153 ];
4154 assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
4155 drop(sent_frames);
4156
4157 let response_adu = [0x00, 0x11, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0x01];
4159
4160 client_services
4161 .transport
4162 .recv_frames
4163 .borrow_mut()
4164 .push_back(Vec::from_slice(&response_adu).unwrap())
4165 .unwrap();
4166 client_services.poll();
4167
4168 let received_responses = client_services
4169 .app
4170 .received_discrete_input_responses
4171 .borrow();
4172 assert_eq!(received_responses.len(), 1);
4173 let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
4174 assert_eq!(*rcv_txn_id, txn_id);
4175 assert_eq!(*rcv_unit_id, unit_id);
4176 assert_eq!(rcv_inputs.from_address(), address);
4177 assert_eq!(rcv_inputs.quantity(), 1);
4178 assert_eq!(rcv_inputs.value(address).unwrap(), true);
4179 assert_eq!(*rcv_quantity, 1);
4180 }
4181
4182 #[test]
4184 fn test_client_services_read_device_identification_e2e_success() {
4185 let transport = MockTransport::default();
4186 let app = MockApp::default();
4187 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4188 let mut client_services =
4189 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4190
4191 let txn_id = 20;
4192 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4193 let read_code = ReadDeviceIdCode::Basic;
4194 let object_id = ObjectId::from(0x00);
4195
4196 client_services
4197 .read_device_identification(txn_id, unit_id, read_code, object_id)
4198 .unwrap();
4199
4200 let sent_frames = client_services.transport.sent_frames.borrow();
4202 assert_eq!(sent_frames.len(), 1);
4203 let expected_request = [
4207 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2B, 0x0E, 0x01, 0x00,
4208 ];
4209 assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
4210 drop(sent_frames);
4211
4212 let response_adu = [
4217 0x00, 0x14, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x01,
4218 0x00, 0x03, 0x46, 0x6F, 0x6F,
4219 ];
4220
4221 client_services
4222 .transport
4223 .recv_frames
4224 .borrow_mut()
4225 .push_back(Vec::from_slice(&response_adu).unwrap())
4226 .unwrap();
4227 client_services.poll();
4228
4229 let received_responses = client_services
4230 .app
4231 .received_read_device_id_responses
4232 .borrow();
4233 assert_eq!(received_responses.len(), 1);
4234 let (rcv_txn_id, rcv_unit_id, rcv_resp) = &received_responses[0];
4235 assert_eq!(*rcv_txn_id, txn_id);
4236 assert_eq!(*rcv_unit_id, unit_id);
4237 assert_eq!(rcv_resp.read_device_id_code, ReadDeviceIdCode::Basic);
4238 assert_eq!(
4239 rcv_resp.conformity_level,
4240 ConformityLevel::BasicStreamAndIndividual
4241 );
4242 assert_eq!(rcv_resp.number_of_objects, 1);
4243
4244 assert_eq!(&rcv_resp.objects_data[..5], &[0x00, 0x03, 0x46, 0x6F, 0x6F]);
4246 }
4247
4248 #[test]
4250 fn test_client_services_read_device_identification_multi_transaction() {
4251 let transport = MockTransport::default();
4252 let app = MockApp::default();
4253 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4254 let mut client_services =
4255 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4256
4257 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4258 let txn_id_1 = 21;
4260 client_services
4261 .read_device_identification(
4262 txn_id_1,
4263 unit_id,
4264 ReadDeviceIdCode::Basic,
4265 ObjectId::from(0x00),
4266 )
4267 .unwrap();
4268
4269 let txn_id_2 = 22;
4271 client_services
4272 .read_device_identification(
4273 txn_id_2,
4274 unit_id,
4275 ReadDeviceIdCode::Regular,
4276 ObjectId::from(0x00),
4277 )
4278 .unwrap();
4279
4280 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
4281
4282 let response_adu_2 = [
4286 0x00, 0x16, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x82, 0x00, 0x00, 0x00,
4287 ];
4288 client_services
4289 .transport
4290 .recv_frames
4291 .borrow_mut()
4292 .push_back(Vec::from_slice(&response_adu_2).unwrap())
4293 .unwrap();
4294
4295 client_services.poll();
4296
4297 {
4298 let received_responses = client_services
4299 .app
4300 .received_read_device_id_responses
4301 .borrow();
4302 assert_eq!(received_responses.len(), 1);
4303 assert_eq!(received_responses[0].0, txn_id_2);
4304 assert_eq!(
4305 received_responses[0].2.read_device_id_code,
4306 ReadDeviceIdCode::Regular
4307 );
4308 }
4309
4310 let response_adu_1 = [
4313 0x00, 0x15, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x00,
4314 ];
4315 client_services
4316 .transport
4317 .recv_frames
4318 .borrow_mut()
4319 .push_back(Vec::from_slice(&response_adu_1).unwrap())
4320 .unwrap();
4321
4322 client_services.poll();
4323
4324 {
4325 let received_responses = client_services
4326 .app
4327 .received_read_device_id_responses
4328 .borrow();
4329 assert_eq!(received_responses.len(), 2);
4330 assert_eq!(received_responses[1].0, txn_id_1);
4331 assert_eq!(
4332 received_responses[1].2.read_device_id_code,
4333 ReadDeviceIdCode::Basic
4334 );
4335 }
4336 }
4337
4338 #[test]
4340 fn test_client_services_read_device_identification_mismatch_code() {
4341 let transport = MockTransport::default();
4342 let app = MockApp::default();
4343 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4344 let mut client_services =
4345 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4346
4347 let txn_id = 30;
4348 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4349 client_services
4351 .read_device_identification(
4352 txn_id,
4353 unit_id,
4354 ReadDeviceIdCode::Basic,
4355 ObjectId::from(0x00),
4356 )
4357 .unwrap();
4358
4359 let response_adu = [
4362 0x00, 0x1E, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x81, 0x00, 0x00, 0x00,
4363 ];
4364
4365 client_services
4366 .transport
4367 .recv_frames
4368 .borrow_mut()
4369 .push_back(Vec::from_slice(&response_adu).unwrap())
4370 .unwrap();
4371
4372 client_services.poll();
4373
4374 assert!(
4376 client_services
4377 .app
4378 .received_read_device_id_responses
4379 .borrow()
4380 .is_empty()
4381 );
4382
4383 let failed = client_services.app().failed_requests.borrow();
4385 assert_eq!(failed.len(), 1);
4386 assert_eq!(failed[0].2, MbusError::InvalidDeviceIdentification);
4387 }
4388
4389 #[test]
4391 fn test_client_services_read_exception_status_e2e_success() {
4392 let transport = MockTransport::default();
4393 let app = MockApp::default();
4394 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4395 let mut client_services =
4396 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4397
4398 let txn_id = 40;
4399 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4400
4401 let err = client_services.read_exception_status(txn_id, unit_id).err();
4402 assert_eq!(err, Some(MbusError::InvalidTransport));
4404 }
4405
4406 #[test]
4408 fn test_client_services_diagnostics_query_data_success() {
4409 let transport = MockTransport::default();
4410 let app = MockApp::default();
4411 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4412 let mut client_services =
4413 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4414
4415 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4416 let data = [0x1234, 0x5678];
4417 let sub_function = DiagnosticSubFunction::ReturnQueryData;
4418 let err = client_services
4419 .diagnostics(50, unit_id, sub_function, &data)
4420 .err();
4421 assert_eq!(err, Some(MbusError::InvalidTransport));
4422 }
4423
4424 #[test]
4426 fn test_client_services_get_comm_event_counter_success() {
4427 let transport = MockTransport::default();
4428 let app = MockApp::default();
4429 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4430 let mut client_services =
4431 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4432 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4433 let err = client_services.get_comm_event_counter(60, unit_id).err();
4434
4435 assert_eq!(err, Some(MbusError::InvalidTransport));
4436 }
4437
4438 #[test]
4440 fn test_client_services_report_server_id_success() {
4441 let transport = MockTransport::default();
4442 let app = MockApp::default();
4443 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4444 let mut client_services =
4445 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4446
4447 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4448 let err = client_services.report_server_id(70, unit_id).err();
4449
4450 assert_eq!(err, Some(MbusError::InvalidTransport));
4451 }
4452
4453 #[test]
4457 fn test_broadcast_read_multiple_coils_not_allowed() {
4458 let transport = MockTransport::default();
4459 let app = MockApp::default();
4460 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4461 let mut client_services =
4462 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4463
4464 let txn_id = 0x0001;
4465 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4466 let address = 0x0000;
4467 let quantity = 8;
4468 let res = client_services.read_multiple_coils(txn_id, unit_id, address, quantity);
4469 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4470 }
4471
4472 #[test]
4474 fn test_broadcast_write_single_coil_tcp_not_allowed() {
4475 let transport = MockTransport::default();
4476 let app = MockApp::default();
4477 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4478 let mut client_services =
4479 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4480
4481 let txn_id = 0x0002;
4482 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4483 let res = client_services.write_single_coil(txn_id, unit_id, 0x0000, true);
4484 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4485 }
4486
4487 #[test]
4489 fn test_broadcast_write_multiple_coils_tcp_not_allowed() {
4490 let transport = MockTransport::default();
4491 let app = MockApp::default();
4492 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4493 let mut client_services =
4494 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4495
4496 let txn_id = 0x0003;
4497 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4498 let mut values = Coils::new(0x0000, 2).unwrap();
4499 values.set_value(0x0000, true).unwrap();
4500 values.set_value(0x0001, false).unwrap();
4501
4502 let res = client_services.write_multiple_coils(txn_id, unit_id, 0x0000, &values);
4503 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4504 }
4505
4506 #[test]
4508 fn test_broadcast_read_discrete_inputs_not_allowed() {
4509 let transport = MockTransport::default();
4510 let app = MockApp::default();
4511 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4512 let mut client_services =
4513 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4514
4515 let txn_id = 0x0006;
4516 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4517 let res = client_services.read_discrete_inputs(txn_id, unit_id, 0x0000, 2);
4518 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4519 }
4520
4521 #[test]
4524 fn test_client_services_clears_buffer_on_overflow() {
4525 let transport = MockTransport::default();
4526 let app = MockApp::default();
4527 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4528 let mut client_services =
4529 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4530
4531 let initial_garbage = [0xFF; MAX_ADU_FRAME_LEN - 10];
4533 client_services
4534 .rxed_frame
4535 .extend_from_slice(&initial_garbage)
4536 .unwrap();
4537
4538 let chunk = [0xAA; 20];
4540 client_services
4541 .transport
4542 .recv_frames
4543 .borrow_mut()
4544 .push_back(Vec::from_slice(&chunk).unwrap())
4545 .unwrap();
4546
4547 client_services.poll();
4549
4550 assert!(
4551 client_services.rxed_frame.is_empty(),
4552 "Buffer should be cleared on overflow to prevent crashing and recover from stream noise."
4553 );
4554 }
4555}