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 = "traffic")]
34use crate::app::TrafficNotifier;
35#[cfg(feature = "diagnostics")]
36use diagnostic::ReadDeviceIdCode;
37use heapless::Vec;
38use mbus_core::data_unit::common::{ModbusMessage, SlaveAddress, derive_length_from_bytes};
39use mbus_core::function_codes::public::EncapsulatedInterfaceType;
40use mbus_core::transport::{UidSaddrFrom, UnitIdOrSlaveAddr};
41use mbus_core::{
42 data_unit::common::{self, MAX_ADU_FRAME_LEN},
43 errors::MbusError,
44 transport::{
45 BackoffStrategy, JitterStrategy, ModbusConfig, ModbusSerialConfig, TimeKeeper, Transport,
46 TransportType,
47 },
48};
49
50#[cfg(feature = "logging")]
51macro_rules! client_log_debug {
52 ($($arg:tt)*) => {
53 log::debug!($($arg)*)
54 };
55}
56
57#[cfg(not(feature = "logging"))]
58macro_rules! client_log_debug {
59 ($($arg:tt)*) => {{
60 let _ = core::format_args!($($arg)*);
61 }};
62}
63
64#[cfg(feature = "logging")]
65macro_rules! client_log_trace {
66 ($($arg:tt)*) => {
67 log::trace!($($arg)*)
68 };
69}
70
71#[cfg(not(feature = "logging"))]
72macro_rules! client_log_trace {
73 ($($arg:tt)*) => {{
74 let _ = core::format_args!($($arg)*);
75 }};
76}
77
78type ResponseHandler<T, A, const N: usize> =
79 fn(&mut ClientServices<T, A, N>, &ExpectedResponse<T, A, N>, &ModbusMessage);
80
81#[doc(hidden)]
83pub trait SerialQueueSizeOne {}
84impl SerialQueueSizeOne for [(); 1] {}
85
86pub type SerialClientServices<TRANSPORT, APP> = ClientServices<TRANSPORT, APP, 1>;
88
89#[cfg(feature = "coils")]
94pub struct CoilsApi<'a, TRANSPORT, APP, const N: usize> {
95 client: &'a mut ClientServices<TRANSPORT, APP, N>,
96}
97
98#[cfg(feature = "coils")]
99impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
100where
101 TRANSPORT: Transport,
102 APP: ClientCommon + crate::app::CoilResponse,
103{
104 pub fn coils(&mut self) -> CoilsApi<'_, TRANSPORT, APP, N> {
106 CoilsApi { client: self }
107 }
108
109 pub fn with_coils<R>(
111 &mut self,
112 f: impl FnOnce(&mut CoilsApi<'_, TRANSPORT, APP, N>) -> R,
113 ) -> R {
114 let mut api = self.coils();
115 f(&mut api)
116 }
117}
118
119#[cfg(feature = "coils")]
120impl<TRANSPORT, APP, const N: usize> CoilsApi<'_, TRANSPORT, APP, N>
121where
122 TRANSPORT: Transport,
123 APP: ClientCommon + crate::app::CoilResponse,
124{
125 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
127 pub fn read_multiple_coils(
128 &mut self,
129 txn_id: u16,
130 unit_id_slave_addr: UnitIdOrSlaveAddr,
131 address: u16,
132 quantity: u16,
133 ) -> Result<(), MbusError> {
134 self.client
135 .read_multiple_coils(txn_id, unit_id_slave_addr, address, quantity)
136 }
137
138 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
140 pub fn read_single_coil(
141 &mut self,
142 txn_id: u16,
143 unit_id_slave_addr: UnitIdOrSlaveAddr,
144 address: u16,
145 ) -> Result<(), MbusError> {
146 self.client
147 .read_single_coil(txn_id, unit_id_slave_addr, address)
148 }
149
150 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
152 pub fn write_single_coil(
153 &mut self,
154 txn_id: u16,
155 unit_id_slave_addr: UnitIdOrSlaveAddr,
156 address: u16,
157 value: bool,
158 ) -> Result<(), MbusError> {
159 self.client
160 .write_single_coil(txn_id, unit_id_slave_addr, address, value)
161 }
162
163 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
165 pub fn write_multiple_coils(
166 &mut self,
167 txn_id: u16,
168 unit_id_slave_addr: UnitIdOrSlaveAddr,
169 address: u16,
170 values: &crate::services::coil::Coils,
171 ) -> Result<(), MbusError> {
172 self.client
173 .write_multiple_coils(txn_id, unit_id_slave_addr, address, values)
174 }
175}
176
177#[cfg(feature = "discrete-inputs")]
179pub struct DiscreteInputsApi<'a, TRANSPORT, APP, const N: usize> {
180 client: &'a mut ClientServices<TRANSPORT, APP, N>,
181}
182
183#[cfg(feature = "discrete-inputs")]
184impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
185where
186 TRANSPORT: Transport,
187 APP: ClientCommon + crate::app::DiscreteInputResponse,
188{
189 pub fn discrete_inputs(&mut self) -> DiscreteInputsApi<'_, TRANSPORT, APP, N> {
191 DiscreteInputsApi { client: self }
192 }
193
194 pub fn with_discrete_inputs<R>(
196 &mut self,
197 f: impl FnOnce(&mut DiscreteInputsApi<'_, TRANSPORT, APP, N>) -> R,
198 ) -> R {
199 let mut api = self.discrete_inputs();
200 f(&mut api)
201 }
202}
203
204#[cfg(feature = "discrete-inputs")]
205impl<TRANSPORT, APP, const N: usize> DiscreteInputsApi<'_, TRANSPORT, APP, N>
206where
207 TRANSPORT: Transport,
208 APP: ClientCommon + crate::app::DiscreteInputResponse,
209{
210 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
212 pub fn read_discrete_inputs(
213 &mut self,
214 txn_id: u16,
215 unit_id_slave_addr: UnitIdOrSlaveAddr,
216 address: u16,
217 quantity: u16,
218 ) -> Result<(), MbusError> {
219 self.client
220 .read_discrete_inputs(txn_id, unit_id_slave_addr, address, quantity)
221 }
222
223 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
225 pub fn read_single_discrete_input(
226 &mut self,
227 txn_id: u16,
228 unit_id_slave_addr: UnitIdOrSlaveAddr,
229 address: u16,
230 ) -> Result<(), MbusError> {
231 self.client
232 .read_single_discrete_input(txn_id, unit_id_slave_addr, address)
233 }
234}
235
236#[cfg(feature = "registers")]
238pub struct RegistersApi<'a, TRANSPORT, APP, const N: usize> {
239 client: &'a mut ClientServices<TRANSPORT, APP, N>,
240}
241
242#[cfg(feature = "registers")]
243impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
244where
245 TRANSPORT: Transport,
246 APP: ClientCommon + crate::app::RegisterResponse,
247{
248 pub fn registers(&mut self) -> RegistersApi<'_, TRANSPORT, APP, N> {
250 RegistersApi { client: self }
251 }
252
253 pub fn with_registers<R>(
255 &mut self,
256 f: impl FnOnce(&mut RegistersApi<'_, TRANSPORT, APP, N>) -> R,
257 ) -> R {
258 let mut api = self.registers();
259 f(&mut api)
260 }
261}
262
263#[cfg(feature = "registers")]
264impl<TRANSPORT, APP, const N: usize> RegistersApi<'_, TRANSPORT, APP, N>
265where
266 TRANSPORT: Transport,
267 APP: ClientCommon + crate::app::RegisterResponse,
268{
269 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
271 pub fn read_holding_registers(
272 &mut self,
273 txn_id: u16,
274 unit_id_slave_addr: UnitIdOrSlaveAddr,
275 from_address: u16,
276 quantity: u16,
277 ) -> Result<(), MbusError> {
278 self.client
279 .read_holding_registers(txn_id, unit_id_slave_addr, from_address, quantity)
280 }
281
282 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
284 pub fn read_single_holding_register(
285 &mut self,
286 txn_id: u16,
287 unit_id_slave_addr: UnitIdOrSlaveAddr,
288 address: u16,
289 ) -> Result<(), MbusError> {
290 self.client
291 .read_single_holding_register(txn_id, unit_id_slave_addr, address)
292 }
293
294 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
296 pub fn read_input_registers(
297 &mut self,
298 txn_id: u16,
299 unit_id_slave_addr: UnitIdOrSlaveAddr,
300 address: u16,
301 quantity: u16,
302 ) -> Result<(), MbusError> {
303 self.client
304 .read_input_registers(txn_id, unit_id_slave_addr, address, quantity)
305 }
306
307 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
309 pub fn read_single_input_register(
310 &mut self,
311 txn_id: u16,
312 unit_id_slave_addr: UnitIdOrSlaveAddr,
313 address: u16,
314 ) -> Result<(), MbusError> {
315 self.client
316 .read_single_input_register(txn_id, unit_id_slave_addr, address)
317 }
318
319 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
321 pub fn write_single_register(
322 &mut self,
323 txn_id: u16,
324 unit_id_slave_addr: UnitIdOrSlaveAddr,
325 address: u16,
326 value: u16,
327 ) -> Result<(), MbusError> {
328 self.client
329 .write_single_register(txn_id, unit_id_slave_addr, address, value)
330 }
331
332 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
334 pub fn write_multiple_registers(
335 &mut self,
336 txn_id: u16,
337 unit_id_slave_addr: UnitIdOrSlaveAddr,
338 address: u16,
339 quantity: u16,
340 values: &[u16],
341 ) -> Result<(), MbusError> {
342 self.client
343 .write_multiple_registers(txn_id, unit_id_slave_addr, address, quantity, values)
344 }
345
346 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
348 pub fn read_write_multiple_registers(
349 &mut self,
350 txn_id: u16,
351 unit_id_slave_addr: UnitIdOrSlaveAddr,
352 read_address: u16,
353 read_quantity: u16,
354 write_address: u16,
355 write_values: &[u16],
356 ) -> Result<(), MbusError> {
357 self.client.read_write_multiple_registers(
358 txn_id,
359 unit_id_slave_addr,
360 read_address,
361 read_quantity,
362 write_address,
363 write_values,
364 )
365 }
366
367 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
369 pub fn mask_write_register(
370 &mut self,
371 txn_id: u16,
372 unit_id_slave_addr: UnitIdOrSlaveAddr,
373 address: u16,
374 and_mask: u16,
375 or_mask: u16,
376 ) -> Result<(), MbusError> {
377 self.client
378 .mask_write_register(txn_id, unit_id_slave_addr, address, and_mask, or_mask)
379 }
380}
381
382#[cfg(feature = "diagnostics")]
384pub struct DiagnosticApi<'a, TRANSPORT, APP, const N: usize> {
385 client: &'a mut ClientServices<TRANSPORT, APP, N>,
386}
387
388#[cfg(feature = "diagnostics")]
389impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
390where
391 TRANSPORT: Transport,
392 APP: ClientCommon + crate::app::DiagnosticsResponse,
393{
394 pub fn diagnostic(&mut self) -> DiagnosticApi<'_, TRANSPORT, APP, N> {
396 DiagnosticApi { client: self }
397 }
398
399 pub fn with_diagnostic<R>(
401 &mut self,
402 f: impl FnOnce(&mut DiagnosticApi<'_, TRANSPORT, APP, N>) -> R,
403 ) -> R {
404 let mut api = self.diagnostic();
405 f(&mut api)
406 }
407}
408
409#[cfg(feature = "diagnostics")]
410impl<TRANSPORT, APP, const N: usize> DiagnosticApi<'_, TRANSPORT, APP, N>
411where
412 TRANSPORT: Transport,
413 APP: ClientCommon + crate::app::DiagnosticsResponse,
414{
415 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
417 pub fn read_device_identification(
418 &mut self,
419 txn_id: u16,
420 unit_id_slave_addr: UnitIdOrSlaveAddr,
421 read_device_id_code: crate::services::diagnostic::ReadDeviceIdCode,
422 object_id: crate::services::diagnostic::ObjectId,
423 ) -> Result<(), MbusError> {
424 self.client.read_device_identification(
425 txn_id,
426 unit_id_slave_addr,
427 read_device_id_code,
428 object_id,
429 )
430 }
431
432 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
434 pub fn encapsulated_interface_transport(
435 &mut self,
436 txn_id: u16,
437 unit_id_slave_addr: UnitIdOrSlaveAddr,
438 mei_type: EncapsulatedInterfaceType,
439 data: &[u8],
440 ) -> Result<(), MbusError> {
441 self.client
442 .encapsulated_interface_transport(txn_id, unit_id_slave_addr, mei_type, data)
443 }
444
445 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
447 pub fn read_exception_status(
448 &mut self,
449 txn_id: u16,
450 unit_id_slave_addr: UnitIdOrSlaveAddr,
451 ) -> Result<(), MbusError> {
452 self.client
453 .read_exception_status(txn_id, unit_id_slave_addr)
454 }
455
456 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
458 pub fn diagnostics(
459 &mut self,
460 txn_id: u16,
461 unit_id_slave_addr: UnitIdOrSlaveAddr,
462 sub_function: mbus_core::function_codes::public::DiagnosticSubFunction,
463 data: &[u16],
464 ) -> Result<(), MbusError> {
465 self.client
466 .diagnostics(txn_id, unit_id_slave_addr, sub_function, data)
467 }
468
469 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
471 pub fn get_comm_event_counter(
472 &mut self,
473 txn_id: u16,
474 unit_id_slave_addr: UnitIdOrSlaveAddr,
475 ) -> Result<(), MbusError> {
476 self.client
477 .get_comm_event_counter(txn_id, unit_id_slave_addr)
478 }
479
480 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
482 pub fn get_comm_event_log(
483 &mut self,
484 txn_id: u16,
485 unit_id_slave_addr: UnitIdOrSlaveAddr,
486 ) -> Result<(), MbusError> {
487 self.client.get_comm_event_log(txn_id, unit_id_slave_addr)
488 }
489
490 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
492 pub fn report_server_id(
493 &mut self,
494 txn_id: u16,
495 unit_id_slave_addr: UnitIdOrSlaveAddr,
496 ) -> Result<(), MbusError> {
497 self.client.report_server_id(txn_id, unit_id_slave_addr)
498 }
499}
500
501#[cfg(feature = "fifo")]
503pub struct FifoApi<'a, TRANSPORT, APP, const N: usize> {
504 client: &'a mut ClientServices<TRANSPORT, APP, N>,
505}
506
507#[cfg(feature = "fifo")]
508impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
509where
510 TRANSPORT: Transport,
511 APP: ClientCommon + crate::app::FifoQueueResponse,
512{
513 pub fn fifo(&mut self) -> FifoApi<'_, TRANSPORT, APP, N> {
515 FifoApi { client: self }
516 }
517
518 pub fn with_fifo<R>(&mut self, f: impl FnOnce(&mut FifoApi<'_, TRANSPORT, APP, N>) -> R) -> R {
520 let mut api = self.fifo();
521 f(&mut api)
522 }
523}
524
525#[cfg(feature = "fifo")]
526impl<TRANSPORT, APP, const N: usize> FifoApi<'_, TRANSPORT, APP, N>
527where
528 TRANSPORT: Transport,
529 APP: ClientCommon + crate::app::FifoQueueResponse,
530{
531 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
533 pub fn read_fifo_queue(
534 &mut self,
535 txn_id: u16,
536 unit_id_slave_addr: UnitIdOrSlaveAddr,
537 address: u16,
538 ) -> Result<(), MbusError> {
539 self.client
540 .read_fifo_queue(txn_id, unit_id_slave_addr, address)
541 }
542}
543
544#[cfg(feature = "file-record")]
546pub struct FileRecordsApi<'a, TRANSPORT, APP, const N: usize> {
547 client: &'a mut ClientServices<TRANSPORT, APP, N>,
548}
549
550#[cfg(feature = "file-record")]
551impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
552where
553 TRANSPORT: Transport,
554 APP: ClientCommon + crate::app::FileRecordResponse,
555{
556 pub fn file_records(&mut self) -> FileRecordsApi<'_, TRANSPORT, APP, N> {
558 FileRecordsApi { client: self }
559 }
560
561 pub fn with_file_records<R>(
563 &mut self,
564 f: impl FnOnce(&mut FileRecordsApi<'_, TRANSPORT, APP, N>) -> R,
565 ) -> R {
566 let mut api = self.file_records();
567 f(&mut api)
568 }
569}
570
571#[cfg(feature = "file-record")]
572impl<TRANSPORT, APP, const N: usize> FileRecordsApi<'_, TRANSPORT, APP, N>
573where
574 TRANSPORT: Transport,
575 APP: ClientCommon + crate::app::FileRecordResponse,
576{
577 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
579 pub fn read_file_record(
580 &mut self,
581 txn_id: u16,
582 unit_id_slave_addr: UnitIdOrSlaveAddr,
583 sub_request: &crate::services::file_record::SubRequest,
584 ) -> Result<(), MbusError> {
585 self.client
586 .read_file_record(txn_id, unit_id_slave_addr, sub_request)
587 }
588
589 #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
591 pub fn write_file_record(
592 &mut self,
593 txn_id: u16,
594 unit_id_slave_addr: UnitIdOrSlaveAddr,
595 sub_request: &crate::services::file_record::SubRequest,
596 ) -> Result<(), MbusError> {
597 self.client
598 .write_file_record(txn_id, unit_id_slave_addr, sub_request)
599 }
600}
601
602#[derive(Debug, Clone, PartialEq, Eq)]
604pub(crate) struct Single {
605 address: u16,
606 value: u16,
607}
608#[derive(Debug, Clone, PartialEq, Eq)]
610pub(crate) struct Multiple {
611 address: u16,
612 quantity: u16,
613}
614#[derive(Debug, Clone, PartialEq, Eq)]
616pub(crate) struct Mask {
617 address: u16,
618 and_mask: u16,
619 or_mask: u16,
620}
621#[cfg(feature = "diagnostics")]
623#[derive(Debug, Clone, PartialEq, Eq)]
624pub(crate) struct Diag {
625 device_id_code: ReadDeviceIdCode,
626 encap_type: EncapsulatedInterfaceType,
627}
628
629#[derive(Debug, Clone, PartialEq, Eq)]
631pub(crate) enum OperationMeta {
632 Other,
633 Single(Single),
634 Multiple(Multiple),
635 Masking(Mask),
636 #[cfg(feature = "diagnostics")]
637 Diag(Diag),
638}
639
640impl OperationMeta {
641 fn address(&self) -> u16 {
642 match self {
643 OperationMeta::Single(s) => s.address,
644 OperationMeta::Multiple(m) => m.address,
645 OperationMeta::Masking(m) => m.address,
646 _ => 0,
647 }
648 }
649
650 fn value(&self) -> u16 {
651 match self {
652 OperationMeta::Single(s) => s.value,
653 _ => 0,
654 }
655 }
656
657 fn quantity(&self) -> u16 {
658 match self {
659 OperationMeta::Single(_) => 1,
660 OperationMeta::Multiple(m) => m.quantity,
661 _ => 0,
662 }
663 }
664
665 fn and_mask(&self) -> u16 {
666 match self {
667 OperationMeta::Masking(m) => m.and_mask,
668 _ => 0,
669 }
670 }
671
672 fn or_mask(&self) -> u16 {
673 match self {
674 OperationMeta::Masking(m) => m.or_mask,
675 _ => 0,
676 }
677 }
678
679 fn is_single(&self) -> bool {
680 matches!(self, OperationMeta::Single(_))
681 }
682
683 fn single_value(&self) -> u16 {
684 match self {
685 OperationMeta::Single(s) => s.value,
686 _ => 0,
687 }
688 }
689
690 fn device_id_code(&self) -> ReadDeviceIdCode {
691 match self {
692 #[cfg(feature = "diagnostics")]
693 OperationMeta::Diag(d) => d.device_id_code,
694 _ => ReadDeviceIdCode::default(),
695 }
696 }
697
698 fn encap_type(&self) -> EncapsulatedInterfaceType {
699 match self {
700 #[cfg(feature = "diagnostics")]
701 OperationMeta::Diag(d) => d.encap_type,
702 _ => EncapsulatedInterfaceType::default(),
703 }
704 }
705}
706
707#[derive(Debug)]
714pub(crate) struct ExpectedResponse<T, A, const N: usize> {
715 pub txn_id: u16,
717 pub unit_id_or_slave_addr: u8,
719
720 pub original_adu: Vec<u8, MAX_ADU_FRAME_LEN>,
723
724 pub sent_timestamp: u64,
726 pub retries_left: u8,
728 pub retry_attempt_index: u8,
730 pub next_retry_timestamp: Option<u64>,
735
736 pub handler: ResponseHandler<T, A, N>,
738
739 pub operation_meta: OperationMeta,
741}
742
743#[derive(Debug)]
755pub struct ClientServices<TRANSPORT, APP, const N: usize = 1> {
756 app: APP,
758 transport: TRANSPORT,
760
761 config: ModbusConfig,
763
764 rxed_frame: Vec<u8, MAX_ADU_FRAME_LEN>,
766
767 expected_responses: Vec<ExpectedResponse<TRANSPORT, APP, N>, N>,
769
770 next_timeout_check: Option<u64>,
772}
773
774#[cfg(feature = "traffic")]
783pub trait ClientCommon: RequestErrorNotifier + TimeKeeper + TrafficNotifier {}
784
785#[cfg(feature = "traffic")]
786impl<T> ClientCommon for T where T: RequestErrorNotifier + TimeKeeper + TrafficNotifier {}
787
788#[cfg(not(feature = "traffic"))]
790pub trait ClientCommon: RequestErrorNotifier + TimeKeeper {}
791
792#[cfg(not(feature = "traffic"))]
793impl<T> ClientCommon for T where T: RequestErrorNotifier + TimeKeeper {}
794
795impl<T, APP, const N: usize> ClientServices<T, APP, N>
796where
797 T: Transport,
798 APP: ClientCommon,
799{
800 fn dispatch_response(&mut self, message: &ModbusMessage, raw_frame: &[u8]) {
801 let wire_txn_id = message.transaction_id();
802 let unit_id_or_slave_addr = message.unit_id_or_slave_addr();
803
804 let index = if self.transport.transport_type().is_tcp_type() {
805 self.expected_responses.iter().position(|r| {
806 r.txn_id == wire_txn_id && r.unit_id_or_slave_addr == unit_id_or_slave_addr.into()
807 })
808 } else {
809 self.expected_responses
810 .iter()
811 .position(|r| r.unit_id_or_slave_addr == unit_id_or_slave_addr.into())
812 };
813
814 let expected = match index {
815 Some(i) => self.expected_responses.swap_remove(i),
818 None => {
819 client_log_debug!(
820 "dropping unmatched response: txn_id={}, unit_id_or_slave_addr={}",
821 wire_txn_id,
822 unit_id_or_slave_addr.get()
823 );
824 return;
825 }
826 };
827
828 let request_txn_id = expected.txn_id;
829
830 #[cfg(feature = "traffic")]
831 self.app
832 .on_rx_frame(request_txn_id, unit_id_or_slave_addr, raw_frame);
833
834 #[cfg(not(feature = "traffic"))]
835 let _ = raw_frame;
836
837 client_log_trace!(
838 "dispatching response: txn_id={}, unit_id_or_slave_addr={}, queue_len_after_pop={}",
839 request_txn_id,
840 unit_id_or_slave_addr.get(),
841 self.expected_responses.len()
842 );
843
844 if let Some(exception_code) = message.pdu().error_code() {
847 client_log_debug!(
848 "modbus exception response: txn_id={}, unit_id_or_slave_addr={}, code=0x{:02X}",
849 request_txn_id,
850 unit_id_or_slave_addr.get(),
851 exception_code
852 );
853 #[cfg(feature = "traffic")]
854 self.app.on_rx_error(
855 request_txn_id,
856 unit_id_or_slave_addr,
857 MbusError::ModbusException(exception_code),
858 raw_frame,
859 );
860 self.app.request_failed(
861 request_txn_id,
862 unit_id_or_slave_addr,
863 MbusError::ModbusException(exception_code),
864 );
865 return;
866 }
867
868 (expected.handler)(self, &expected, message);
869 }
870}
871
872#[derive(Debug, PartialEq, Eq)]
878enum LoopAction {
879 Advance,
881 Repeat,
884 NotHandled,
886}
887
888#[derive(Copy, Clone)]
889struct RetryPolicy {
890 backoff: BackoffStrategy,
891 jitter: JitterStrategy,
892 random_fn: Option<fn() -> u32>,
893}
894
895impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
896where
897 TRANSPORT: Transport,
898 TRANSPORT::Error: Into<MbusError>,
899 APP: ClientCommon,
900{
901 pub fn poll(&mut self) {
948 match self.transport.recv() {
950 Ok(frame) => {
951 self.append_to_rxed_frame(frame);
952
953 self.process_rxed_frame();
955 }
956 Err(err) => {
957 self.handle_recv_error(err);
958 }
959 }
960
961 self.handle_timeouts();
963 }
964
965 fn handle_recv_error(&mut self, err: <TRANSPORT as Transport>::Error) {
966 let recv_error: MbusError = err.into();
967 let is_connection_loss = matches!(
968 recv_error,
969 MbusError::ConnectionClosed
970 | MbusError::ConnectionFailed
971 | MbusError::ConnectionLost
972 | MbusError::IoError
973 ) || !self.transport.is_connected();
974
975 if is_connection_loss {
976 client_log_debug!(
977 "connection loss detected during poll: error={:?}, pending_requests={}",
978 recv_error,
979 self.expected_responses.len()
980 );
981 self.fail_all_pending_requests(MbusError::ConnectionLost);
982 let _ = self.transport.disconnect();
983 self.rxed_frame.clear();
984 } else {
985 client_log_trace!("non-fatal recv status during poll: {:?}", recv_error);
986 #[cfg(feature = "traffic")]
987 {
988 if !self.expected_responses.is_empty() {
991 self.app.on_rx_error(
992 0,
993 UnitIdOrSlaveAddr::from_u8(0),
994 recv_error,
995 self.rxed_frame.as_slice(),
996 );
997 }
998 }
999 }
1000 }
1001
1002 fn process_rxed_frame(&mut self) {
1003 while !self.rxed_frame.is_empty() {
1004 match self.ingest_frame() {
1005 Ok(consumed) => {
1006 self.drain_rxed_frame(consumed);
1007 }
1008 Err(MbusError::BufferTooSmall) => {
1009 client_log_trace!(
1011 "incomplete frame in rx buffer; waiting for more bytes (buffer_len={})",
1012 self.rxed_frame.len()
1013 );
1014 break;
1015 }
1016 Err(err) => {
1017 self.handle_parse_error(err);
1018 }
1019 }
1020 }
1021 }
1022
1023 fn handle_parse_error(&mut self, err: MbusError) {
1024 #[cfg(feature = "traffic")]
1025 self.app.on_rx_error(
1026 0,
1027 UnitIdOrSlaveAddr::from_u8(self.rxed_frame.first().copied().unwrap_or(0)),
1028 err,
1029 self.rxed_frame.as_slice(),
1030 );
1031
1032 client_log_debug!(
1034 "frame parse/resync event: error={:?}, buffer_len={}; dropping 1 byte",
1035 err,
1036 self.rxed_frame.len()
1037 );
1038 let len = self.rxed_frame.len();
1039 if len > 1 {
1040 self.rxed_frame.copy_within(1.., 0);
1041 self.rxed_frame.truncate(len - 1);
1042 } else {
1043 self.rxed_frame.clear();
1044 }
1045 }
1046
1047 fn drain_rxed_frame(&mut self, consumed: usize) {
1048 client_log_trace!(
1049 "ingested complete frame consuming {} bytes from rx buffer len {}",
1050 consumed,
1051 self.rxed_frame.len()
1052 );
1053 let len = self.rxed_frame.len();
1054 if consumed < len {
1055 self.rxed_frame.copy_within(consumed.., 0);
1057 self.rxed_frame.truncate(len - consumed);
1058 } else {
1059 self.rxed_frame.clear();
1060 }
1061 }
1062
1063 fn append_to_rxed_frame(&mut self, frame: Vec<u8, 513>) {
1064 client_log_trace!("received {} transport bytes", frame.len());
1065 if self.rxed_frame.extend_from_slice(frame.as_slice()).is_err() {
1066 client_log_debug!(
1068 "received frame buffer overflow while appending {} bytes; clearing receive buffer",
1069 frame.len()
1070 );
1071 #[cfg(feature = "traffic")]
1072 self.app.on_rx_error(
1073 0,
1074 UnitIdOrSlaveAddr::from_u8(0),
1075 MbusError::BufferTooSmall,
1076 frame.as_slice(),
1077 );
1078 self.rxed_frame.clear();
1079 }
1080 }
1081
1082 fn fail_all_pending_requests(&mut self, error: MbusError) {
1083 let pending_count = self.expected_responses.len();
1084 client_log_debug!(
1085 "failing {} pending request(s) with error {:?}",
1086 pending_count,
1087 error
1088 );
1089 while let Some(response) = self.expected_responses.pop() {
1090 #[cfg(feature = "traffic")]
1091 self.app.on_rx_error(
1092 response.txn_id,
1093 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1094 error,
1095 &[],
1096 );
1097 self.app.request_failed(
1098 response.txn_id,
1099 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1100 error,
1101 );
1102 }
1103 self.next_timeout_check = None;
1104 }
1105
1106 fn handle_timeouts(&mut self) {
1121 if self.expected_responses.is_empty() {
1122 self.next_timeout_check = None;
1123 return;
1124 }
1125
1126 let current_millis = self.app.current_millis();
1127
1128 if let Some(check_at) = self.next_timeout_check
1130 && current_millis < check_at
1131 {
1132 client_log_trace!(
1133 "skipping timeout scan until {}, current_millis={}",
1134 check_at,
1135 current_millis
1136 );
1137 return;
1138 }
1139
1140 let response_timeout_ms = self.response_timeout_ms();
1141 let retry_policy = RetryPolicy {
1142 backoff: self.config.retry_backoff_strategy(),
1143 jitter: self.config.retry_jitter_strategy(),
1144 random_fn: self.config.retry_random_fn(),
1145 };
1146 let mut i = 0;
1147 let mut new_next_check = u64::MAX;
1148
1149 while i < self.expected_responses.len() {
1150 match self.try_process_scheduled_retry(
1152 i,
1153 current_millis,
1154 response_timeout_ms,
1155 &mut new_next_check,
1156 ) {
1157 LoopAction::Advance => {
1158 i += 1;
1159 continue;
1160 }
1161 LoopAction::Repeat => {
1162 continue;
1163 }
1164 LoopAction::NotHandled => {}
1165 }
1166
1167 match self.try_handle_request_timeout(
1169 i,
1170 current_millis,
1171 response_timeout_ms,
1172 retry_policy,
1173 &mut new_next_check,
1174 ) {
1175 LoopAction::Advance => {
1176 i += 1;
1177 continue;
1178 }
1179 LoopAction::Repeat => {
1180 continue;
1181 }
1182 LoopAction::NotHandled => {}
1183 }
1184
1185 i += 1;
1187 }
1188
1189 self.next_timeout_check = if new_next_check != u64::MAX {
1190 Some(new_next_check)
1191 } else {
1192 None
1193 };
1194 }
1195
1196 fn try_process_scheduled_retry(
1205 &mut self,
1206 i: usize,
1207 current_millis: u64,
1208 response_timeout_ms: u64,
1209 new_next_check: &mut u64,
1210 ) -> LoopAction {
1211 let retry_at = match self.expected_responses[i].next_retry_timestamp {
1212 Some(t) => t,
1213 None => return LoopAction::NotHandled,
1214 };
1215
1216 if current_millis >= retry_at {
1217 return self.send_due_retry(i, current_millis, response_timeout_ms, new_next_check);
1218 }
1219
1220 if retry_at < *new_next_check {
1222 *new_next_check = retry_at;
1223 }
1224 LoopAction::Advance
1225 }
1226
1227 fn send_due_retry(
1232 &mut self,
1233 i: usize,
1234 current_millis: u64,
1235 response_timeout_ms: u64,
1236 new_next_check: &mut u64,
1237 ) -> LoopAction {
1238 let expected_response = &self.expected_responses[i];
1239 client_log_debug!(
1240 "retry due now: txn_id={}, unit_id_or_slave_addr={}, retry_attempt_index={}, retries_left={}",
1241 expected_response.txn_id,
1242 expected_response.unit_id_or_slave_addr,
1243 expected_response.retry_attempt_index.saturating_add(1),
1244 expected_response.retries_left
1245 );
1246
1247 let adu = self.expected_responses[i].original_adu.clone();
1249 if self.transport.send(&adu).is_err() {
1250 let response = self.expected_responses.swap_remove(i);
1254 client_log_debug!(
1255 "retry send failed: txn_id={}, unit_id_or_slave_addr={}; dropping request",
1256 response.txn_id,
1257 response.unit_id_or_slave_addr
1258 );
1259 self.app.request_failed(
1260 response.txn_id,
1261 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1262 MbusError::SendFailed,
1263 );
1264 #[cfg(feature = "traffic")]
1265 self.app.on_tx_error(
1266 response.txn_id,
1267 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1268 MbusError::SendFailed,
1269 adu.as_slice(),
1270 );
1271 return LoopAction::Repeat;
1272 }
1273
1274 #[cfg(feature = "traffic")]
1275 {
1276 let response = &self.expected_responses[i];
1277 self.app.on_tx_frame(
1278 response.txn_id,
1279 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1280 adu.as_slice(),
1281 );
1282 }
1283
1284 update_retries(
1285 current_millis,
1286 response_timeout_ms,
1287 new_next_check,
1288 &mut self.expected_responses[i],
1289 );
1290 LoopAction::Advance
1291 }
1292
1293 fn try_handle_request_timeout(
1306 &mut self,
1307 i: usize,
1308 current_millis: u64,
1309 response_timeout_ms: u64,
1310 retry_policy: RetryPolicy,
1311 new_next_check: &mut u64,
1312 ) -> LoopAction {
1313 let expires_at = self.expected_responses[i]
1314 .sent_timestamp
1315 .saturating_add(response_timeout_ms);
1316
1317 if current_millis <= expires_at {
1318 if expires_at < *new_next_check {
1320 *new_next_check = expires_at;
1321 }
1322 return LoopAction::NotHandled;
1323 }
1324
1325 if self.expected_responses[i].retries_left == 0 {
1326 return self.fail_exhausted_request(i);
1327 }
1328
1329 self.schedule_next_retry(i, current_millis, retry_policy, new_next_check)
1330 }
1331
1332 fn fail_exhausted_request(&mut self, i: usize) -> LoopAction {
1334 let response = self.expected_responses.swap_remove(i);
1337 client_log_debug!(
1338 "request exhausted retries: txn_id={}, unit_id_or_slave_addr={}",
1339 response.txn_id,
1340 response.unit_id_or_slave_addr
1341 );
1342 self.app.request_failed(
1343 response.txn_id,
1344 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1345 MbusError::NoRetriesLeft,
1346 );
1347 #[cfg(feature = "traffic")]
1348 self.app.on_rx_error(
1349 response.txn_id,
1350 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1351 MbusError::NoRetriesLeft,
1352 &[],
1353 );
1354 LoopAction::Repeat
1355 }
1356
1357 fn schedule_next_retry(
1362 &mut self,
1363 i: usize,
1364 current_millis: u64,
1365 retry_policy: RetryPolicy,
1366 new_next_check: &mut u64,
1367 ) -> LoopAction {
1368 let expected_response = &mut self.expected_responses[i];
1369 let next_attempt = expected_response.retry_attempt_index.saturating_add(1);
1370 let base_delay_ms = retry_policy.backoff.delay_ms_for_retry(next_attempt);
1371 let retry_delay_ms = retry_policy
1372 .jitter
1373 .apply(base_delay_ms, retry_policy.random_fn) as u64;
1374 let retry_at = current_millis.saturating_add(retry_delay_ms);
1375 expected_response.next_retry_timestamp = Some(retry_at);
1376
1377 client_log_debug!(
1378 "scheduling retry: txn_id={}, unit_id_or_slave_addr={}, next_attempt={}, delay_ms={}, retry_at={}",
1379 expected_response.txn_id,
1380 expected_response.unit_id_or_slave_addr,
1381 next_attempt,
1382 retry_delay_ms,
1383 retry_at
1384 );
1385
1386 if retry_delay_ms == 0 {
1389 client_log_trace!(
1390 "retry delay is zero; retry will be processed in the same poll cycle for txn_id={}",
1391 expected_response.txn_id
1392 );
1393 return LoopAction::Repeat;
1394 }
1395
1396 if retry_at < *new_next_check {
1397 *new_next_check = retry_at;
1398 }
1399 LoopAction::Advance
1400 }
1401
1402 fn add_an_expectation(
1403 &mut self,
1404 txn_id: u16,
1405 unit_id_slave_addr: UnitIdOrSlaveAddr,
1406 frame: &heapless::Vec<u8, MAX_ADU_FRAME_LEN>,
1407 operation_meta: OperationMeta,
1408 handler: ResponseHandler<TRANSPORT, APP, N>,
1409 ) -> Result<(), MbusError> {
1410 client_log_trace!(
1411 "queueing expected response: txn_id={}, unit_id_or_slave_addr={}, queue_len_before={}",
1412 txn_id,
1413 unit_id_slave_addr.get(),
1414 self.expected_responses.len()
1415 );
1416 self.expected_responses
1417 .push(ExpectedResponse {
1418 txn_id,
1419 unit_id_or_slave_addr: unit_id_slave_addr.get(),
1420 original_adu: frame.clone(),
1421 sent_timestamp: self.app.current_millis(),
1422 retries_left: self.retry_attempts(),
1423 retry_attempt_index: 0,
1424 next_retry_timestamp: None,
1425 handler,
1426 operation_meta,
1427 })
1428 .map_err(|_| MbusError::TooManyRequests)?;
1429 Ok(())
1430 }
1431}
1432
1433fn update_retries<TRANSPORT, APP, const N: usize>(
1434 current_millis: u64,
1435 response_timeout_ms: u64,
1436 new_next_check: &mut u64,
1437 expected_response: &mut ExpectedResponse<TRANSPORT, APP, N>,
1438) {
1439 expected_response.retries_left = expected_response.retries_left.saturating_sub(1);
1440 expected_response.retry_attempt_index = expected_response.retry_attempt_index.saturating_add(1);
1441 expected_response.sent_timestamp = current_millis;
1442 expected_response.next_retry_timestamp = None;
1443
1444 let expires_at = current_millis.saturating_add(response_timeout_ms);
1445 if expires_at < *new_next_check {
1446 *new_next_check = expires_at;
1447 }
1448}
1449
1450impl<TRANSPORT: Transport, APP: ClientCommon, const N: usize> ClientServices<TRANSPORT, APP, N> {
1452 pub fn new(transport: TRANSPORT, app: APP, config: ModbusConfig) -> Result<Self, MbusError> {
1456 let transport_type = transport.transport_type();
1457 if matches!(
1458 transport_type,
1459 TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1460 ) && N != 1
1461 {
1462 return Err(MbusError::InvalidNumOfExpectedRsps);
1463 }
1464
1465 client_log_debug!(
1466 "client created with transport_type={:?}, queue_capacity={}",
1467 transport_type,
1468 N
1469 );
1470
1471 Ok(Self {
1472 app,
1473 transport,
1474 rxed_frame: Vec::new(),
1475 config,
1476 expected_responses: Vec::new(),
1477 next_timeout_check: None,
1478 })
1479 }
1480
1481 pub fn connect(&mut self) -> Result<(), MbusError>
1487 where
1488 TRANSPORT::Error: Into<MbusError>,
1489 {
1490 client_log_debug!("connecting transport");
1491 self.transport.connect(&self.config).map_err(|e| e.into())
1492 }
1493
1494 pub fn app(&self) -> &APP {
1499 &self.app
1500 }
1501
1502 pub fn is_connected(&self) -> bool {
1504 self.transport.is_connected()
1505 }
1506
1507 pub fn disconnect(&mut self)
1518 where
1519 TRANSPORT::Error: Into<MbusError>,
1520 {
1521 client_log_debug!(
1522 "disconnect requested; pending_requests={}",
1523 self.expected_responses.len()
1524 );
1525 self.fail_all_pending_requests(MbusError::ConnectionLost);
1526 self.rxed_frame.clear();
1527 self.next_timeout_check = None;
1528 let _ = self.transport.disconnect();
1529 }
1530
1531 pub fn reconnect(&mut self) -> Result<(), MbusError>
1542 where
1543 TRANSPORT::Error: Into<MbusError>,
1544 {
1545 client_log_debug!(
1546 "reconnect requested; pending_requests={}",
1547 self.expected_responses.len()
1548 );
1549 self.fail_all_pending_requests(MbusError::ConnectionLost);
1550 self.rxed_frame.clear();
1551 self.next_timeout_check = None;
1552
1553 let _ = self.transport.disconnect();
1554 self.connect()
1555 }
1556
1557 pub fn new_serial(
1567 transport: TRANSPORT,
1568 app: APP,
1569 config: ModbusSerialConfig,
1570 ) -> Result<Self, MbusError>
1571 where
1572 [(); N]: SerialQueueSizeOne,
1573 {
1574 let transport_type = transport.transport_type();
1575 if !matches!(
1576 transport_type,
1577 TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1578 ) {
1579 return Err(MbusError::InvalidTransport);
1580 }
1581
1582 let config = ModbusConfig::Serial(config);
1583
1584 client_log_debug!("serial client created with queue_capacity={}", N);
1585
1586 Ok(Self {
1587 app,
1588 transport,
1589 rxed_frame: Vec::new(),
1590 config,
1591 expected_responses: Vec::new(),
1592 next_timeout_check: None,
1593 })
1594 }
1595
1596 fn response_timeout_ms(&self) -> u64 {
1598 match &self.config {
1599 ModbusConfig::Tcp(config) => config.response_timeout_ms as u64,
1600 ModbusConfig::Serial(config) => config.response_timeout_ms as u64,
1601 }
1602 }
1603
1604 fn retry_attempts(&self) -> u8 {
1606 match &self.config {
1607 ModbusConfig::Tcp(config) => config.retry_attempts,
1608 ModbusConfig::Serial(config) => config.retry_attempts,
1609 }
1610 }
1611
1612 fn ingest_frame(&mut self) -> Result<usize, MbusError> {
1614 let frame = self.rxed_frame.as_slice();
1615 let transport_type = self.transport.transport_type();
1616
1617 client_log_trace!(
1618 "attempting frame ingest: transport_type={:?}, buffer_len={}",
1619 transport_type,
1620 frame.len()
1621 );
1622
1623 let expected_length = match derive_length_from_bytes(frame, transport_type) {
1624 Some(len) => len,
1625 None => return Err(MbusError::BufferTooSmall),
1626 };
1627
1628 client_log_trace!("derived expected frame length={}", expected_length);
1629
1630 if expected_length > MAX_ADU_FRAME_LEN {
1631 client_log_debug!(
1632 "derived frame length {} exceeds MAX_ADU_FRAME_LEN {}",
1633 expected_length,
1634 MAX_ADU_FRAME_LEN
1635 );
1636 return Err(MbusError::BasicParseError);
1637 }
1638
1639 if self.rxed_frame.len() < expected_length {
1640 return Err(MbusError::BufferTooSmall);
1641 }
1642
1643 let message = match common::decompile_adu_frame(&frame[..expected_length], transport_type) {
1644 Ok(value) => value,
1645 Err(err) => {
1646 client_log_debug!(
1647 "decompile_adu_frame failed for {} bytes: {:?}",
1648 expected_length,
1649 err
1650 );
1651 return Err(err); }
1653 };
1654 use mbus_core::data_unit::common::AdditionalAddress;
1655 use mbus_core::transport::TransportType::*;
1656 let message = match self.transport.transport_type() {
1657 StdTcp | CustomTcp => {
1658 let mbap_header = match message.additional_address() {
1659 AdditionalAddress::MbapHeader(header) => header,
1660 _ => return Ok(expected_length),
1661 };
1662 let additional_addr = AdditionalAddress::MbapHeader(*mbap_header);
1663 ModbusMessage::new(additional_addr, message.pdu)
1664 }
1665 StdSerial(_) | CustomSerial(_) => {
1666 let slave_addr = match message.additional_address() {
1667 AdditionalAddress::SlaveAddress(addr) => addr.address(),
1668 _ => return Ok(expected_length),
1669 };
1670
1671 let additional_address =
1672 AdditionalAddress::SlaveAddress(SlaveAddress::new(slave_addr)?);
1673 ModbusMessage::new(additional_address, message.pdu)
1674 }
1675 };
1676
1677 let mut raw_frame = Vec::<u8, MAX_ADU_FRAME_LEN>::new();
1678 raw_frame
1679 .extend_from_slice(&frame[..expected_length])
1680 .map_err(|_| MbusError::BufferLenMissmatch)?;
1681
1682 self.dispatch_response(&message, raw_frame.as_slice());
1683 client_log_trace!("frame dispatch complete for {} bytes", expected_length);
1684
1685 Ok(expected_length)
1686 }
1687
1688 pub(crate) fn dispatch_request_frame(
1689 &mut self,
1690 txn_id: u16,
1691 unit_id_slave_addr: UnitIdOrSlaveAddr,
1692 frame: &heapless::Vec<u8, MAX_ADU_FRAME_LEN>,
1693 ) -> Result<(), MbusError> {
1694 if self.transport.send(frame).is_err() {
1695 #[cfg(feature = "traffic")]
1696 self.app.on_tx_error(
1697 txn_id,
1698 unit_id_slave_addr,
1699 MbusError::SendFailed,
1700 frame.as_slice(),
1701 );
1702 return Err(MbusError::SendFailed);
1703 }
1704
1705 #[cfg(feature = "traffic")]
1706 self.app
1707 .on_tx_frame(txn_id, unit_id_slave_addr, frame.as_slice());
1708
1709 #[cfg(not(feature = "traffic"))]
1710 {
1711 let _ = txn_id;
1712 let _ = unit_id_slave_addr;
1713 }
1714
1715 Ok(())
1716 }
1717}
1718
1719#[cfg(test)]
1720mod tests {
1721 use super::*;
1722 use crate::app::CoilResponse;
1723 use crate::app::DiagnosticsResponse;
1724 use crate::app::DiscreteInputResponse;
1725 use crate::app::FifoQueueResponse;
1726 use crate::app::FileRecordResponse;
1727 use crate::app::RegisterResponse;
1728 #[cfg(feature = "traffic")]
1729 use crate::app::TrafficDirection;
1730 use crate::services::coil::Coils;
1731
1732 use crate::services::diagnostic::ConformityLevel;
1733 use crate::services::diagnostic::DeviceIdentificationResponse;
1734 use crate::services::diagnostic::ObjectId;
1735 use crate::services::discrete_input::DiscreteInputs;
1736 use crate::services::fifo_queue::FifoQueue;
1737 use crate::services::file_record::MAX_SUB_REQUESTS_PER_PDU;
1738 use crate::services::file_record::SubRequest;
1739 use crate::services::file_record::SubRequestParams;
1740 use crate::services::register::Registers;
1741 use core::cell::RefCell; use core::str::FromStr;
1743 use heapless::Deque;
1744 use heapless::Vec;
1745 use mbus_core::errors::MbusError;
1746 use mbus_core::function_codes::public::DiagnosticSubFunction;
1747 use mbus_core::transport::TransportType;
1748 use mbus_core::transport::checksum;
1749 use mbus_core::transport::{
1750 BackoffStrategy, BaudRate, JitterStrategy, ModbusConfig, ModbusSerialConfig,
1751 ModbusTcpConfig, Parity, SerialMode,
1752 };
1753
1754 const MOCK_DEQUE_CAPACITY: usize = 10; fn rand_zero() -> u32 {
1757 0
1758 }
1759
1760 fn rand_upper_percent_20() -> u32 {
1761 40
1762 }
1763
1764 fn make_serial_config() -> ModbusSerialConfig {
1765 ModbusSerialConfig {
1766 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
1767 mode: SerialMode::Rtu,
1768 baud_rate: BaudRate::Baud19200,
1769 data_bits: mbus_core::transport::DataBits::Eight,
1770 stop_bits: 1,
1771 parity: Parity::Even,
1772 response_timeout_ms: 100,
1773 retry_attempts: 0,
1774 retry_backoff_strategy: BackoffStrategy::Immediate,
1775 retry_jitter_strategy: JitterStrategy::None,
1776 retry_random_fn: None,
1777 }
1778 }
1779
1780 fn make_serial_client() -> ClientServices<MockTransport, MockApp, 1> {
1781 let transport = MockTransport {
1782 transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
1783 ..Default::default()
1784 };
1785 let app = MockApp::default();
1786 let mut client = ClientServices::<MockTransport, MockApp, 1>::new_serial(
1787 transport,
1788 app,
1789 make_serial_config(),
1790 )
1791 .unwrap();
1792 client.connect().unwrap();
1793 client
1794 }
1795
1796 fn make_rtu_exception_adu(
1797 unit_id: UnitIdOrSlaveAddr,
1798 function_code: u8,
1799 exception_code: u8,
1800 ) -> Vec<u8, MAX_ADU_FRAME_LEN> {
1801 let mut frame = Vec::new();
1802 frame.push(unit_id.get()).unwrap();
1803 frame.push(function_code | 0x80).unwrap();
1804 frame.push(exception_code).unwrap();
1805 let crc = checksum::crc16(frame.as_slice()).to_le_bytes();
1806 frame.extend_from_slice(&crc).unwrap();
1807 frame
1808 }
1809
1810 #[derive(Debug, Default)]
1812 struct MockTransport {
1813 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>>,
1816 pub connect_should_fail: bool,
1817 pub send_should_fail: bool,
1818 pub is_connected_flag: RefCell<bool>,
1819 pub transport_type: Option<TransportType>,
1820 }
1821
1822 impl Transport for MockTransport {
1823 type Error = MbusError;
1824
1825 fn connect(&mut self, _config: &ModbusConfig) -> Result<(), Self::Error> {
1826 if self.connect_should_fail {
1827 return Err(MbusError::ConnectionFailed);
1828 }
1829 *self.is_connected_flag.borrow_mut() = true;
1830 Ok(())
1831 }
1832
1833 fn disconnect(&mut self) -> Result<(), Self::Error> {
1834 *self.is_connected_flag.borrow_mut() = false;
1835 Ok(())
1836 }
1837
1838 fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
1839 if self.send_should_fail {
1840 return Err(MbusError::SendFailed);
1841 }
1842 let mut vec_adu = Vec::new();
1843 vec_adu
1844 .extend_from_slice(adu)
1845 .map_err(|_| MbusError::BufferLenMissmatch)?;
1846 self.sent_frames
1847 .borrow_mut()
1848 .push_back(vec_adu)
1849 .map_err(|_| MbusError::BufferLenMissmatch)?;
1850 Ok(())
1851 }
1852
1853 fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
1854 if let Some(err) = self.recv_error.borrow_mut().take() {
1855 return Err(err);
1856 }
1857 self.recv_frames
1858 .borrow_mut()
1859 .pop_front()
1860 .ok_or(MbusError::Timeout)
1861 }
1862
1863 fn is_connected(&self) -> bool {
1864 *self.is_connected_flag.borrow()
1865 }
1866
1867 fn transport_type(&self) -> TransportType {
1868 self.transport_type.unwrap_or(TransportType::StdTcp)
1869 }
1870 }
1871
1872 #[derive(Debug, Default)]
1874 struct MockApp {
1875 pub received_coil_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr, Coils), 10>>, pub received_write_single_coil_responses:
1877 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, bool), 10>>,
1878 pub received_write_multiple_coils_responses:
1879 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1880 pub received_discrete_input_responses:
1881 RefCell<Vec<(u16, UnitIdOrSlaveAddr, DiscreteInputs, u16), 10>>,
1882 pub received_holding_register_responses:
1883 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1884 pub received_input_register_responses:
1885 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1886 pub received_write_single_register_responses:
1887 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1888 pub received_write_multiple_register_responses:
1889 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1890 pub received_read_write_multiple_registers_responses:
1891 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers), 10>>,
1892 pub received_mask_write_register_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1893 pub received_read_fifo_queue_responses:
1894 RefCell<Vec<(u16, UnitIdOrSlaveAddr, FifoQueue), 10>>,
1895 pub received_read_file_record_responses: RefCell<
1896 Vec<
1897 (
1898 u16,
1899 UnitIdOrSlaveAddr,
1900 Vec<SubRequestParams, MAX_SUB_REQUESTS_PER_PDU>,
1901 ),
1902 10,
1903 >,
1904 >,
1905 pub received_write_file_record_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1906 pub received_read_device_id_responses:
1907 RefCell<Vec<(u16, UnitIdOrSlaveAddr, DeviceIdentificationResponse), 10>>,
1908 pub failed_requests: RefCell<Vec<(u16, UnitIdOrSlaveAddr, MbusError), 10>>,
1909 #[cfg(feature = "traffic")]
1910 pub traffic_events: RefCell<Vec<(TrafficDirection, u16, UnitIdOrSlaveAddr), 32>>,
1911 #[cfg(feature = "traffic")]
1912 pub traffic_error_events:
1913 RefCell<Vec<(TrafficDirection, u16, UnitIdOrSlaveAddr, MbusError), 32>>,
1914
1915 pub current_time: RefCell<u64>, }
1917
1918 impl CoilResponse for MockApp {
1919 fn read_coils_response(
1920 &mut self,
1921 txn_id: u16,
1922 unit_id_slave_addr: UnitIdOrSlaveAddr,
1923 coils: &Coils,
1924 ) {
1925 self.received_coil_responses
1926 .borrow_mut()
1927 .push((txn_id, unit_id_slave_addr, coils.clone()))
1928 .unwrap();
1929 }
1930
1931 fn read_single_coil_response(
1932 &mut self,
1933 txn_id: u16,
1934 unit_id_slave_addr: UnitIdOrSlaveAddr,
1935 address: u16,
1936 value: bool,
1937 ) {
1938 let mut values_vec = [0x00, 1];
1940 values_vec[0] = if value { 0x01 } else { 0x00 }; let coils = Coils::new(address, 1)
1942 .unwrap()
1943 .with_values(&values_vec, 1)
1944 .unwrap();
1945 self.received_coil_responses
1946 .borrow_mut()
1947 .push((txn_id, unit_id_slave_addr, coils))
1948 .unwrap();
1949 }
1950
1951 fn write_single_coil_response(
1952 &mut self,
1953 txn_id: u16,
1954 unit_id_slave_addr: UnitIdOrSlaveAddr,
1955 address: u16,
1956 value: bool,
1957 ) {
1958 self.received_write_single_coil_responses
1959 .borrow_mut()
1960 .push((txn_id, unit_id_slave_addr, address, value))
1961 .unwrap();
1962 }
1963
1964 fn write_multiple_coils_response(
1965 &mut self,
1966 txn_id: u16,
1967 unit_id_slave_addr: UnitIdOrSlaveAddr,
1968 address: u16,
1969 quantity: u16,
1970 ) {
1971 self.received_write_multiple_coils_responses
1972 .borrow_mut()
1973 .push((txn_id, unit_id_slave_addr, address, quantity))
1974 .unwrap();
1975 }
1976 }
1977
1978 impl DiscreteInputResponse for MockApp {
1979 fn read_multiple_discrete_inputs_response(
1980 &mut self,
1981 txn_id: u16,
1982 unit_id_slave_addr: UnitIdOrSlaveAddr,
1983 inputs: &DiscreteInputs,
1984 ) {
1985 self.received_discrete_input_responses
1986 .borrow_mut()
1987 .push((
1988 txn_id,
1989 unit_id_slave_addr,
1990 inputs.clone(),
1991 inputs.quantity(),
1992 ))
1993 .unwrap();
1994 }
1995
1996 fn read_single_discrete_input_response(
1997 &mut self,
1998 txn_id: u16,
1999 unit_id_slave_addr: UnitIdOrSlaveAddr,
2000 address: u16,
2001 value: bool,
2002 ) {
2003 let mut values = [0u8; mbus_core::models::discrete_input::MAX_DISCRETE_INPUT_BYTES];
2004 values[0] = if value { 0x01 } else { 0x00 };
2005 let inputs = DiscreteInputs::new(address, 1)
2006 .unwrap()
2007 .with_values(&values, 1)
2008 .unwrap();
2009 self.received_discrete_input_responses
2010 .borrow_mut()
2011 .push((txn_id, unit_id_slave_addr, inputs, 1))
2012 .unwrap();
2013 }
2014 }
2015
2016 impl RequestErrorNotifier for MockApp {
2017 fn request_failed(
2018 &mut self,
2019 txn_id: u16,
2020 unit_id_slave_addr: UnitIdOrSlaveAddr,
2021 error: MbusError,
2022 ) {
2023 self.failed_requests
2024 .borrow_mut()
2025 .push((txn_id, unit_id_slave_addr, error))
2026 .unwrap();
2027 }
2028 }
2029
2030 #[cfg(feature = "traffic")]
2031 impl crate::app::TrafficNotifier for MockApp {
2032 fn on_tx_frame(
2033 &mut self,
2034 txn_id: u16,
2035 unit_id_slave_addr: UnitIdOrSlaveAddr,
2036 _frame_bytes: &[u8],
2037 ) {
2038 self.traffic_events
2039 .borrow_mut()
2040 .push((TrafficDirection::Tx, txn_id, unit_id_slave_addr))
2041 .unwrap();
2042 }
2043
2044 fn on_rx_frame(
2045 &mut self,
2046 txn_id: u16,
2047 unit_id_slave_addr: UnitIdOrSlaveAddr,
2048 _frame_bytes: &[u8],
2049 ) {
2050 self.traffic_events
2051 .borrow_mut()
2052 .push((TrafficDirection::Rx, txn_id, unit_id_slave_addr))
2053 .unwrap();
2054 }
2055
2056 fn on_tx_error(
2057 &mut self,
2058 txn_id: u16,
2059 unit_id_slave_addr: UnitIdOrSlaveAddr,
2060 error: MbusError,
2061 _frame_bytes: &[u8],
2062 ) {
2063 self.traffic_error_events
2064 .borrow_mut()
2065 .push((TrafficDirection::Tx, txn_id, unit_id_slave_addr, error))
2066 .unwrap();
2067 }
2068
2069 fn on_rx_error(
2070 &mut self,
2071 txn_id: u16,
2072 unit_id_slave_addr: UnitIdOrSlaveAddr,
2073 error: MbusError,
2074 _frame_bytes: &[u8],
2075 ) {
2076 self.traffic_error_events
2077 .borrow_mut()
2078 .push((TrafficDirection::Rx, txn_id, unit_id_slave_addr, error))
2079 .unwrap();
2080 }
2081 }
2082
2083 impl RegisterResponse for MockApp {
2084 fn read_multiple_holding_registers_response(
2085 &mut self,
2086 txn_id: u16,
2087 unit_id_slave_addr: UnitIdOrSlaveAddr,
2088 registers: &Registers,
2089 ) {
2090 let quantity = registers.quantity();
2091 self.received_holding_register_responses
2092 .borrow_mut()
2093 .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
2094 .unwrap();
2095 }
2096
2097 fn read_single_input_register_response(
2098 &mut self,
2099 txn_id: u16,
2100 unit_id_slave_addr: UnitIdOrSlaveAddr,
2101 address: u16,
2102 value: u16,
2103 ) {
2104 let values = [value];
2106 let registers = Registers::new(address, 1)
2107 .unwrap()
2108 .with_values(&values, 1)
2109 .unwrap();
2110 self.received_input_register_responses
2111 .borrow_mut()
2112 .push((txn_id, unit_id_slave_addr, registers, 1))
2113 .unwrap();
2114 }
2115
2116 fn read_single_holding_register_response(
2117 &mut self,
2118 txn_id: u16,
2119 unit_id_slave_addr: UnitIdOrSlaveAddr,
2120 address: u16,
2121 value: u16,
2122 ) {
2123 let data = [value];
2125 let registers = Registers::new(address, 1)
2127 .unwrap()
2128 .with_values(&data, 1)
2129 .unwrap();
2130
2131 self.received_holding_register_responses
2132 .borrow_mut()
2133 .push((txn_id, unit_id_slave_addr, registers, 1))
2134 .unwrap();
2135 }
2136
2137 fn read_multiple_input_registers_response(
2138 &mut self,
2139 txn_id: u16,
2140 unit_id_slave_addr: UnitIdOrSlaveAddr,
2141 registers: &Registers,
2142 ) {
2143 let quantity = registers.quantity();
2144 self.received_input_register_responses
2145 .borrow_mut()
2146 .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
2147 .unwrap();
2148 }
2149
2150 fn write_single_register_response(
2151 &mut self,
2152 txn_id: u16,
2153 unit_id_slave_addr: UnitIdOrSlaveAddr,
2154 address: u16,
2155 value: u16,
2156 ) {
2157 self.received_write_single_register_responses
2158 .borrow_mut()
2159 .push((txn_id, unit_id_slave_addr, address, value))
2160 .unwrap();
2161 }
2162
2163 fn write_multiple_registers_response(
2164 &mut self,
2165 txn_id: u16,
2166 unit_id_slave_addr: UnitIdOrSlaveAddr,
2167 address: u16,
2168 quantity: u16,
2169 ) {
2170 self.received_write_multiple_register_responses
2171 .borrow_mut()
2172 .push((txn_id, unit_id_slave_addr, address, quantity))
2173 .unwrap();
2174 }
2175
2176 fn read_write_multiple_registers_response(
2177 &mut self,
2178 txn_id: u16,
2179 unit_id_slave_addr: UnitIdOrSlaveAddr,
2180 registers: &Registers,
2181 ) {
2182 self.received_read_write_multiple_registers_responses
2183 .borrow_mut()
2184 .push((txn_id, unit_id_slave_addr, registers.clone()))
2185 .unwrap();
2186 }
2187
2188 fn mask_write_register_response(
2189 &mut self,
2190 txn_id: u16,
2191 unit_id_slave_addr: UnitIdOrSlaveAddr,
2192 ) {
2193 self.received_mask_write_register_responses
2194 .borrow_mut()
2195 .push((txn_id, unit_id_slave_addr))
2196 .unwrap();
2197 }
2198
2199 fn read_single_register_response(
2200 &mut self,
2201 txn_id: u16,
2202 unit_id_slave_addr: UnitIdOrSlaveAddr,
2203 address: u16,
2204 value: u16,
2205 ) {
2206 let data = [value];
2208 let registers = Registers::new(address, 1)
2210 .unwrap()
2211 .with_values(&data, 1)
2212 .unwrap();
2213
2214 self.received_holding_register_responses
2215 .borrow_mut()
2216 .push((txn_id, unit_id_slave_addr, registers, 1))
2217 .unwrap();
2218 }
2219 }
2220
2221 impl FifoQueueResponse for MockApp {
2222 fn read_fifo_queue_response(
2223 &mut self,
2224 txn_id: u16,
2225 unit_id_slave_addr: UnitIdOrSlaveAddr,
2226 fifo_queue: &FifoQueue,
2227 ) {
2228 self.received_read_fifo_queue_responses
2229 .borrow_mut()
2230 .push((txn_id, unit_id_slave_addr, fifo_queue.clone()))
2231 .unwrap();
2232 }
2233 }
2234
2235 impl FileRecordResponse for MockApp {
2236 fn read_file_record_response(
2237 &mut self,
2238 txn_id: u16,
2239 unit_id_slave_addr: UnitIdOrSlaveAddr,
2240 data: &[SubRequestParams],
2241 ) {
2242 let mut vec = Vec::new();
2243 vec.extend_from_slice(data).unwrap();
2244 self.received_read_file_record_responses
2245 .borrow_mut()
2246 .push((txn_id, unit_id_slave_addr, vec))
2247 .unwrap();
2248 }
2249 fn write_file_record_response(
2250 &mut self,
2251 txn_id: u16,
2252 unit_id_slave_addr: UnitIdOrSlaveAddr,
2253 ) {
2254 self.received_write_file_record_responses
2255 .borrow_mut()
2256 .push((txn_id, unit_id_slave_addr))
2257 .unwrap();
2258 }
2259 }
2260
2261 impl DiagnosticsResponse for MockApp {
2262 fn read_device_identification_response(
2263 &mut self,
2264 txn_id: u16,
2265 unit_id_slave_addr: UnitIdOrSlaveAddr,
2266 response: &DeviceIdentificationResponse,
2267 ) {
2268 self.received_read_device_id_responses
2269 .borrow_mut()
2270 .push((txn_id, unit_id_slave_addr, response.clone()))
2271 .unwrap();
2272 }
2273
2274 fn encapsulated_interface_transport_response(
2275 &mut self,
2276 _: u16,
2277 _: UnitIdOrSlaveAddr,
2278 _: EncapsulatedInterfaceType,
2279 _: &[u8],
2280 ) {
2281 }
2282
2283 fn diagnostics_response(
2284 &mut self,
2285 _: u16,
2286 _: UnitIdOrSlaveAddr,
2287 _: DiagnosticSubFunction,
2288 _: &[u16],
2289 ) {
2290 }
2291
2292 fn get_comm_event_counter_response(
2293 &mut self,
2294 _: u16,
2295 _: UnitIdOrSlaveAddr,
2296 _: u16,
2297 _: u16,
2298 ) {
2299 }
2300
2301 fn get_comm_event_log_response(
2302 &mut self,
2303 _: u16,
2304 _: UnitIdOrSlaveAddr,
2305 _: u16,
2306 _: u16,
2307 _: u16,
2308 _: &[u8],
2309 ) {
2310 }
2311
2312 fn read_exception_status_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: u8) {}
2313
2314 fn report_server_id_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: &[u8]) {}
2315 }
2316
2317 impl TimeKeeper for MockApp {
2318 fn current_millis(&self) -> u64 {
2319 *self.current_time.borrow()
2320 }
2321 }
2322
2323 #[test]
2327 fn test_client_services_new_success() {
2328 let transport = MockTransport::default();
2329 let app = MockApp::default();
2330 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2331
2332 let client_services =
2333 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
2334 assert!(client_services.is_ok());
2335 let mut client = client_services.unwrap();
2336 assert!(!client.is_connected());
2337 assert!(client.connect().is_ok());
2338 assert!(client.is_connected());
2339 }
2340
2341 #[test]
2343 fn test_client_services_connect_failure() {
2344 let mut transport = MockTransport::default();
2345 transport.connect_should_fail = true;
2346 let app = MockApp::default();
2347 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2348
2349 let client_services =
2350 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
2351 assert!(client_services.is_ok());
2352 let mut client = client_services.unwrap();
2353 let result = client.connect();
2354 assert!(result.is_err());
2355 assert_eq!(result.unwrap_err(), MbusError::ConnectionFailed);
2356 }
2357
2358 #[test]
2359 fn test_client_services_new_serial_success() {
2360 let transport = MockTransport {
2361 transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
2362 ..Default::default()
2363 };
2364 let app = MockApp::default();
2365 let serial_config = ModbusSerialConfig {
2366 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
2367 mode: SerialMode::Rtu,
2368 baud_rate: BaudRate::Baud19200,
2369 data_bits: mbus_core::transport::DataBits::Eight,
2370 stop_bits: 1,
2371 parity: Parity::Even,
2372 response_timeout_ms: 1000,
2373 retry_attempts: 1,
2374 retry_backoff_strategy: BackoffStrategy::Immediate,
2375 retry_jitter_strategy: JitterStrategy::None,
2376 retry_random_fn: None,
2377 };
2378
2379 let client_services =
2380 ClientServices::<MockTransport, MockApp, 1>::new_serial(transport, app, serial_config);
2381 assert!(client_services.is_ok());
2382 let mut client = client_services.unwrap();
2383 assert!(client.connect().is_ok());
2384 }
2385
2386 #[test]
2387 fn test_reconnect_success_flushes_pending_requests() {
2388 let transport = MockTransport::default();
2389 let app = MockApp::default();
2390 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2391 let mut client_services =
2392 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2393 client_services.connect().unwrap();
2394
2395 let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
2396 client_services.read_single_coil(10, unit_id, 0).unwrap();
2397 assert_eq!(client_services.expected_responses.len(), 1);
2398
2399 let reconnect_result = client_services.reconnect();
2400 assert!(reconnect_result.is_ok());
2401 assert!(client_services.is_connected());
2402 assert!(client_services.expected_responses.is_empty());
2403
2404 let failed_requests = client_services.app().failed_requests.borrow();
2405 assert_eq!(failed_requests.len(), 1);
2406 assert_eq!(failed_requests[0].0, 10);
2407 assert_eq!(failed_requests[0].2, MbusError::ConnectionLost);
2408 }
2409
2410 #[test]
2411 fn test_reconnect_failure_propagates_connect_error() {
2412 let transport = MockTransport::default();
2413 let app = MockApp::default();
2414 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2415 let mut client_services =
2416 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2417 client_services.connect().unwrap();
2418
2419 client_services.transport.connect_should_fail = true;
2420 let reconnect_result = client_services.reconnect();
2421
2422 assert!(reconnect_result.is_err());
2423 assert_eq!(reconnect_result.unwrap_err(), MbusError::ConnectionFailed);
2424 assert!(!client_services.is_connected());
2425 }
2426
2427 #[test]
2429 fn test_read_multiple_coils_sends_valid_adu() {
2430 let transport = MockTransport::default();
2431 let app = MockApp::default();
2432 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2433 let mut client_services =
2434 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2435 client_services.connect().unwrap();
2436
2437 let txn_id = 0x0001;
2438 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2439 let address = 0x0000;
2440 let quantity = 8;
2441 client_services
2442 .read_multiple_coils(txn_id, unit_id, address, quantity)
2443 .unwrap();
2444
2445 let sent_frames = client_services.transport.sent_frames.borrow();
2446 assert_eq!(sent_frames.len(), 1);
2447 let sent_adu = sent_frames.front().unwrap();
2448
2449 #[rustfmt::skip]
2451 let expected_adu: [u8; 12] = [
2452 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, ];
2460 assert_eq!(sent_adu.as_slice(), &expected_adu);
2461 }
2462
2463 #[cfg(feature = "traffic")]
2464 #[test]
2465 fn test_traffic_tx_event_emitted_on_submit() {
2466 let transport = MockTransport::default();
2467 let app = MockApp::default();
2468 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2469 let mut client_services =
2470 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2471 client_services.connect().unwrap();
2472
2473 let txn_id = 0x0001;
2474 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2475 client_services
2476 .read_multiple_coils(txn_id, unit_id, 0x0000, 8)
2477 .unwrap();
2478
2479 let events = client_services.app().traffic_events.borrow();
2480 assert!(!events.is_empty());
2481 assert_eq!(events[0].0, TrafficDirection::Tx);
2482 assert_eq!(events[0].1, txn_id);
2483 assert_eq!(events[0].2, unit_id);
2484 }
2485
2486 #[cfg(feature = "traffic")]
2487 #[test]
2488 fn test_traffic_rx_event_emitted_on_dispatch() {
2489 let transport = MockTransport::default();
2490 let app = MockApp::default();
2491 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2492 let mut client_services =
2493 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2494 client_services.connect().unwrap();
2495
2496 let txn_id = 0x0001;
2497 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2498 client_services
2499 .read_multiple_coils(txn_id, unit_id, 0x0000, 8)
2500 .unwrap();
2501
2502 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2503 client_services
2504 .transport
2505 .recv_frames
2506 .borrow_mut()
2507 .push_back(Vec::from_slice(&response_adu).unwrap())
2508 .unwrap();
2509
2510 client_services.poll();
2511
2512 let events = client_services.app().traffic_events.borrow();
2513 assert!(events.len() >= 2);
2514 assert_eq!(events[0].0, TrafficDirection::Tx);
2515 assert_eq!(events[1].0, TrafficDirection::Rx);
2516 assert_eq!(events[1].1, txn_id);
2517 assert_eq!(events[1].2, unit_id);
2518 }
2519
2520 #[cfg(feature = "traffic")]
2521 #[test]
2522 fn test_traffic_tx_error_emitted_on_submit_send_failure() {
2523 let mut transport = MockTransport::default();
2524 transport.send_should_fail = true;
2525 let app = MockApp::default();
2526 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2527 let mut client_services =
2528 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2529 client_services.connect().unwrap();
2530
2531 let txn_id = 0x0066;
2532 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2533 let result = client_services.read_multiple_coils(txn_id, unit_id, 0x0000, 8);
2534 assert_eq!(result.unwrap_err(), MbusError::SendFailed);
2535
2536 let events = client_services.app().traffic_error_events.borrow();
2537 assert!(!events.is_empty());
2538 assert_eq!(events[0].0, TrafficDirection::Tx);
2539 assert_eq!(events[0].1, txn_id);
2540 assert_eq!(events[0].2, unit_id);
2541 assert_eq!(events[0].3, MbusError::SendFailed);
2542 }
2543
2544 #[cfg(feature = "traffic")]
2545 #[test]
2546 fn test_traffic_rx_error_emitted_on_timeout_path() {
2547 let transport = MockTransport::default();
2548 let app = MockApp::default();
2549 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2550 tcp_config.response_timeout_ms = 100;
2551 tcp_config.retry_attempts = 0;
2552 let config = ModbusConfig::Tcp(tcp_config);
2553 let mut client_services =
2554 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2555 client_services.connect().unwrap();
2556
2557 let txn_id = 0x0007;
2558 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2559 client_services
2560 .read_multiple_coils(txn_id, unit_id, 0x0000, 8)
2561 .unwrap();
2562
2563 *client_services.app.current_time.borrow_mut() = 500;
2564 client_services.poll();
2565
2566 let events = client_services.app().traffic_error_events.borrow();
2567 assert!(!events.is_empty());
2568 assert!(events.iter().any(|(direction, _, _, err)| {
2569 *direction == TrafficDirection::Rx
2570 && matches!(err, MbusError::Timeout | MbusError::NoRetriesLeft)
2571 }));
2572 }
2573
2574 #[test]
2576 fn test_read_multiple_coils_invalid_quantity() {
2577 let transport = MockTransport::default();
2578 let app = MockApp::default();
2579 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2580 let mut client_services =
2581 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2582 client_services.connect().unwrap();
2583
2584 let txn_id = 0x0001;
2585 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2586 let address = 0x0000;
2587 let quantity = 0; let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); assert_eq!(result.unwrap_err(), MbusError::InvalidQuantity);
2591 }
2592
2593 #[test]
2595 fn test_read_multiple_coils_send_failure() {
2596 let mut transport = MockTransport::default();
2597 transport.send_should_fail = true;
2598 let app = MockApp::default();
2599 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2600 let mut client_services =
2601 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2602 client_services.connect().unwrap();
2603
2604 let txn_id = 0x0001;
2605 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2606 let address = 0x0000;
2607 let quantity = 8;
2608
2609 let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); assert_eq!(result.unwrap_err(), MbusError::SendFailed);
2611 }
2612
2613 #[test]
2615 fn test_ingest_frame_wrong_fc() {
2616 let transport = MockTransport::default();
2617 let app = MockApp::default();
2618 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2619 let mut client_services =
2620 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2621 client_services.connect().unwrap();
2622
2623 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x03, 0x01, 0xB3];
2625
2626 client_services
2627 .transport
2628 .recv_frames
2629 .borrow_mut()
2630 .push_back(Vec::from_slice(&response_adu).unwrap())
2631 .unwrap();
2632 client_services.poll();
2633
2634 let received_responses = client_services.app().received_coil_responses.borrow();
2635 assert!(received_responses.is_empty());
2636 }
2637
2638 #[test]
2640 fn test_ingest_frame_malformed_adu() {
2641 let transport = MockTransport::default();
2642 let app = MockApp::default();
2643 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2644 let mut client_services =
2645 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2646 client_services.connect().unwrap();
2647
2648 let malformed_adu = [0x01, 0x02, 0x03];
2650
2651 client_services
2652 .transport
2653 .recv_frames
2654 .borrow_mut()
2655 .push_back(Vec::from_slice(&malformed_adu).unwrap())
2656 .unwrap();
2657 client_services.poll();
2658
2659 let received_responses = client_services.app().received_coil_responses.borrow();
2660 assert!(received_responses.is_empty());
2661 }
2662
2663 #[test]
2665 fn test_ingest_frame_unknown_txn_id() {
2666 let transport = MockTransport::default();
2667 let app = MockApp::default();
2668 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2669 let mut client_services =
2670 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2671 client_services.connect().unwrap();
2672
2673 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2675
2676 client_services
2677 .transport
2678 .recv_frames
2679 .borrow_mut()
2680 .push_back(Vec::from_slice(&response_adu).unwrap())
2681 .unwrap();
2682 client_services.poll();
2683
2684 let received_responses = client_services.app().received_coil_responses.borrow();
2685 assert!(received_responses.is_empty());
2686 }
2687
2688 #[test]
2690 fn test_ingest_frame_pdu_parse_failure() {
2691 let transport = MockTransport::default();
2692 let app = MockApp::default();
2693 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2694 let mut client_services =
2695 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2696 client_services.connect().unwrap();
2697
2698 let txn_id = 0x0001;
2699 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2700 let address = 0x0000;
2701 let quantity = 8;
2702 client_services
2703 .read_multiple_coils(txn_id, unit_id, address, quantity) .unwrap();
2705
2706 let response_adu = [
2710 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x01, 0xB3, 0x00,
2711 ]; client_services
2714 .transport
2715 .recv_frames
2716 .borrow_mut()
2717 .push_back(Vec::from_slice(&response_adu).unwrap())
2718 .unwrap();
2719 client_services.poll();
2720
2721 let received_responses = client_services.app().received_coil_responses.borrow();
2722 assert!(received_responses.is_empty());
2723 assert!(client_services.expected_responses.is_empty());
2725 }
2726
2727 #[test]
2729 fn test_client_services_read_single_coil_e2e_success() {
2730 let transport = MockTransport::default();
2731 let app = MockApp::default();
2732 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2733 let mut client_services =
2734 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2735 client_services.connect().unwrap();
2736
2737 let txn_id = 0x0002;
2738 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2739 let address = 0x0005;
2740
2741 client_services .read_single_coil(txn_id, unit_id, address)
2744 .unwrap();
2745
2746 let sent_adu = client_services
2748 .transport
2749 .sent_frames
2750 .borrow_mut()
2751 .pop_front()
2752 .unwrap();
2753 #[rustfmt::skip]
2755 let expected_adu: [u8; 12] = [
2756 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x05, 0x00, 0x01, ];
2764 assert_eq!(sent_adu.as_slice(), &expected_adu);
2765
2766 let response_adu = [0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x01];
2770
2771 client_services
2773 .transport
2774 .recv_frames
2775 .borrow_mut()
2776 .push_back(Vec::from_slice(&response_adu).unwrap())
2777 .unwrap();
2778 client_services.poll();
2779
2780 let received_responses = client_services.app().received_coil_responses.borrow();
2782 assert_eq!(received_responses.len(), 1);
2783
2784 let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2785 let rcv_quantity = rcv_coils.quantity();
2786 assert_eq!(*rcv_txn_id, txn_id);
2787 assert_eq!(*rcv_unit_id, unit_id);
2788 assert_eq!(rcv_coils.from_address(), address);
2789 assert_eq!(rcv_coils.quantity(), 1); assert_eq!(&rcv_coils.values()[..1], &[0x01]); assert_eq!(rcv_quantity, 1);
2792
2793 assert!(client_services.expected_responses.is_empty());
2795 }
2796
2797 #[test]
2799 fn test_read_single_coil_request_sends_valid_adu() {
2800 let transport = MockTransport::default();
2801 let app = MockApp::default();
2802 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2803 let mut client_services =
2804 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2805 client_services.connect().unwrap();
2806
2807 let txn_id = 0x0002;
2808 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2809 let address = 0x0005;
2810
2811 client_services
2812 .read_single_coil(txn_id, unit_id, address) .unwrap();
2814
2815 let sent_frames = client_services.transport.sent_frames.borrow();
2816 assert_eq!(sent_frames.len(), 1);
2817 let sent_adu = sent_frames.front().unwrap();
2818
2819 #[rustfmt::skip]
2821 let expected_adu: [u8; 12] = [
2822 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x05, 0x00, 0x01, ];
2830 assert_eq!(sent_adu.as_slice(), &expected_adu);
2831
2832 assert_eq!(client_services.expected_responses.len(), 1); let single_read = client_services.expected_responses[0]
2835 .operation_meta
2836 .is_single();
2837 assert!(single_read);
2838 }
2839
2840 #[test]
2842 fn test_write_single_coil_sends_valid_adu() {
2843 let transport = MockTransport::default();
2844 let app = MockApp::default();
2845 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2846 let mut client_services =
2847 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2848 client_services.connect().unwrap();
2849
2850 let txn_id = 0x0003;
2851 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2852 let address = 0x000A;
2853 let value = true;
2854
2855 client_services
2856 .write_single_coil(txn_id, unit_id, address, value) .unwrap();
2858
2859 let sent_frames = client_services.transport.sent_frames.borrow();
2860 assert_eq!(sent_frames.len(), 1);
2861 let sent_adu = sent_frames.front().unwrap();
2862
2863 #[rustfmt::skip]
2865 let expected_adu: [u8; 12] = [
2866 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, ];
2874 assert_eq!(sent_adu.as_slice(), &expected_adu);
2875
2876 assert_eq!(client_services.expected_responses.len(), 1);
2878 let expected_address = client_services.expected_responses[0]
2879 .operation_meta
2880 .address();
2881 let expected_value = client_services.expected_responses[0].operation_meta.value() != 0;
2882
2883 assert_eq!(expected_address, address);
2884 assert_eq!(expected_value, value);
2885 }
2886
2887 #[test]
2889 fn test_client_services_write_single_coil_e2e_success() {
2890 let transport = MockTransport::default();
2891 let app = MockApp::default();
2892 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2893 let mut client_services =
2894 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2895 client_services.connect().unwrap();
2896
2897 let txn_id = 0x0003;
2898 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2899 let address = 0x000A;
2900 let value = true;
2901
2902 client_services .write_single_coil(txn_id, unit_id, address, value)
2905 .unwrap();
2906
2907 let sent_adu = client_services
2909 .transport
2910 .sent_frames
2911 .borrow_mut()
2912 .pop_front()
2913 .unwrap();
2914 #[rustfmt::skip]
2915 let expected_request_adu: [u8; 12] = [
2916 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, ];
2924 assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2925
2926 let response_adu = [
2929 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00,
2930 ];
2931
2932 client_services
2934 .transport
2935 .recv_frames
2936 .borrow_mut()
2937 .push_back(Vec::from_slice(&response_adu).unwrap())
2938 .unwrap();
2939 client_services.poll();
2940
2941 let received_responses = client_services
2943 .app
2944 .received_write_single_coil_responses
2945 .borrow();
2946 assert_eq!(received_responses.len(), 1);
2947
2948 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
2949 assert_eq!(*rcv_txn_id, txn_id);
2950 assert_eq!(*rcv_unit_id, unit_id);
2951 assert_eq!(*rcv_address, address);
2952 assert_eq!(*rcv_value, value);
2953
2954 assert!(client_services.expected_responses.is_empty());
2956 }
2957
2958 #[test]
2960 fn test_write_multiple_coils_sends_valid_adu() {
2961 let transport = MockTransport::default();
2962 let app = MockApp::default();
2963 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2964 let mut client_services =
2965 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2966 client_services.connect().unwrap();
2967
2968 let txn_id = 0x0004;
2969 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2970 let address = 0x0000;
2971 let quantity = 10;
2972
2973 let mut values = Coils::new(address, quantity).unwrap();
2975 for i in 0..quantity {
2976 values.set_value(address + i, i % 2 == 0).unwrap();
2977 }
2978
2979 client_services
2980 .write_multiple_coils(txn_id, unit_id, address, &values) .unwrap();
2982
2983 let sent_frames = client_services.transport.sent_frames.borrow();
2984 assert_eq!(sent_frames.len(), 1);
2985 let sent_adu = sent_frames.front().unwrap();
2986
2987 #[rustfmt::skip]
2989 let expected_adu: [u8; 15] = [
2990 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0x55, 0x01, ];
3000 assert_eq!(sent_adu.as_slice(), &expected_adu);
3001
3002 assert_eq!(client_services.expected_responses.len(), 1);
3004 let expected_address = client_services.expected_responses[0]
3005 .operation_meta
3006 .address();
3007 let expected_quantity = client_services.expected_responses[0]
3008 .operation_meta
3009 .quantity();
3010 assert_eq!(expected_address, address);
3011 assert_eq!(expected_quantity, quantity);
3012 }
3013
3014 #[test]
3016 fn test_client_services_write_multiple_coils_e2e_success() {
3017 let transport = MockTransport::default();
3018 let app = MockApp::default();
3019 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3020 let mut client_services =
3021 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3022 client_services.connect().unwrap();
3023
3024 let txn_id = 0x0004;
3025 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3026 let address = 0x0000;
3027 let quantity = 10;
3028
3029 let mut values = Coils::new(address, quantity).unwrap();
3031 for i in 0..quantity {
3032 values.set_value(address + i, i % 2 == 0).unwrap();
3033 }
3034
3035 client_services .write_multiple_coils(txn_id, unit_id, address, &values)
3038 .unwrap();
3039
3040 let sent_adu = client_services
3042 .transport
3043 .sent_frames
3044 .borrow_mut()
3045 .pop_front()
3046 .unwrap();
3047 #[rustfmt::skip]
3048 let expected_request_adu: [u8; 15] = [
3049 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0x55, 0x01, ];
3059 assert_eq!(sent_adu.as_slice(), &expected_request_adu);
3060
3061 let response_adu = [
3064 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A,
3065 ];
3066
3067 client_services
3069 .transport
3070 .recv_frames
3071 .borrow_mut()
3072 .push_back(Vec::from_slice(&response_adu).unwrap())
3073 .unwrap();
3074 client_services.poll();
3075
3076 let received_responses = client_services
3078 .app
3079 .received_write_multiple_coils_responses
3080 .borrow();
3081 assert_eq!(received_responses.len(), 1);
3082
3083 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
3084 assert_eq!(*rcv_txn_id, txn_id);
3085 assert_eq!(*rcv_unit_id, unit_id);
3086 assert_eq!(*rcv_address, address);
3087 assert_eq!(*rcv_quantity, quantity);
3088
3089 assert!(client_services.expected_responses.is_empty());
3091 }
3092
3093 #[test]
3095 fn test_client_services_read_coils_e2e_success() {
3096 let transport = MockTransport::default();
3097 let app = MockApp::default();
3098 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3099 let mut client_services =
3100 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3101 client_services.connect().unwrap();
3102
3103 let txn_id = 0x0001;
3104 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3105 let address = 0x0000;
3106 let quantity = 8;
3107 client_services
3108 .read_multiple_coils(txn_id, unit_id, address, quantity) .unwrap();
3110
3111 let sent_adu = client_services
3113 .transport
3114 .sent_frames
3115 .borrow_mut()
3116 .pop_front()
3117 .unwrap(); assert_eq!(
3120 sent_adu.as_slice(),
3121 &[
3122 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08
3123 ]
3124 );
3125
3126 assert_eq!(client_services.expected_responses.len(), 1); let from_address = client_services.expected_responses[0]
3129 .operation_meta
3130 .address();
3131 let expected_quantity = client_services.expected_responses[0]
3132 .operation_meta
3133 .quantity();
3134
3135 assert_eq!(expected_quantity, quantity);
3136 assert_eq!(from_address, address);
3137
3138 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
3142
3143 client_services
3145 .transport
3146 .recv_frames
3147 .borrow_mut()
3148 .push_back(Vec::from_slice(&response_adu).unwrap())
3149 .unwrap();
3150 client_services.poll(); let received_responses = client_services.app().received_coil_responses.borrow();
3156 assert_eq!(received_responses.len(), 1);
3157
3158 let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
3159 let rcv_quantity = rcv_coils.quantity();
3160 assert_eq!(*rcv_txn_id, txn_id);
3161 assert_eq!(*rcv_unit_id, unit_id);
3162 assert_eq!(rcv_coils.from_address(), address);
3163 assert_eq!(rcv_coils.quantity(), quantity);
3164 assert_eq!(&rcv_coils.values()[..1], &[0xB3]);
3165 assert_eq!(rcv_quantity, quantity);
3166
3167 assert!(client_services.expected_responses.is_empty());
3169 }
3170
3171 #[test]
3173 fn test_client_services_timeout_with_retry() {
3174 let transport = MockTransport::default();
3175 transport.recv_frames.borrow_mut().clear();
3177 let app = MockApp::default();
3178 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3179 tcp_config.response_timeout_ms = 100; tcp_config.retry_attempts = 1; let config = ModbusConfig::Tcp(tcp_config);
3182
3183 let mut client_services =
3184 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3185 client_services.connect().unwrap();
3186
3187 let txn_id = 0x0005;
3188 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3189 let address = 0x0000;
3190
3191 client_services
3192 .read_single_coil(txn_id, unit_id, address)
3193 .unwrap();
3194
3195 *client_services.app().current_time.borrow_mut() = 150;
3197 client_services.poll(); assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
3202 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;
3207 client_services.poll(); assert!(client_services.expected_responses.is_empty());
3212 }
3214
3215 #[test]
3217 fn test_client_services_concurrent_timeouts() {
3218 let transport = MockTransport::default();
3219 let app = MockApp::default();
3220
3221 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3223 tcp_config.response_timeout_ms = 100;
3224 tcp_config.retry_attempts = 1;
3225 let config = ModbusConfig::Tcp(tcp_config);
3226
3227 let mut client_services =
3228 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3229 client_services.connect().unwrap();
3230
3231 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3232
3233 client_services
3235 .read_single_coil(1, unit_id, 0x0000)
3236 .unwrap();
3237 client_services
3238 .read_single_coil(2, unit_id, 0x0001)
3239 .unwrap();
3240
3241 assert_eq!(client_services.expected_responses.len(), 2);
3243 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
3244
3245 *client_services.app().current_time.borrow_mut() = 150;
3247
3248 client_services.poll();
3250
3251 assert_eq!(client_services.expected_responses.len(), 2);
3253 assert_eq!(client_services.expected_responses[0].retries_left, 0);
3254 assert_eq!(client_services.expected_responses[1].retries_left, 0);
3255
3256 assert_eq!(client_services.transport.sent_frames.borrow().len(), 4);
3258
3259 *client_services.app().current_time.borrow_mut() = 300;
3261
3262 client_services.poll();
3264
3265 assert!(client_services.expected_responses.is_empty());
3267
3268 let failed_requests = client_services.app().failed_requests.borrow();
3270 assert_eq!(failed_requests.len(), 2);
3271
3272 let has_txn_1 = failed_requests
3274 .iter()
3275 .any(|(txn, _, err)| *txn == 1 && *err == MbusError::NoRetriesLeft);
3276 let has_txn_2 = failed_requests
3277 .iter()
3278 .any(|(txn, _, err)| *txn == 2 && *err == MbusError::NoRetriesLeft);
3279 assert!(has_txn_1, "Transaction 1 should have failed");
3280 assert!(has_txn_2, "Transaction 2 should have failed");
3281 }
3282
3283 #[test]
3284 fn test_poll_connection_loss_flushes_pending_requests() {
3285 let transport = MockTransport::default();
3286 let app = MockApp::default();
3287 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3288 let mut client_services =
3289 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3290 client_services.connect().unwrap();
3291
3292 let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
3293 client_services.read_single_coil(1, unit_id, 0).unwrap();
3294 client_services.read_single_coil(2, unit_id, 1).unwrap();
3295 assert_eq!(client_services.expected_responses.len(), 2);
3296
3297 *client_services.transport.is_connected_flag.borrow_mut() = false;
3298 *client_services.transport.recv_error.borrow_mut() = Some(MbusError::ConnectionClosed);
3299
3300 client_services.poll();
3301
3302 assert!(client_services.expected_responses.is_empty());
3303 assert_eq!(client_services.next_timeout_check, None);
3304
3305 let failed_requests = client_services.app().failed_requests.borrow();
3306 assert_eq!(failed_requests.len(), 2);
3307 assert!(
3308 failed_requests
3309 .iter()
3310 .all(|(txn, _, err)| (*txn == 1 || *txn == 2) && *err == MbusError::ConnectionLost)
3311 );
3312 }
3313
3314 #[test]
3315 fn test_fixed_backoff_schedules_and_does_not_retry_early() {
3316 let transport = MockTransport::default();
3317 let app = MockApp::default();
3318 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3319 tcp_config.response_timeout_ms = 100;
3320 tcp_config.retry_attempts = 1;
3321 tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 50 };
3322 let config = ModbusConfig::Tcp(tcp_config);
3323
3324 let mut client_services =
3325 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3326 client_services.connect().unwrap();
3327
3328 client_services
3329 .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3330 .unwrap();
3331 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
3332
3333 *client_services.app().current_time.borrow_mut() = 101;
3334 client_services.poll();
3335 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
3336 assert_eq!(
3337 client_services.expected_responses[0].next_retry_timestamp,
3338 Some(151)
3339 );
3340
3341 *client_services.app().current_time.borrow_mut() = 150;
3342 client_services.poll();
3343 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
3344
3345 *client_services.app().current_time.borrow_mut() = 151;
3346 client_services.poll();
3347 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
3348 }
3349
3350 #[test]
3351 fn test_exponential_backoff_growth() {
3352 let transport = MockTransport::default();
3353 let app = MockApp::default();
3354 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3355 tcp_config.response_timeout_ms = 100;
3356 tcp_config.retry_attempts = 2;
3357 tcp_config.retry_backoff_strategy = BackoffStrategy::Exponential {
3358 base_delay_ms: 50,
3359 max_delay_ms: 500,
3360 };
3361 let config = ModbusConfig::Tcp(tcp_config);
3362
3363 let mut client_services =
3364 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3365 client_services.connect().unwrap();
3366
3367 client_services
3368 .read_single_coil(7, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3369 .unwrap();
3370
3371 *client_services.app().current_time.borrow_mut() = 101;
3372 client_services.poll();
3373 assert_eq!(
3374 client_services.expected_responses[0].next_retry_timestamp,
3375 Some(151)
3376 );
3377
3378 *client_services.app().current_time.borrow_mut() = 151;
3379 client_services.poll();
3380 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
3381
3382 *client_services.app().current_time.borrow_mut() = 252;
3383 client_services.poll();
3384 assert_eq!(
3385 client_services.expected_responses[0].next_retry_timestamp,
3386 Some(352)
3387 );
3388
3389 *client_services.app().current_time.borrow_mut() = 352;
3390 client_services.poll();
3391 assert_eq!(client_services.transport.sent_frames.borrow().len(), 3);
3392 }
3393
3394 #[test]
3395 fn test_jitter_bounds_with_random_source_lower_bound() {
3396 let transport = MockTransport::default();
3397 let app = MockApp::default();
3398 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3399 tcp_config.response_timeout_ms = 100;
3400 tcp_config.retry_attempts = 1;
3401 tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
3402 tcp_config.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
3403 tcp_config.retry_random_fn = Some(rand_zero);
3404 let config = ModbusConfig::Tcp(tcp_config);
3405
3406 let mut client_services =
3407 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3408 client_services.connect().unwrap();
3409 client_services
3410 .read_single_coil(10, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3411 .unwrap();
3412
3413 *client_services.app().current_time.borrow_mut() = 101;
3414 client_services.poll();
3415 assert_eq!(
3416 client_services.expected_responses[0].next_retry_timestamp,
3417 Some(181)
3418 );
3419 }
3420
3421 #[test]
3422 fn test_jitter_bounds_with_random_source_upper_bound() {
3423 let transport3 = MockTransport::default();
3424 let app3 = MockApp::default();
3425 let mut tcp_config3 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3426 tcp_config3.response_timeout_ms = 100;
3427 tcp_config3.retry_attempts = 1;
3428 tcp_config3.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
3429 tcp_config3.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
3430 tcp_config3.retry_random_fn = Some(rand_upper_percent_20);
3431 let config3 = ModbusConfig::Tcp(tcp_config3);
3432
3433 let mut client_services3 =
3434 ClientServices::<MockTransport, MockApp, 10>::new(transport3, app3, config3).unwrap();
3435 client_services3.connect().unwrap();
3436 client_services3
3437 .read_single_coil(12, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3438 .unwrap();
3439
3440 *client_services3.app.current_time.borrow_mut() = 101;
3441 client_services3.poll();
3442 assert_eq!(
3443 client_services3.expected_responses[0].next_retry_timestamp,
3444 Some(221)
3445 );
3446 }
3447
3448 #[test]
3449 fn test_jitter_falls_back_without_random_source() {
3450 let transport2 = MockTransport::default();
3451 let app2 = MockApp::default();
3452 let mut tcp_config2 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3453 tcp_config2.response_timeout_ms = 100;
3454 tcp_config2.retry_attempts = 1;
3455 tcp_config2.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
3456 tcp_config2.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
3457 tcp_config2.retry_random_fn = None;
3458 let config2 = ModbusConfig::Tcp(tcp_config2);
3459
3460 let mut client_services2 =
3461 ClientServices::<MockTransport, MockApp, 10>::new(transport2, app2, config2).unwrap();
3462 client_services2.connect().unwrap();
3463 client_services2
3464 .read_single_coil(11, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3465 .unwrap();
3466
3467 *client_services2.app.current_time.borrow_mut() = 101;
3468 client_services2.poll();
3469 assert_eq!(
3470 client_services2.expected_responses[0].next_retry_timestamp,
3471 Some(201)
3472 );
3473 }
3474
3475 #[test]
3476 fn test_serial_retry_scheduling_uses_backoff() {
3477 let transport = MockTransport {
3478 transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
3479 ..Default::default()
3480 };
3481 let app = MockApp::default();
3482
3483 let serial_config = ModbusSerialConfig {
3484 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
3485 mode: SerialMode::Rtu,
3486 baud_rate: BaudRate::Baud9600,
3487 data_bits: mbus_core::transport::DataBits::Eight,
3488 stop_bits: 1,
3489 parity: Parity::None,
3490 response_timeout_ms: 100,
3491 retry_attempts: 1,
3492 retry_backoff_strategy: BackoffStrategy::Fixed { delay_ms: 25 },
3493 retry_jitter_strategy: JitterStrategy::None,
3494 retry_random_fn: None,
3495 };
3496
3497 let mut client_services = ClientServices::<MockTransport, MockApp, 1>::new(
3498 transport,
3499 app,
3500 ModbusConfig::Serial(serial_config),
3501 )
3502 .unwrap();
3503 client_services.connect().unwrap();
3504
3505 client_services
3506 .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3507 .unwrap();
3508
3509 *client_services.app().current_time.borrow_mut() = 101;
3510 client_services.poll();
3511 assert_eq!(
3512 client_services.expected_responses[0].next_retry_timestamp,
3513 Some(126)
3514 );
3515
3516 *client_services.app().current_time.borrow_mut() = 126;
3517 client_services.poll();
3518 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
3519 }
3520
3521 #[test]
3523 fn test_too_many_requests_error() {
3524 let transport = MockTransport::default();
3525 let app = MockApp::default();
3526 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3527 let mut client_services =
3529 ClientServices::<MockTransport, MockApp, 1>::new(transport, app, config).unwrap();
3530 client_services.connect().unwrap();
3531
3532 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3533 client_services
3535 .read_multiple_coils(1, unit_id, 0, 1)
3536 .unwrap();
3537 assert_eq!(client_services.expected_responses.len(), 1);
3538
3539 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3540 let result = client_services.read_multiple_coils(2, unit_id, 0, 1);
3542 assert!(result.is_err());
3543 assert_eq!(result.unwrap_err(), MbusError::TooManyRequests);
3544 assert_eq!(client_services.expected_responses.len(), 1); }
3546
3547 #[test]
3549 fn test_read_holding_registers_sends_valid_adu() {
3550 let transport = MockTransport::default();
3551 let app = MockApp::default();
3552 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3553 let mut client_services =
3554 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3555 client_services.connect().unwrap();
3556
3557 let txn_id = 0x0005;
3558 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3559 let address = 0x0000;
3560 let quantity = 2;
3561 client_services
3562 .read_holding_registers(txn_id, unit_id, address, quantity)
3563 .unwrap();
3564
3565 let sent_frames = client_services.transport.sent_frames.borrow();
3566 assert_eq!(sent_frames.len(), 1);
3567 let sent_adu = sent_frames.front().unwrap();
3568
3569 #[rustfmt::skip]
3571 let expected_adu: [u8; 12] = [
3572 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, ];
3580 assert_eq!(sent_adu.as_slice(), &expected_adu);
3581 }
3582
3583 #[test]
3585 fn test_client_services_read_holding_registers_e2e_success() {
3586 let transport = MockTransport::default();
3587 let app = MockApp::default();
3588 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3589 let mut client_services =
3590 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3591 client_services.connect().unwrap();
3592
3593 let txn_id = 0x0005;
3594 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3595 let address = 0x0000;
3596 let quantity = 2;
3597 client_services
3598 .read_holding_registers(txn_id, unit_id, address, quantity)
3599 .unwrap();
3600
3601 let response_adu = [
3604 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x01, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78,
3605 ];
3606 client_services
3607 .transport
3608 .recv_frames
3609 .borrow_mut()
3610 .push_back(Vec::from_slice(&response_adu).unwrap())
3611 .unwrap();
3612 client_services.poll();
3613
3614 let received_responses = client_services
3615 .app
3616 .received_holding_register_responses
3617 .borrow();
3618 assert_eq!(received_responses.len(), 1);
3619 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3620 assert_eq!(*rcv_txn_id, txn_id);
3621 assert_eq!(*rcv_unit_id, unit_id);
3622 assert_eq!(rcv_registers.from_address(), address);
3623 assert_eq!(rcv_registers.quantity(), quantity);
3624 assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3625 assert_eq!(*rcv_quantity, quantity);
3626 assert!(client_services.expected_responses.is_empty());
3627 }
3628
3629 #[test]
3631 fn test_read_input_registers_sends_valid_adu() {
3632 let transport = MockTransport::default();
3633 let app = MockApp::default();
3634 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3635 let mut client_services =
3636 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3637 client_services.connect().unwrap();
3638
3639 let txn_id = 0x0006;
3640 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3641 let address = 0x0000;
3642 let quantity = 2;
3643 client_services
3644 .read_input_registers(txn_id, unit_id, address, quantity)
3645 .unwrap();
3646
3647 let sent_frames = client_services.transport.sent_frames.borrow();
3648 assert_eq!(sent_frames.len(), 1);
3649 let sent_adu = sent_frames.front().unwrap();
3650
3651 #[rustfmt::skip]
3653 let expected_adu: [u8; 12] = [
3654 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x00, 0x00, 0x02, ];
3662 assert_eq!(sent_adu.as_slice(), &expected_adu);
3663 }
3664
3665 #[test]
3667 fn test_client_services_read_input_registers_e2e_success() {
3668 let transport = MockTransport::default();
3669 let app = MockApp::default();
3670 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3671 let mut client_services =
3672 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3673 client_services.connect().unwrap();
3674
3675 let txn_id = 0x0006;
3676 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3677 let address = 0x0000;
3678 let quantity = 2;
3679 client_services
3680 .read_input_registers(txn_id, unit_id, address, quantity)
3681 .unwrap();
3682
3683 let response_adu = [
3686 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x01, 0x04, 0x04, 0xAA, 0xBB, 0xCC, 0xDD,
3687 ];
3688 client_services
3689 .transport
3690 .recv_frames
3691 .borrow_mut()
3692 .push_back(Vec::from_slice(&response_adu).unwrap())
3693 .unwrap();
3694 client_services.poll();
3695
3696 let received_responses = client_services
3697 .app
3698 .received_input_register_responses
3699 .borrow();
3700 assert_eq!(received_responses.len(), 1);
3701 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3702 assert_eq!(*rcv_txn_id, txn_id);
3703 assert_eq!(*rcv_unit_id, unit_id);
3704 assert_eq!(rcv_registers.from_address(), address);
3705 assert_eq!(rcv_registers.quantity(), quantity);
3706 assert_eq!(&rcv_registers.values()[..2], &[0xAABB, 0xCCDD]);
3707 assert_eq!(*rcv_quantity, quantity);
3708 assert!(client_services.expected_responses.is_empty());
3709 }
3710
3711 #[test]
3713 fn test_write_single_register_sends_valid_adu() {
3714 let transport = MockTransport::default();
3715 let app = MockApp::default();
3716 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3717 let mut client_services =
3718 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3719 client_services.connect().unwrap();
3720
3721 let txn_id = 0x0007;
3722 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3723 let address = 0x0001;
3724 let value = 0x1234;
3725 client_services
3726 .write_single_register(txn_id, unit_id, address, value)
3727 .unwrap();
3728
3729 let sent_frames = client_services.transport.sent_frames.borrow();
3730 assert_eq!(sent_frames.len(), 1);
3731 let sent_adu = sent_frames.front().unwrap();
3732
3733 #[rustfmt::skip]
3735 let expected_adu: [u8; 12] = [
3736 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34, ];
3744 assert_eq!(sent_adu.as_slice(), &expected_adu);
3745 }
3746
3747 #[test]
3749 fn test_client_services_write_single_register_e2e_success() {
3750 let transport = MockTransport::default();
3751 let app = MockApp::default();
3752 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3753 let mut client_services =
3754 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3755 client_services.connect().unwrap();
3756
3757 let txn_id = 0x0007;
3758 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3759 let address = 0x0001;
3760 let value = 0x1234;
3761 client_services
3762 .write_single_register(txn_id, unit_id, address, value)
3763 .unwrap();
3764
3765 let response_adu = [
3768 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34,
3769 ];
3770 client_services
3771 .transport
3772 .recv_frames
3773 .borrow_mut()
3774 .push_back(Vec::from_slice(&response_adu).unwrap())
3775 .unwrap();
3776 client_services.poll();
3777
3778 let received_responses = client_services
3779 .app
3780 .received_write_single_register_responses
3781 .borrow();
3782 assert_eq!(received_responses.len(), 1);
3783 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
3784 assert_eq!(*rcv_txn_id, txn_id);
3785 assert_eq!(*rcv_unit_id, unit_id);
3786 assert_eq!(*rcv_address, address);
3787 assert_eq!(*rcv_value, value);
3788 assert!(client_services.expected_responses.is_empty());
3789 }
3790
3791 #[test]
3793 fn test_write_multiple_registers_sends_valid_adu() {
3794 let transport = MockTransport::default();
3795 let app = MockApp::default();
3796 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3797 let mut client_services =
3798 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3799 client_services.connect().unwrap();
3800
3801 let txn_id = 0x0008;
3802 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3803 let address = 0x0001;
3804 let quantity = 2;
3805 let values = [0x1234, 0x5678];
3806 client_services
3807 .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3808 .unwrap();
3809
3810 let sent_frames = client_services.transport.sent_frames.borrow();
3811 assert_eq!(sent_frames.len(), 1);
3812 let sent_adu = sent_frames.front().unwrap();
3813
3814 #[rustfmt::skip]
3816 let expected_adu: [u8; 17] = [ 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x34, 0x56, 0x78, ];
3827 assert_eq!(sent_adu.as_slice(), &expected_adu);
3828 }
3829
3830 #[test]
3832 fn test_client_services_write_multiple_registers_e2e_success() {
3833 let transport = MockTransport::default();
3834 let app = MockApp::default();
3835 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3836 let mut client_services =
3837 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3838 client_services.connect().unwrap();
3839
3840 let txn_id = 0x0008;
3841 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3842 let address = 0x0001;
3843 let quantity = 2;
3844 let values = [0x1234, 0x5678];
3845 client_services
3846 .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3847 .unwrap();
3848
3849 let response_adu = [
3852 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02,
3853 ];
3854 client_services
3855 .transport
3856 .recv_frames
3857 .borrow_mut()
3858 .push_back(Vec::from_slice(&response_adu).unwrap())
3859 .unwrap();
3860 client_services.poll();
3861
3862 let received_responses = client_services
3863 .app
3864 .received_write_multiple_register_responses
3865 .borrow();
3866 assert_eq!(received_responses.len(), 1);
3867 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
3868 assert_eq!(*rcv_txn_id, txn_id);
3869 assert_eq!(*rcv_unit_id, unit_id);
3870 assert_eq!(*rcv_address, address);
3871 assert_eq!(*rcv_quantity, quantity);
3872 assert!(client_services.expected_responses.is_empty());
3873 }
3874
3875 #[test]
3877 fn test_client_services_handles_exception_response() {
3878 let transport = MockTransport::default();
3879 let app = MockApp::default();
3880 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3881 let mut client_services =
3882 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3883 client_services.connect().unwrap();
3884
3885 let txn_id = 0x0009;
3886 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3887 let address = 0x0000;
3888 let quantity = 1;
3889
3890 client_services
3891 .read_holding_registers(txn_id, unit_id, address, quantity)
3892 .unwrap();
3893
3894 let exception_adu = [
3897 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x01, 0x83, 0x02, ];
3904 client_services
3905 .transport
3906 .recv_frames
3907 .borrow_mut()
3908 .push_back(Vec::from_slice(&exception_adu).unwrap())
3909 .unwrap();
3910 client_services.poll();
3911
3912 assert!(
3914 client_services
3915 .app
3916 .received_holding_register_responses
3917 .borrow()
3918 .is_empty()
3919 );
3920 assert_eq!(client_services.app().failed_requests.borrow().len(), 1);
3922 let (failed_txn, failed_unit, failed_err) =
3923 &client_services.app().failed_requests.borrow()[0];
3924 assert_eq!(*failed_txn, txn_id);
3925 assert_eq!(*failed_unit, unit_id);
3926 assert_eq!(*failed_err, MbusError::ModbusException(0x02));
3927 }
3928
3929 #[test]
3930 fn test_serial_exception_coil_response_fails_immediately_with_request_txn_id() {
3931 let mut client_services = make_serial_client();
3932
3933 let txn_id = 0x2001;
3934 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3935 let mut values = Coils::new(0x0000, 10).unwrap();
3936 values.set_value(0x0000, true).unwrap();
3937 values.set_value(0x0001, false).unwrap();
3938 values.set_value(0x0002, true).unwrap();
3939 values.set_value(0x0003, false).unwrap();
3940 values.set_value(0x0004, true).unwrap();
3941 values.set_value(0x0005, false).unwrap();
3942 values.set_value(0x0006, true).unwrap();
3943 values.set_value(0x0007, false).unwrap();
3944 values.set_value(0x0008, true).unwrap();
3945 values.set_value(0x0009, false).unwrap();
3946
3947 client_services
3948 .write_multiple_coils(txn_id, unit_id, 0x0000, &values)
3949 .unwrap();
3950
3951 let exception_adu = make_rtu_exception_adu(unit_id, 0x0F, 0x01);
3952 client_services
3953 .transport
3954 .recv_frames
3955 .borrow_mut()
3956 .push_back(exception_adu)
3957 .unwrap();
3958
3959 client_services.poll();
3960
3961 let failed = client_services.app().failed_requests.borrow();
3962 assert_eq!(failed.len(), 1);
3963 assert_eq!(failed[0].0, txn_id);
3964 assert_eq!(failed[0].1, unit_id);
3965 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
3966 assert!(
3967 client_services
3968 .app
3969 .received_write_multiple_coils_responses
3970 .borrow()
3971 .is_empty()
3972 );
3973 assert!(client_services.expected_responses.is_empty());
3974 }
3975
3976 #[test]
3977 fn test_serial_exception_register_response_fails_immediately_with_request_txn_id() {
3978 let mut client_services = make_serial_client();
3979
3980 let txn_id = 0x2002;
3981 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3982 client_services
3983 .read_holding_registers(txn_id, unit_id, 0x0000, 1)
3984 .unwrap();
3985
3986 let exception_adu = make_rtu_exception_adu(unit_id, 0x03, 0x02);
3987 client_services
3988 .transport
3989 .recv_frames
3990 .borrow_mut()
3991 .push_back(exception_adu)
3992 .unwrap();
3993
3994 client_services.poll();
3995
3996 let failed = client_services.app().failed_requests.borrow();
3997 assert_eq!(failed.len(), 1);
3998 assert_eq!(failed[0].0, txn_id);
3999 assert_eq!(failed[0].1, unit_id);
4000 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
4001 assert!(
4002 client_services
4003 .app
4004 .received_holding_register_responses
4005 .borrow()
4006 .is_empty()
4007 );
4008 assert!(client_services.expected_responses.is_empty());
4009 }
4010
4011 #[test]
4012 fn test_serial_exception_discrete_input_response_fails_immediately_with_request_txn_id() {
4013 let mut client_services = make_serial_client();
4014
4015 let txn_id = 0x2003;
4016 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4017 client_services
4018 .read_discrete_inputs(txn_id, unit_id, 0x0000, 8)
4019 .unwrap();
4020
4021 let exception_adu = make_rtu_exception_adu(unit_id, 0x02, 0x02);
4022 client_services
4023 .transport
4024 .recv_frames
4025 .borrow_mut()
4026 .push_back(exception_adu)
4027 .unwrap();
4028
4029 client_services.poll();
4030
4031 let failed = client_services.app().failed_requests.borrow();
4032 assert_eq!(failed.len(), 1);
4033 assert_eq!(failed[0].0, txn_id);
4034 assert_eq!(failed[0].1, unit_id);
4035 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
4036 assert!(
4037 client_services
4038 .app
4039 .received_discrete_input_responses
4040 .borrow()
4041 .is_empty()
4042 );
4043 assert!(client_services.expected_responses.is_empty());
4044 }
4045
4046 #[test]
4047 fn test_serial_exception_fifo_response_fails_immediately_with_request_txn_id() {
4048 let mut client_services = make_serial_client();
4049
4050 let txn_id = 0x2004;
4051 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4052 client_services
4053 .read_fifo_queue(txn_id, unit_id, 0x0001)
4054 .unwrap();
4055
4056 let exception_adu = make_rtu_exception_adu(unit_id, 0x18, 0x01);
4057 client_services
4058 .transport
4059 .recv_frames
4060 .borrow_mut()
4061 .push_back(exception_adu)
4062 .unwrap();
4063
4064 client_services.poll();
4065
4066 let failed = client_services.app().failed_requests.borrow();
4067 assert_eq!(failed.len(), 1);
4068 assert_eq!(failed[0].0, txn_id);
4069 assert_eq!(failed[0].1, unit_id);
4070 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
4071 assert!(
4072 client_services
4073 .app
4074 .received_read_fifo_queue_responses
4075 .borrow()
4076 .is_empty()
4077 );
4078 assert!(client_services.expected_responses.is_empty());
4079 }
4080
4081 #[test]
4082 fn test_serial_exception_file_record_response_fails_immediately_with_request_txn_id() {
4083 let mut client_services = make_serial_client();
4084
4085 let txn_id = 0x2005;
4086 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4087 let mut sub_req = SubRequest::new();
4088 sub_req.add_read_sub_request(4, 1, 2).unwrap();
4089 client_services
4090 .read_file_record(txn_id, unit_id, &sub_req)
4091 .unwrap();
4092
4093 let exception_adu = make_rtu_exception_adu(unit_id, 0x14, 0x02);
4094 client_services
4095 .transport
4096 .recv_frames
4097 .borrow_mut()
4098 .push_back(exception_adu)
4099 .unwrap();
4100
4101 client_services.poll();
4102
4103 let failed = client_services.app().failed_requests.borrow();
4104 assert_eq!(failed.len(), 1);
4105 assert_eq!(failed[0].0, txn_id);
4106 assert_eq!(failed[0].1, unit_id);
4107 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
4108 assert!(
4109 client_services
4110 .app
4111 .received_read_file_record_responses
4112 .borrow()
4113 .is_empty()
4114 );
4115 assert!(client_services.expected_responses.is_empty());
4116 }
4117
4118 #[test]
4119 fn test_serial_exception_diagnostic_response_fails_immediately_with_request_txn_id() {
4120 let mut client_services = make_serial_client();
4121
4122 let txn_id = 0x2006;
4123 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4124 client_services
4125 .read_device_identification(
4126 txn_id,
4127 unit_id,
4128 ReadDeviceIdCode::Basic,
4129 ObjectId::from(0x00),
4130 )
4131 .unwrap();
4132
4133 let exception_adu = make_rtu_exception_adu(unit_id, 0x2B, 0x01);
4134 client_services
4135 .transport
4136 .recv_frames
4137 .borrow_mut()
4138 .push_back(exception_adu)
4139 .unwrap();
4140
4141 client_services.poll();
4142
4143 let failed = client_services.app().failed_requests.borrow();
4144 assert_eq!(failed.len(), 1);
4145 assert_eq!(failed[0].0, txn_id);
4146 assert_eq!(failed[0].1, unit_id);
4147 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
4148 assert!(
4149 client_services
4150 .app
4151 .received_read_device_id_responses
4152 .borrow()
4153 .is_empty()
4154 );
4155 assert!(client_services.expected_responses.is_empty());
4156 }
4157
4158 #[test]
4160 fn test_read_single_holding_register_sends_valid_adu() {
4161 let transport = MockTransport::default();
4162 let app = MockApp::default();
4163 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4164 let mut client_services =
4165 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4166 client_services.connect().unwrap();
4167
4168 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4169 client_services
4170 .read_single_holding_register(10, unit_id, 100)
4171 .unwrap();
4172
4173 let sent_frames = client_services.transport.sent_frames.borrow();
4174 assert_eq!(sent_frames.len(), 1);
4175 let sent_adu = sent_frames.front().unwrap();
4176
4177 #[rustfmt::skip]
4178 let expected_adu: [u8; 12] = [
4179 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x64, 0x00, 0x01, ];
4187 assert_eq!(sent_adu.as_slice(), &expected_adu);
4188
4189 assert_eq!(client_services.expected_responses.len(), 1);
4191 let single_read = client_services.expected_responses[0]
4192 .operation_meta
4193 .is_single();
4194 assert!(single_read);
4195 }
4196
4197 #[test]
4199 fn test_client_services_read_single_holding_register_e2e_success() {
4200 let transport = MockTransport::default();
4201 let app = MockApp::default();
4202 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4203 let mut client_services =
4204 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4205 client_services.connect().unwrap();
4206
4207 let txn_id = 10;
4208 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4209 let address = 100;
4210
4211 client_services
4212 .read_single_holding_register(txn_id, unit_id, address)
4213 .unwrap();
4214
4215 let response_adu = [
4217 0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x03, 0x02, 0x12, 0x34,
4218 ];
4219 client_services
4220 .transport
4221 .recv_frames
4222 .borrow_mut()
4223 .push_back(Vec::from_slice(&response_adu).unwrap())
4224 .unwrap();
4225 client_services.poll();
4226
4227 let received_responses = client_services
4228 .app
4229 .received_holding_register_responses
4230 .borrow();
4231 assert_eq!(received_responses.len(), 1);
4232 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
4233 assert_eq!(*rcv_txn_id, txn_id);
4234 assert_eq!(*rcv_unit_id, unit_id);
4235 assert_eq!(rcv_registers.from_address(), address);
4236 assert_eq!(rcv_registers.quantity(), 1);
4237 assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
4238 assert_eq!(*rcv_quantity, 1);
4239 }
4240
4241 #[test]
4243 fn test_read_single_input_register_sends_valid_adu() {
4244 let transport = MockTransport::default();
4245 let app = MockApp::default();
4246 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4247 let mut client_services =
4248 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4249 client_services.connect().unwrap();
4250
4251 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4252 client_services
4253 .read_single_input_register(10, unit_id, 100)
4254 .unwrap();
4255
4256 let sent_frames = client_services.transport.sent_frames.borrow();
4257 assert_eq!(sent_frames.len(), 1);
4258 let sent_adu = sent_frames.front().unwrap();
4259
4260 #[rustfmt::skip]
4261 let expected_adu: [u8; 12] = [
4262 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x64, 0x00, 0x01, ];
4270 assert_eq!(sent_adu.as_slice(), &expected_adu);
4271
4272 assert_eq!(client_services.expected_responses.len(), 1);
4274 let single_read = client_services.expected_responses[0]
4275 .operation_meta
4276 .is_single();
4277 assert!(single_read);
4278 }
4279
4280 #[test]
4282 fn test_client_services_read_single_input_register_e2e_success() {
4283 let transport = MockTransport::default();
4284 let app = MockApp::default();
4285 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4286 let mut client_services =
4287 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4288 client_services.connect().unwrap();
4289
4290 let txn_id = 10;
4291 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4292 let address = 100;
4293
4294 client_services
4295 .read_single_input_register(txn_id, unit_id, address)
4296 .unwrap();
4297
4298 let response_adu = [
4301 0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x04, 0x02, 0x12, 0x34,
4302 ];
4303 client_services
4304 .transport
4305 .recv_frames
4306 .borrow_mut()
4307 .push_back(Vec::from_slice(&response_adu).unwrap())
4308 .unwrap();
4309 client_services.poll();
4310
4311 let received_responses = client_services
4312 .app
4313 .received_input_register_responses
4314 .borrow();
4315 assert_eq!(received_responses.len(), 1);
4316 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
4317 assert_eq!(*rcv_txn_id, txn_id);
4318 assert_eq!(*rcv_unit_id, unit_id);
4319 assert_eq!(rcv_registers.from_address(), address);
4320 assert_eq!(rcv_registers.quantity(), 1);
4321 assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
4322 assert_eq!(*rcv_quantity, 1);
4323 }
4324
4325 #[test]
4327 fn test_read_write_multiple_registers_sends_valid_adu() {
4328 let transport = MockTransport::default();
4329 let app = MockApp::default();
4330 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4331 let mut client_services =
4332 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4333 client_services.connect().unwrap();
4334
4335 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4336 let write_values = [0xAAAA, 0xBBBB];
4337 client_services
4338 .read_write_multiple_registers(11, unit_id, 10, 2, 20, &write_values)
4339 .unwrap();
4340
4341 let sent_frames = client_services.transport.sent_frames.borrow();
4342 assert_eq!(sent_frames.len(), 1);
4343 let sent_adu = sent_frames.front().unwrap();
4344
4345 #[rustfmt::skip]
4346 let expected_adu: [u8; 21] = [
4347 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x17, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x14, 0x00, 0x02, 0x04, 0xAA, 0xAA, 0xBB, 0xBB, ];
4360 assert_eq!(sent_adu.as_slice(), &expected_adu);
4361 }
4362
4363 #[test]
4365 fn test_mask_write_register_sends_valid_adu() {
4366 let transport = MockTransport::default();
4367 let app = MockApp::default();
4368 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4369 let mut client_services =
4370 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4371 client_services.connect().unwrap();
4372
4373 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4374 client_services
4375 .mask_write_register(12, unit_id, 30, 0xF0F0, 0x0F0F)
4376 .unwrap();
4377
4378 let sent_frames = client_services.transport.sent_frames.borrow();
4379 assert_eq!(sent_frames.len(), 1);
4380 let sent_adu = sent_frames.front().unwrap();
4381
4382 #[rustfmt::skip]
4383 let expected_adu: [u8; 14] = [
4384 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F, ];
4393 assert_eq!(sent_adu.as_slice(), &expected_adu);
4394 }
4395
4396 #[test]
4398 fn test_client_services_read_write_multiple_registers_e2e_success() {
4399 let transport = MockTransport::default();
4400 let app = MockApp::default();
4401 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4402 let mut client_services =
4403 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4404 client_services.connect().unwrap();
4405
4406 let txn_id = 11;
4407 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4408 let read_address = 10;
4409 let read_quantity = 2;
4410 let write_address = 20;
4411 let write_values = [0xAAAA, 0xBBBB];
4412
4413 client_services
4414 .read_write_multiple_registers(
4415 txn_id,
4416 unit_id,
4417 read_address,
4418 read_quantity,
4419 write_address,
4420 &write_values,
4421 )
4422 .unwrap();
4423
4424 let response_adu = [
4426 0x00, 0x0B, 0x00, 0x00, 0x00, 0x07, 0x01, 0x17, 0x04, 0x12, 0x34, 0x56, 0x78,
4427 ];
4428 client_services
4429 .transport
4430 .recv_frames
4431 .borrow_mut()
4432 .push_back(Vec::from_slice(&response_adu).unwrap())
4433 .unwrap();
4434 client_services.poll();
4435
4436 let received_responses = client_services
4437 .app
4438 .received_read_write_multiple_registers_responses
4439 .borrow();
4440 assert_eq!(received_responses.len(), 1);
4441 let (rcv_txn_id, rcv_unit_id, rcv_registers) = &received_responses[0];
4442 assert_eq!(*rcv_txn_id, txn_id);
4443 assert_eq!(*rcv_unit_id, unit_id);
4444 assert_eq!(rcv_registers.from_address(), read_address);
4445 assert_eq!(rcv_registers.quantity(), read_quantity);
4446 assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
4447 }
4448
4449 #[test]
4451 fn test_client_services_mask_write_register_e2e_success() {
4452 let transport = MockTransport::default();
4453 let app = MockApp::default();
4454 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4455 let mut client_services =
4456 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4457 client_services.connect().unwrap();
4458
4459 let txn_id = 12;
4460 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4461 let address = 30;
4462 let and_mask = 0xF0F0;
4463 let or_mask = 0x0F0F;
4464
4465 client_services
4466 .mask_write_register(txn_id, unit_id, address, and_mask, or_mask)
4467 .unwrap();
4468
4469 let response_adu = [
4471 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F,
4472 ];
4473 client_services
4474 .transport
4475 .recv_frames
4476 .borrow_mut()
4477 .push_back(Vec::from_slice(&response_adu).unwrap())
4478 .unwrap();
4479 client_services.poll();
4480
4481 let received_responses = client_services
4482 .app
4483 .received_mask_write_register_responses
4484 .borrow();
4485 assert_eq!(received_responses.len(), 1);
4486 let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
4487 assert_eq!(*rcv_txn_id, txn_id);
4488 assert_eq!(*rcv_unit_id, unit_id);
4489 }
4490
4491 #[test]
4493 fn test_client_services_read_fifo_queue_e2e_success() {
4494 let transport = MockTransport::default();
4495 let app = MockApp::default();
4496 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4497 let mut client_services =
4498 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4499 client_services.connect().unwrap();
4500
4501 let txn_id = 13;
4502 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4503 let address = 40;
4504
4505 client_services
4506 .read_fifo_queue(txn_id, unit_id, address)
4507 .unwrap();
4508
4509 #[rustfmt::skip]
4511 let response_adu = [
4512 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x18, 0x00, 0x06, 0x00, 0x02, 0xAA, 0xAA, 0xBB, 0xBB, ];
4522 client_services
4523 .transport
4524 .recv_frames
4525 .borrow_mut()
4526 .push_back(Vec::from_slice(&response_adu).unwrap())
4527 .unwrap();
4528 client_services.poll();
4529
4530 let received_responses = client_services
4531 .app
4532 .received_read_fifo_queue_responses
4533 .borrow();
4534 assert_eq!(received_responses.len(), 1);
4535 let (rcv_txn_id, rcv_unit_id, rcv_fifo_queue) = &received_responses[0];
4536 assert_eq!(*rcv_txn_id, txn_id);
4537 assert_eq!(*rcv_unit_id, unit_id);
4538 assert_eq!(rcv_fifo_queue.length(), 2);
4539 assert_eq!(&rcv_fifo_queue.queue()[..2], &[0xAAAA, 0xBBBB]);
4540 }
4541
4542 #[test]
4544 fn test_client_services_read_file_record_e2e_success() {
4545 let transport = MockTransport::default();
4546 let app = MockApp::default();
4547 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4548 let mut client_services =
4549 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4550 client_services.connect().unwrap();
4551
4552 let txn_id = 14;
4553 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4554 let mut sub_req = SubRequest::new();
4555 sub_req.add_read_sub_request(4, 1, 2).unwrap();
4556
4557 client_services
4558 .read_file_record(txn_id, unit_id, &sub_req)
4559 .unwrap();
4560
4561 let response_adu = [
4567 0x00, 0x0E, 0x00, 0x00, 0x00, 0x09, 0x01, 0x14, 0x06, 0x05, 0x06, 0x12, 0x34, 0x56,
4568 0x78,
4569 ];
4570
4571 client_services
4572 .transport
4573 .recv_frames
4574 .borrow_mut()
4575 .push_back(Vec::from_slice(&response_adu).unwrap())
4576 .unwrap();
4577 client_services.poll();
4578
4579 let received_responses = client_services
4580 .app
4581 .received_read_file_record_responses
4582 .borrow();
4583 assert_eq!(received_responses.len(), 1);
4584 let (rcv_txn_id, rcv_unit_id, rcv_data) = &received_responses[0];
4585 assert_eq!(*rcv_txn_id, txn_id);
4586 assert_eq!(*rcv_unit_id, unit_id);
4587 assert_eq!(rcv_data.len(), 1);
4588 assert_eq!(
4589 rcv_data[0].record_data.as_ref().unwrap().as_slice(),
4590 &[0x1234, 0x5678]
4591 );
4592 }
4593
4594 #[test]
4596 fn test_client_services_write_file_record_e2e_success() {
4597 let transport = MockTransport::default();
4598 let app = MockApp::default();
4599 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4600 let mut client_services =
4601 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4602 client_services.connect().unwrap();
4603
4604 let txn_id = 15;
4605 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4606 let mut sub_req = SubRequest::new();
4607 let mut data = Vec::new();
4608 data.push(0x1122).unwrap();
4609 sub_req.add_write_sub_request(4, 1, 1, data).unwrap();
4610
4611 client_services
4612 .write_file_record(txn_id, unit_id, &sub_req)
4613 .unwrap();
4614
4615 let response_adu = [
4618 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0C, 0x01, 0x15, 0x09, 0x06, 0x00, 0x04, 0x00, 0x01,
4619 0x00, 0x01, 0x11, 0x22,
4620 ];
4621
4622 client_services
4623 .transport
4624 .recv_frames
4625 .borrow_mut()
4626 .push_back(Vec::from_slice(&response_adu).unwrap())
4627 .unwrap();
4628 client_services.poll();
4629
4630 let received_responses = client_services
4631 .app
4632 .received_write_file_record_responses
4633 .borrow();
4634 assert_eq!(received_responses.len(), 1);
4635 let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
4636 assert_eq!(*rcv_txn_id, txn_id);
4637 assert_eq!(*rcv_unit_id, unit_id);
4638 }
4639
4640 #[test]
4642 fn test_client_services_read_discrete_inputs_e2e_success() {
4643 let transport = MockTransport::default();
4644 let app = MockApp::default();
4645 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4646 let mut client_services =
4647 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4648 client_services.connect().unwrap();
4649
4650 let txn_id = 16;
4651 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4652 let address = 50;
4653 let quantity = 8;
4654
4655 client_services
4656 .read_discrete_inputs(txn_id, unit_id, address, quantity)
4657 .unwrap();
4658
4659 let response_adu = [0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0xAA];
4661
4662 client_services
4663 .transport
4664 .recv_frames
4665 .borrow_mut()
4666 .push_back(Vec::from_slice(&response_adu).unwrap())
4667 .unwrap();
4668 client_services.poll();
4669
4670 let received_responses = client_services
4671 .app
4672 .received_discrete_input_responses
4673 .borrow();
4674 assert_eq!(received_responses.len(), 1);
4675 let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
4676 assert_eq!(*rcv_txn_id, txn_id);
4677 assert_eq!(*rcv_unit_id, unit_id);
4678 assert_eq!(rcv_inputs.from_address(), address);
4679 assert_eq!(rcv_inputs.quantity(), quantity);
4680 assert_eq!(rcv_inputs.values(), &[0xAA]);
4681 assert_eq!(*rcv_quantity, quantity);
4682 }
4683
4684 #[test]
4686 fn test_client_services_read_single_discrete_input_e2e_success() {
4687 let transport = MockTransport::default();
4688 let app = MockApp::default();
4689 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4690 let mut client_services =
4691 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4692 client_services.connect().unwrap();
4693
4694 let txn_id = 17;
4695 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4696 let address = 10;
4697
4698 client_services
4699 .read_single_discrete_input(txn_id, unit_id, address)
4700 .unwrap();
4701
4702 let sent_frames = client_services.transport.sent_frames.borrow();
4704 assert_eq!(sent_frames.len(), 1);
4705 let expected_request = [
4709 0x00, 0x11, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x0A, 0x00, 0x01,
4710 ];
4711 assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
4712 drop(sent_frames);
4713
4714 let response_adu = [0x00, 0x11, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0x01];
4716
4717 client_services
4718 .transport
4719 .recv_frames
4720 .borrow_mut()
4721 .push_back(Vec::from_slice(&response_adu).unwrap())
4722 .unwrap();
4723 client_services.poll();
4724
4725 let received_responses = client_services
4726 .app
4727 .received_discrete_input_responses
4728 .borrow();
4729 assert_eq!(received_responses.len(), 1);
4730 let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
4731 assert_eq!(*rcv_txn_id, txn_id);
4732 assert_eq!(*rcv_unit_id, unit_id);
4733 assert_eq!(rcv_inputs.from_address(), address);
4734 assert_eq!(rcv_inputs.quantity(), 1);
4735 assert_eq!(rcv_inputs.value(address).unwrap(), true);
4736 assert_eq!(*rcv_quantity, 1);
4737 }
4738
4739 #[test]
4741 fn test_client_services_read_device_identification_e2e_success() {
4742 let transport = MockTransport::default();
4743 let app = MockApp::default();
4744 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4745 let mut client_services =
4746 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4747 client_services.connect().unwrap();
4748
4749 let txn_id = 20;
4750 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4751 let read_code = ReadDeviceIdCode::Basic;
4752 let object_id = ObjectId::from(0x00);
4753
4754 client_services
4755 .read_device_identification(txn_id, unit_id, read_code, object_id)
4756 .unwrap();
4757
4758 let sent_frames = client_services.transport.sent_frames.borrow();
4760 assert_eq!(sent_frames.len(), 1);
4761 let expected_request = [
4765 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2B, 0x0E, 0x01, 0x00,
4766 ];
4767 assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
4768 drop(sent_frames);
4769
4770 let response_adu = [
4775 0x00, 0x14, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x01,
4776 0x00, 0x03, 0x46, 0x6F, 0x6F,
4777 ];
4778
4779 client_services
4780 .transport
4781 .recv_frames
4782 .borrow_mut()
4783 .push_back(Vec::from_slice(&response_adu).unwrap())
4784 .unwrap();
4785 client_services.poll();
4786
4787 let received_responses = client_services
4788 .app
4789 .received_read_device_id_responses
4790 .borrow();
4791 assert_eq!(received_responses.len(), 1);
4792 let (rcv_txn_id, rcv_unit_id, rcv_resp) = &received_responses[0];
4793 assert_eq!(*rcv_txn_id, txn_id);
4794 assert_eq!(*rcv_unit_id, unit_id);
4795 assert_eq!(rcv_resp.read_device_id_code, ReadDeviceIdCode::Basic);
4796 assert_eq!(
4797 rcv_resp.conformity_level,
4798 ConformityLevel::BasicStreamAndIndividual
4799 );
4800 assert_eq!(rcv_resp.number_of_objects, 1);
4801
4802 assert_eq!(&rcv_resp.objects_data[..5], &[0x00, 0x03, 0x46, 0x6F, 0x6F]);
4804 }
4805
4806 #[test]
4808 fn test_client_services_read_device_identification_multi_transaction() {
4809 let transport = MockTransport::default();
4810 let app = MockApp::default();
4811 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4812 let mut client_services =
4813 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4814 client_services.connect().unwrap();
4815
4816 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4817 let txn_id_1 = 21;
4819 client_services
4820 .read_device_identification(
4821 txn_id_1,
4822 unit_id,
4823 ReadDeviceIdCode::Basic,
4824 ObjectId::from(0x00),
4825 )
4826 .unwrap();
4827
4828 let txn_id_2 = 22;
4830 client_services
4831 .read_device_identification(
4832 txn_id_2,
4833 unit_id,
4834 ReadDeviceIdCode::Regular,
4835 ObjectId::from(0x00),
4836 )
4837 .unwrap();
4838
4839 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
4840
4841 let response_adu_2 = [
4845 0x00, 0x16, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x82, 0x00, 0x00, 0x00,
4846 ];
4847 client_services
4848 .transport
4849 .recv_frames
4850 .borrow_mut()
4851 .push_back(Vec::from_slice(&response_adu_2).unwrap())
4852 .unwrap();
4853
4854 client_services.poll();
4855
4856 {
4857 let received_responses = client_services
4858 .app
4859 .received_read_device_id_responses
4860 .borrow();
4861 assert_eq!(received_responses.len(), 1);
4862 assert_eq!(received_responses[0].0, txn_id_2);
4863 assert_eq!(
4864 received_responses[0].2.read_device_id_code,
4865 ReadDeviceIdCode::Regular
4866 );
4867 }
4868
4869 let response_adu_1 = [
4872 0x00, 0x15, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x00,
4873 ];
4874 client_services
4875 .transport
4876 .recv_frames
4877 .borrow_mut()
4878 .push_back(Vec::from_slice(&response_adu_1).unwrap())
4879 .unwrap();
4880
4881 client_services.poll();
4882
4883 {
4884 let received_responses = client_services
4885 .app
4886 .received_read_device_id_responses
4887 .borrow();
4888 assert_eq!(received_responses.len(), 2);
4889 assert_eq!(received_responses[1].0, txn_id_1);
4890 assert_eq!(
4891 received_responses[1].2.read_device_id_code,
4892 ReadDeviceIdCode::Basic
4893 );
4894 }
4895 }
4896
4897 #[test]
4899 fn test_client_services_read_device_identification_mismatch_code() {
4900 let transport = MockTransport::default();
4901 let app = MockApp::default();
4902 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4903 let mut client_services =
4904 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4905 client_services.connect().unwrap();
4906
4907 let txn_id = 30;
4908 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4909 client_services
4911 .read_device_identification(
4912 txn_id,
4913 unit_id,
4914 ReadDeviceIdCode::Basic,
4915 ObjectId::from(0x00),
4916 )
4917 .unwrap();
4918
4919 let response_adu = [
4922 0x00, 0x1E, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x81, 0x00, 0x00, 0x00,
4923 ];
4924
4925 client_services
4926 .transport
4927 .recv_frames
4928 .borrow_mut()
4929 .push_back(Vec::from_slice(&response_adu).unwrap())
4930 .unwrap();
4931
4932 client_services.poll();
4933
4934 assert!(
4936 client_services
4937 .app
4938 .received_read_device_id_responses
4939 .borrow()
4940 .is_empty()
4941 );
4942
4943 let failed = client_services.app().failed_requests.borrow();
4945 assert_eq!(failed.len(), 1);
4946 assert_eq!(failed[0].2, MbusError::InvalidDeviceIdentification);
4947 }
4948
4949 #[test]
4951 fn test_client_services_read_exception_status_e2e_success() {
4952 let transport = MockTransport::default();
4953 let app = MockApp::default();
4954 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4955 let mut client_services =
4956 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4957 client_services.connect().unwrap();
4958
4959 let txn_id = 40;
4960 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4961
4962 let err = client_services.read_exception_status(txn_id, unit_id).err();
4963 assert_eq!(err, Some(MbusError::InvalidTransport));
4965 }
4966
4967 #[test]
4969 fn test_client_services_diagnostics_query_data_success() {
4970 let transport = MockTransport::default();
4971 let app = MockApp::default();
4972 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4973 let mut client_services =
4974 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4975 client_services.connect().unwrap();
4976
4977 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4978 let data = [0x1234, 0x5678];
4979 let sub_function = DiagnosticSubFunction::ReturnQueryData;
4980 let err = client_services
4981 .diagnostics(50, unit_id, sub_function, &data)
4982 .err();
4983 assert_eq!(err, Some(MbusError::InvalidTransport));
4984 }
4985
4986 #[test]
4988 fn test_client_services_get_comm_event_counter_success() {
4989 let transport = MockTransport::default();
4990 let app = MockApp::default();
4991 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4992 let mut client_services =
4993 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4994 client_services.connect().unwrap();
4995 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4996 let err = client_services.get_comm_event_counter(60, unit_id).err();
4997
4998 assert_eq!(err, Some(MbusError::InvalidTransport));
4999 }
5000
5001 #[test]
5003 fn test_client_services_report_server_id_success() {
5004 let transport = MockTransport::default();
5005 let app = MockApp::default();
5006 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5007 let mut client_services =
5008 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5009 client_services.connect().unwrap();
5010
5011 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
5012 let err = client_services.report_server_id(70, unit_id).err();
5013
5014 assert_eq!(err, Some(MbusError::InvalidTransport));
5015 }
5016
5017 #[test]
5021 fn test_broadcast_read_multiple_coils_not_allowed() {
5022 let transport = MockTransport::default();
5023 let app = MockApp::default();
5024 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5025 let mut client_services =
5026 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5027 client_services.connect().unwrap();
5028
5029 let txn_id = 0x0001;
5030 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
5031 let address = 0x0000;
5032 let quantity = 8;
5033 let res = client_services.read_multiple_coils(txn_id, unit_id, address, quantity);
5034 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
5035 }
5036
5037 #[test]
5039 fn test_broadcast_write_single_coil_tcp_not_allowed() {
5040 let transport = MockTransport::default();
5041 let app = MockApp::default();
5042 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5043 let mut client_services =
5044 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5045 client_services.connect().unwrap();
5046
5047 let txn_id = 0x0002;
5048 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
5049 let res = client_services.write_single_coil(txn_id, unit_id, 0x0000, true);
5050 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
5051 }
5052
5053 #[test]
5055 fn test_broadcast_write_multiple_coils_tcp_not_allowed() {
5056 let transport = MockTransport::default();
5057 let app = MockApp::default();
5058 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5059 let mut client_services =
5060 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5061 client_services.connect().unwrap();
5062
5063 let txn_id = 0x0003;
5064 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
5065 let mut values = Coils::new(0x0000, 2).unwrap();
5066 values.set_value(0x0000, true).unwrap();
5067 values.set_value(0x0001, false).unwrap();
5068
5069 let res = client_services.write_multiple_coils(txn_id, unit_id, 0x0000, &values);
5070 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
5071 }
5072
5073 #[test]
5075 fn test_broadcast_read_discrete_inputs_not_allowed() {
5076 let transport = MockTransport::default();
5077 let app = MockApp::default();
5078 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5079 let mut client_services =
5080 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5081 client_services.connect().unwrap();
5082
5083 let txn_id = 0x0006;
5084 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
5085 let res = client_services.read_discrete_inputs(txn_id, unit_id, 0x0000, 2);
5086 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
5087 }
5088
5089 #[test]
5092 fn test_client_services_clears_buffer_on_overflow() {
5093 let transport = MockTransport::default();
5094 let app = MockApp::default();
5095 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5096 let mut client_services =
5097 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5098 client_services.connect().unwrap();
5099
5100 let initial_garbage = [0xFF; MAX_ADU_FRAME_LEN - 10];
5102 client_services
5103 .rxed_frame
5104 .extend_from_slice(&initial_garbage)
5105 .unwrap();
5106
5107 let chunk = [0xAA; 20];
5109 client_services
5110 .transport
5111 .recv_frames
5112 .borrow_mut()
5113 .push_back(Vec::from_slice(&chunk).unwrap())
5114 .unwrap();
5115
5116 client_services.poll();
5118
5119 assert!(
5120 client_services.rxed_frame.is_empty(),
5121 "Buffer should be cleared on overflow to prevent crashing and recover from stream noise."
5122 );
5123 }
5124}