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 T::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")]
992 if !matches!(recv_error, MbusError::Timeout) && !self.expected_responses.is_empty() {
993 self.app.on_rx_error(
994 0,
995 UnitIdOrSlaveAddr::from_u8(0),
996 recv_error,
997 self.rxed_frame.as_slice(),
998 );
999 }
1000 }
1001 }
1002
1003 fn process_rxed_frame(&mut self) {
1004 while !self.rxed_frame.is_empty() {
1005 match self.ingest_frame() {
1006 Ok(consumed) => {
1007 self.drain_rxed_frame(consumed);
1008 }
1009 Err(MbusError::BufferTooSmall) => {
1010 client_log_trace!(
1012 "incomplete frame in rx buffer; waiting for more bytes (buffer_len={})",
1013 self.rxed_frame.len()
1014 );
1015 break;
1016 }
1017 Err(err) => {
1018 self.handle_parse_error(err);
1019 }
1020 }
1021 }
1022 }
1023
1024 fn handle_parse_error(&mut self, err: MbusError) {
1025 #[cfg(feature = "traffic")]
1026 self.app.on_rx_error(
1027 0,
1028 UnitIdOrSlaveAddr::from_u8(self.rxed_frame.first().copied().unwrap_or(0)),
1029 err,
1030 self.rxed_frame.as_slice(),
1031 );
1032
1033 client_log_debug!(
1035 "frame parse/resync event: error={:?}, buffer_len={}; dropping 1 byte",
1036 err,
1037 self.rxed_frame.len()
1038 );
1039 let len = self.rxed_frame.len();
1040 if len > 1 {
1041 self.rxed_frame.copy_within(1.., 0);
1042 self.rxed_frame.truncate(len - 1);
1043 } else {
1044 self.rxed_frame.clear();
1045 }
1046 }
1047
1048 fn drain_rxed_frame(&mut self, consumed: usize) {
1049 client_log_trace!(
1050 "ingested complete frame consuming {} bytes from rx buffer len {}",
1051 consumed,
1052 self.rxed_frame.len()
1053 );
1054 let len = self.rxed_frame.len();
1055 if consumed < len {
1056 self.rxed_frame.copy_within(consumed.., 0);
1058 self.rxed_frame.truncate(len - consumed);
1059 } else {
1060 self.rxed_frame.clear();
1061 }
1062 }
1063
1064 fn append_to_rxed_frame(&mut self, frame: Vec<u8, 513>) {
1065 client_log_trace!("received {} transport bytes", frame.len());
1066 if self.rxed_frame.extend_from_slice(frame.as_slice()).is_err() {
1067 client_log_debug!(
1069 "received frame buffer overflow while appending {} bytes; clearing receive buffer",
1070 frame.len()
1071 );
1072 #[cfg(feature = "traffic")]
1073 self.app.on_rx_error(
1074 0,
1075 UnitIdOrSlaveAddr::from_u8(0),
1076 MbusError::BufferTooSmall,
1077 frame.as_slice(),
1078 );
1079 self.rxed_frame.clear();
1080 }
1081 }
1082
1083 fn fail_all_pending_requests(&mut self, error: MbusError) {
1084 let pending_count = self.expected_responses.len();
1085 client_log_debug!(
1086 "failing {} pending request(s) with error {:?}",
1087 pending_count,
1088 error
1089 );
1090 while let Some(response) = self.expected_responses.pop() {
1091 #[cfg(feature = "traffic")]
1092 self.app.on_rx_error(
1093 response.txn_id,
1094 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1095 error,
1096 &[],
1097 );
1098 self.app.request_failed(
1099 response.txn_id,
1100 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1101 error,
1102 );
1103 }
1104 self.next_timeout_check = None;
1105 }
1106
1107 fn handle_timeouts(&mut self) {
1122 if self.expected_responses.is_empty() {
1123 self.next_timeout_check = None;
1124 return;
1125 }
1126
1127 let current_millis = self.app.current_millis();
1128
1129 if let Some(check_at) = self.next_timeout_check
1131 && current_millis < check_at
1132 {
1133 client_log_trace!(
1134 "skipping timeout scan until {}, current_millis={}",
1135 check_at,
1136 current_millis
1137 );
1138 return;
1139 }
1140
1141 let response_timeout_ms = self.response_timeout_ms();
1142 let retry_policy = RetryPolicy {
1143 backoff: self.config.retry_backoff_strategy(),
1144 jitter: self.config.retry_jitter_strategy(),
1145 random_fn: self.config.retry_random_fn(),
1146 };
1147 let mut i = 0;
1148 let mut new_next_check = u64::MAX;
1149
1150 while i < self.expected_responses.len() {
1151 match self.try_process_scheduled_retry(
1153 i,
1154 current_millis,
1155 response_timeout_ms,
1156 &mut new_next_check,
1157 ) {
1158 LoopAction::Advance => {
1159 i += 1;
1160 continue;
1161 }
1162 LoopAction::Repeat => {
1163 continue;
1164 }
1165 LoopAction::NotHandled => {}
1166 }
1167
1168 match self.try_handle_request_timeout(
1170 i,
1171 current_millis,
1172 response_timeout_ms,
1173 retry_policy,
1174 &mut new_next_check,
1175 ) {
1176 LoopAction::Advance => {
1177 i += 1;
1178 continue;
1179 }
1180 LoopAction::Repeat => {
1181 continue;
1182 }
1183 LoopAction::NotHandled => {}
1184 }
1185
1186 i += 1;
1188 }
1189
1190 self.next_timeout_check = if new_next_check != u64::MAX {
1191 Some(new_next_check)
1192 } else {
1193 None
1194 };
1195 }
1196
1197 fn try_process_scheduled_retry(
1206 &mut self,
1207 i: usize,
1208 current_millis: u64,
1209 response_timeout_ms: u64,
1210 new_next_check: &mut u64,
1211 ) -> LoopAction {
1212 let retry_at = match self.expected_responses[i].next_retry_timestamp {
1213 Some(t) => t,
1214 None => return LoopAction::NotHandled,
1215 };
1216
1217 if current_millis >= retry_at {
1218 return self.send_due_retry(i, current_millis, response_timeout_ms, new_next_check);
1219 }
1220
1221 if retry_at < *new_next_check {
1223 *new_next_check = retry_at;
1224 }
1225 LoopAction::Advance
1226 }
1227
1228 fn send_due_retry(
1233 &mut self,
1234 i: usize,
1235 current_millis: u64,
1236 response_timeout_ms: u64,
1237 new_next_check: &mut u64,
1238 ) -> LoopAction {
1239 let expected_response = &self.expected_responses[i];
1240 client_log_debug!(
1241 "retry due now: txn_id={}, unit_id_or_slave_addr={}, retry_attempt_index={}, retries_left={}",
1242 expected_response.txn_id,
1243 expected_response.unit_id_or_slave_addr,
1244 expected_response.retry_attempt_index.saturating_add(1),
1245 expected_response.retries_left
1246 );
1247
1248 let adu = self.expected_responses[i].original_adu.clone();
1250 if self.transport.send(&adu).is_err() {
1251 let response = self.expected_responses.swap_remove(i);
1255 client_log_debug!(
1256 "retry send failed: txn_id={}, unit_id_or_slave_addr={}; dropping request",
1257 response.txn_id,
1258 response.unit_id_or_slave_addr
1259 );
1260 self.app.request_failed(
1261 response.txn_id,
1262 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1263 MbusError::SendFailed,
1264 );
1265 #[cfg(feature = "traffic")]
1266 self.app.on_tx_error(
1267 response.txn_id,
1268 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1269 MbusError::SendFailed,
1270 adu.as_slice(),
1271 );
1272 return LoopAction::Repeat;
1273 }
1274
1275 #[cfg(feature = "traffic")]
1276 {
1277 let response = &self.expected_responses[i];
1278 self.app.on_tx_frame(
1279 response.txn_id,
1280 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1281 adu.as_slice(),
1282 );
1283 }
1284
1285 update_retries(
1286 current_millis,
1287 response_timeout_ms,
1288 new_next_check,
1289 &mut self.expected_responses[i],
1290 );
1291 LoopAction::Advance
1292 }
1293
1294 fn try_handle_request_timeout(
1307 &mut self,
1308 i: usize,
1309 current_millis: u64,
1310 response_timeout_ms: u64,
1311 retry_policy: RetryPolicy,
1312 new_next_check: &mut u64,
1313 ) -> LoopAction {
1314 let expires_at = self.expected_responses[i]
1315 .sent_timestamp
1316 .saturating_add(response_timeout_ms);
1317
1318 if current_millis <= expires_at {
1319 if expires_at < *new_next_check {
1321 *new_next_check = expires_at;
1322 }
1323 return LoopAction::NotHandled;
1324 }
1325
1326 if self.expected_responses[i].retries_left == 0 {
1327 return self.fail_exhausted_request(i);
1328 }
1329
1330 self.schedule_next_retry(i, current_millis, retry_policy, new_next_check)
1331 }
1332
1333 fn fail_exhausted_request(&mut self, i: usize) -> LoopAction {
1335 let response = self.expected_responses.swap_remove(i);
1338 client_log_debug!(
1339 "request exhausted retries: txn_id={}, unit_id_or_slave_addr={}",
1340 response.txn_id,
1341 response.unit_id_or_slave_addr
1342 );
1343 self.app.request_failed(
1344 response.txn_id,
1345 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1346 MbusError::NoRetriesLeft,
1347 );
1348 #[cfg(feature = "traffic")]
1349 self.app.on_rx_error(
1350 response.txn_id,
1351 UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1352 MbusError::NoRetriesLeft,
1353 &[],
1354 );
1355 LoopAction::Repeat
1356 }
1357
1358 fn schedule_next_retry(
1363 &mut self,
1364 i: usize,
1365 current_millis: u64,
1366 retry_policy: RetryPolicy,
1367 new_next_check: &mut u64,
1368 ) -> LoopAction {
1369 let expected_response = &mut self.expected_responses[i];
1370 let next_attempt = expected_response.retry_attempt_index.saturating_add(1);
1371 let base_delay_ms = retry_policy.backoff.delay_ms_for_retry(next_attempt);
1372 let retry_delay_ms = retry_policy
1373 .jitter
1374 .apply(base_delay_ms, retry_policy.random_fn) as u64;
1375 let retry_at = current_millis.saturating_add(retry_delay_ms);
1376 expected_response.next_retry_timestamp = Some(retry_at);
1377
1378 client_log_debug!(
1379 "scheduling retry: txn_id={}, unit_id_or_slave_addr={}, next_attempt={}, delay_ms={}, retry_at={}",
1380 expected_response.txn_id,
1381 expected_response.unit_id_or_slave_addr,
1382 next_attempt,
1383 retry_delay_ms,
1384 retry_at
1385 );
1386
1387 if retry_delay_ms == 0 {
1390 client_log_trace!(
1391 "retry delay is zero; retry will be processed in the same poll cycle for txn_id={}",
1392 expected_response.txn_id
1393 );
1394 return LoopAction::Repeat;
1395 }
1396
1397 if retry_at < *new_next_check {
1398 *new_next_check = retry_at;
1399 }
1400 LoopAction::Advance
1401 }
1402
1403 fn add_an_expectation(
1404 &mut self,
1405 txn_id: u16,
1406 unit_id_slave_addr: UnitIdOrSlaveAddr,
1407 frame: &heapless::Vec<u8, MAX_ADU_FRAME_LEN>,
1408 operation_meta: OperationMeta,
1409 handler: ResponseHandler<TRANSPORT, APP, N>,
1410 ) -> Result<(), MbusError> {
1411 client_log_trace!(
1412 "queueing expected response: txn_id={}, unit_id_or_slave_addr={}, queue_len_before={}",
1413 txn_id,
1414 unit_id_slave_addr.get(),
1415 self.expected_responses.len()
1416 );
1417 self.expected_responses
1418 .push(ExpectedResponse {
1419 txn_id,
1420 unit_id_or_slave_addr: unit_id_slave_addr.get(),
1421 original_adu: frame.clone(),
1422 sent_timestamp: self.app.current_millis(),
1423 retries_left: self.retry_attempts(),
1424 retry_attempt_index: 0,
1425 next_retry_timestamp: None,
1426 handler,
1427 operation_meta,
1428 })
1429 .map_err(|_| MbusError::TooManyRequests)?;
1430 Ok(())
1431 }
1432}
1433
1434fn update_retries<TRANSPORT, APP, const N: usize>(
1435 current_millis: u64,
1436 response_timeout_ms: u64,
1437 new_next_check: &mut u64,
1438 expected_response: &mut ExpectedResponse<TRANSPORT, APP, N>,
1439) {
1440 expected_response.retries_left = expected_response.retries_left.saturating_sub(1);
1441 expected_response.retry_attempt_index = expected_response.retry_attempt_index.saturating_add(1);
1442 expected_response.sent_timestamp = current_millis;
1443 expected_response.next_retry_timestamp = None;
1444
1445 let expires_at = current_millis.saturating_add(response_timeout_ms);
1446 if expires_at < *new_next_check {
1447 *new_next_check = expires_at;
1448 }
1449}
1450
1451impl<TRANSPORT: Transport, APP: ClientCommon, const N: usize> ClientServices<TRANSPORT, APP, N> {
1453 pub fn new(transport: TRANSPORT, app: APP, config: ModbusConfig) -> Result<Self, MbusError> {
1457 let transport_type = TRANSPORT::TRANSPORT_TYPE;
1458 if matches!(
1459 transport_type,
1460 TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1461 ) && N != 1
1462 {
1463 return Err(MbusError::InvalidNumOfExpectedRsps);
1464 }
1465
1466 client_log_debug!(
1467 "client created with transport_type={:?}, queue_capacity={}",
1468 transport_type,
1469 N
1470 );
1471
1472 Ok(Self {
1473 app,
1474 transport,
1475 rxed_frame: Vec::new(),
1476 config,
1477 expected_responses: Vec::new(),
1478 next_timeout_check: None,
1479 })
1480 }
1481
1482 pub fn connect(&mut self) -> Result<(), MbusError>
1488 where
1489 TRANSPORT::Error: Into<MbusError>,
1490 {
1491 client_log_debug!("connecting transport");
1492 self.transport.connect(&self.config).map_err(|e| e.into())
1493 }
1494
1495 pub fn app(&self) -> &APP {
1500 &self.app
1501 }
1502
1503 pub fn has_pending_requests(&self) -> bool {
1515 !self.expected_responses.is_empty()
1516 }
1517
1518 pub fn is_connected(&self) -> bool {
1520 self.transport.is_connected()
1521 }
1522
1523 pub fn disconnect(&mut self)
1534 where
1535 TRANSPORT::Error: Into<MbusError>,
1536 {
1537 client_log_debug!(
1538 "disconnect requested; pending_requests={}",
1539 self.expected_responses.len()
1540 );
1541 self.fail_all_pending_requests(MbusError::ConnectionLost);
1542 self.rxed_frame.clear();
1543 self.next_timeout_check = None;
1544 let _ = self.transport.disconnect();
1545 }
1546
1547 pub fn reconnect(&mut self) -> Result<(), MbusError>
1558 where
1559 TRANSPORT::Error: Into<MbusError>,
1560 {
1561 client_log_debug!(
1562 "reconnect requested; pending_requests={}",
1563 self.expected_responses.len()
1564 );
1565 self.fail_all_pending_requests(MbusError::ConnectionLost);
1566 self.rxed_frame.clear();
1567 self.next_timeout_check = None;
1568
1569 let _ = self.transport.disconnect();
1570 self.connect()
1571 }
1572
1573 pub fn new_serial(
1583 transport: TRANSPORT,
1584 app: APP,
1585 config: ModbusSerialConfig,
1586 ) -> Result<Self, MbusError>
1587 where
1588 [(); N]: SerialQueueSizeOne,
1589 {
1590 let transport_type = TRANSPORT::TRANSPORT_TYPE;
1591 if !matches!(
1592 transport_type,
1593 TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1594 ) {
1595 return Err(MbusError::InvalidTransport);
1596 }
1597
1598 let config = ModbusConfig::Serial(config);
1599
1600 client_log_debug!("serial client created with queue_capacity={}", N);
1601
1602 Ok(Self {
1603 app,
1604 transport,
1605 rxed_frame: Vec::new(),
1606 config,
1607 expected_responses: Vec::new(),
1608 next_timeout_check: None,
1609 })
1610 }
1611
1612 fn response_timeout_ms(&self) -> u64 {
1614 match &self.config {
1615 ModbusConfig::Tcp(config) => config.response_timeout_ms as u64,
1616 ModbusConfig::Serial(config) => config.response_timeout_ms as u64,
1617 }
1618 }
1619
1620 fn retry_attempts(&self) -> u8 {
1622 match &self.config {
1623 ModbusConfig::Tcp(config) => config.retry_attempts,
1624 ModbusConfig::Serial(config) => config.retry_attempts,
1625 }
1626 }
1627
1628 fn ingest_frame(&mut self) -> Result<usize, MbusError> {
1630 let frame = self.rxed_frame.as_slice();
1631 let transport_type = TRANSPORT::TRANSPORT_TYPE;
1632
1633 client_log_trace!(
1634 "attempting frame ingest: transport_type={:?}, buffer_len={}",
1635 transport_type,
1636 frame.len()
1637 );
1638
1639 let expected_length = match derive_length_from_bytes(frame, transport_type) {
1640 Some(len) => len,
1641 None => return Err(MbusError::BufferTooSmall),
1642 };
1643
1644 client_log_trace!("derived expected frame length={}", expected_length);
1645
1646 if expected_length > MAX_ADU_FRAME_LEN {
1647 client_log_debug!(
1648 "derived frame length {} exceeds MAX_ADU_FRAME_LEN {}",
1649 expected_length,
1650 MAX_ADU_FRAME_LEN
1651 );
1652 return Err(MbusError::BasicParseError);
1653 }
1654
1655 if self.rxed_frame.len() < expected_length {
1656 return Err(MbusError::BufferTooSmall);
1657 }
1658
1659 let message = match common::decompile_adu_frame(&frame[..expected_length], transport_type) {
1660 Ok(value) => value,
1661 Err(err) => {
1662 client_log_debug!(
1663 "decompile_adu_frame failed for {} bytes: {:?}",
1664 expected_length,
1665 err
1666 );
1667 return Err(err); }
1669 };
1670 use mbus_core::data_unit::common::AdditionalAddress;
1671 use mbus_core::transport::TransportType::*;
1672 let message = match TRANSPORT::TRANSPORT_TYPE {
1673 StdTcp | CustomTcp => {
1674 let mbap_header = match message.additional_address() {
1675 AdditionalAddress::MbapHeader(header) => header,
1676 _ => return Ok(expected_length),
1677 };
1678 let additional_addr = AdditionalAddress::MbapHeader(*mbap_header);
1679 ModbusMessage::new(additional_addr, message.pdu)
1680 }
1681 StdSerial(_) | CustomSerial(_) => {
1682 let slave_addr = match message.additional_address() {
1683 AdditionalAddress::SlaveAddress(addr) => addr.address(),
1684 _ => return Ok(expected_length),
1685 };
1686
1687 let additional_address =
1688 AdditionalAddress::SlaveAddress(SlaveAddress::new(slave_addr)?);
1689 ModbusMessage::new(additional_address, message.pdu)
1690 }
1691 };
1692
1693 let mut raw_frame = Vec::<u8, MAX_ADU_FRAME_LEN>::new();
1694 raw_frame
1695 .extend_from_slice(&frame[..expected_length])
1696 .map_err(|_| MbusError::BufferLenMissmatch)?;
1697
1698 self.dispatch_response(&message, raw_frame.as_slice());
1699 client_log_trace!("frame dispatch complete for {} bytes", expected_length);
1700
1701 Ok(expected_length)
1702 }
1703
1704 pub(crate) fn dispatch_request_frame(
1705 &mut self,
1706 txn_id: u16,
1707 unit_id_slave_addr: UnitIdOrSlaveAddr,
1708 frame: &heapless::Vec<u8, MAX_ADU_FRAME_LEN>,
1709 ) -> Result<(), MbusError> {
1710 if self.transport.send(frame).is_err() {
1711 #[cfg(feature = "traffic")]
1712 self.app.on_tx_error(
1713 txn_id,
1714 unit_id_slave_addr,
1715 MbusError::SendFailed,
1716 frame.as_slice(),
1717 );
1718 return Err(MbusError::SendFailed);
1719 }
1720
1721 #[cfg(feature = "traffic")]
1722 self.app
1723 .on_tx_frame(txn_id, unit_id_slave_addr, frame.as_slice());
1724
1725 #[cfg(not(feature = "traffic"))]
1726 {
1727 let _ = txn_id;
1728 let _ = unit_id_slave_addr;
1729 }
1730
1731 Ok(())
1732 }
1733}
1734
1735#[cfg(test)]
1736mod tests {
1737 use super::*;
1738 use crate::app::CoilResponse;
1739 use crate::app::DiagnosticsResponse;
1740 use crate::app::DiscreteInputResponse;
1741 use crate::app::FifoQueueResponse;
1742 use crate::app::FileRecordResponse;
1743 use crate::app::RegisterResponse;
1744 #[cfg(feature = "traffic")]
1745 use crate::app::TrafficDirection;
1746 use crate::services::coil::Coils;
1747
1748 use crate::services::diagnostic::ConformityLevel;
1749 use crate::services::diagnostic::DeviceIdentificationResponse;
1750 use crate::services::diagnostic::ObjectId;
1751 use crate::services::discrete_input::DiscreteInputs;
1752 use crate::services::fifo_queue::FifoQueue;
1753 use crate::services::file_record::MAX_SUB_REQUESTS_PER_PDU;
1754 use crate::services::file_record::SubRequest;
1755 use crate::services::file_record::SubRequestParams;
1756 use crate::services::register::Registers;
1757 use core::cell::RefCell; use core::str::FromStr;
1759 use heapless::Deque;
1760 use heapless::Vec;
1761 use mbus_core::errors::MbusError;
1762 use mbus_core::function_codes::public::DiagnosticSubFunction;
1763 use mbus_core::transport::TransportType;
1764 use mbus_core::transport::checksum;
1765 use mbus_core::transport::{
1766 BackoffStrategy, BaudRate, JitterStrategy, ModbusConfig, ModbusSerialConfig,
1767 ModbusTcpConfig, Parity, SerialMode,
1768 };
1769
1770 const MOCK_DEQUE_CAPACITY: usize = 10; fn rand_zero() -> u32 {
1773 0
1774 }
1775
1776 fn rand_upper_percent_20() -> u32 {
1777 40
1778 }
1779
1780 fn make_serial_config() -> ModbusSerialConfig {
1781 ModbusSerialConfig {
1782 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
1783 mode: SerialMode::Rtu,
1784 baud_rate: BaudRate::Baud19200,
1785 data_bits: mbus_core::transport::DataBits::Eight,
1786 stop_bits: 1,
1787 parity: Parity::Even,
1788 response_timeout_ms: 100,
1789 retry_attempts: 0,
1790 retry_backoff_strategy: BackoffStrategy::Immediate,
1791 retry_jitter_strategy: JitterStrategy::None,
1792 retry_random_fn: None,
1793 }
1794 }
1795
1796 fn make_serial_client() -> ClientServices<MockSerialTransport, MockApp, 1> {
1797 let transport = MockSerialTransport::default();
1798 let app = MockApp::default();
1799 let mut client = ClientServices::<MockSerialTransport, MockApp, 1>::new_serial(
1800 transport,
1801 app,
1802 make_serial_config(),
1803 )
1804 .unwrap();
1805 client.connect().unwrap();
1806 client
1807 }
1808
1809 fn make_rtu_exception_adu(
1810 unit_id: UnitIdOrSlaveAddr,
1811 function_code: u8,
1812 exception_code: u8,
1813 ) -> Vec<u8, MAX_ADU_FRAME_LEN> {
1814 let mut frame = Vec::new();
1815 frame.push(unit_id.get()).unwrap();
1816 frame.push(function_code | 0x80).unwrap();
1817 frame.push(exception_code).unwrap();
1818 let crc = checksum::crc16(frame.as_slice()).to_le_bytes();
1819 frame.extend_from_slice(&crc).unwrap();
1820 frame
1821 }
1822
1823 #[derive(Debug, Default)]
1825 struct MockTransport {
1826 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>>,
1829 pub connect_should_fail: bool,
1830 pub send_should_fail: bool,
1831 pub is_connected_flag: RefCell<bool>,
1832 }
1833
1834 impl Transport for MockTransport {
1835 type Error = MbusError;
1836 const TRANSPORT_TYPE: TransportType = TransportType::StdTcp;
1837 const SUPPORTS_BROADCAST_WRITES: bool = false;
1838
1839 fn connect(&mut self, _config: &ModbusConfig) -> Result<(), Self::Error> {
1840 if self.connect_should_fail {
1841 return Err(MbusError::ConnectionFailed);
1842 }
1843 *self.is_connected_flag.borrow_mut() = true;
1844 Ok(())
1845 }
1846
1847 fn disconnect(&mut self) -> Result<(), Self::Error> {
1848 *self.is_connected_flag.borrow_mut() = false;
1849 Ok(())
1850 }
1851
1852 fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
1853 if self.send_should_fail {
1854 return Err(MbusError::SendFailed);
1855 }
1856 let mut vec_adu = Vec::new();
1857 vec_adu
1858 .extend_from_slice(adu)
1859 .map_err(|_| MbusError::BufferLenMissmatch)?;
1860 self.sent_frames
1861 .borrow_mut()
1862 .push_back(vec_adu)
1863 .map_err(|_| MbusError::BufferLenMissmatch)?;
1864 Ok(())
1865 }
1866
1867 fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
1868 if let Some(err) = self.recv_error.borrow_mut().take() {
1869 return Err(err);
1870 }
1871 self.recv_frames
1872 .borrow_mut()
1873 .pop_front()
1874 .ok_or(MbusError::Timeout)
1875 }
1876
1877 fn is_connected(&self) -> bool {
1878 *self.is_connected_flag.borrow()
1879 }
1880 }
1881
1882 #[derive(Debug, Default)]
1883 struct MockSerialTransport {
1884 pub sent_frames: RefCell<Deque<Vec<u8, MAX_ADU_FRAME_LEN>, MOCK_DEQUE_CAPACITY>>,
1885 pub recv_frames: RefCell<Deque<Vec<u8, MAX_ADU_FRAME_LEN>, MOCK_DEQUE_CAPACITY>>,
1886 pub recv_error: RefCell<Option<MbusError>>,
1887 pub is_connected_flag: RefCell<bool>,
1888 }
1889
1890 impl Transport for MockSerialTransport {
1891 type Error = MbusError;
1892 const TRANSPORT_TYPE: TransportType = TransportType::CustomSerial(SerialMode::Rtu);
1893 const SUPPORTS_BROADCAST_WRITES: bool = true;
1894
1895 fn connect(&mut self, _config: &ModbusConfig) -> Result<(), Self::Error> {
1896 *self.is_connected_flag.borrow_mut() = true;
1897 Ok(())
1898 }
1899
1900 fn disconnect(&mut self) -> Result<(), Self::Error> {
1901 *self.is_connected_flag.borrow_mut() = false;
1902 Ok(())
1903 }
1904
1905 fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
1906 let mut vec_adu = Vec::new();
1907 vec_adu
1908 .extend_from_slice(adu)
1909 .map_err(|_| MbusError::BufferLenMissmatch)?;
1910 self.sent_frames
1911 .borrow_mut()
1912 .push_back(vec_adu)
1913 .map_err(|_| MbusError::BufferLenMissmatch)?;
1914 Ok(())
1915 }
1916
1917 fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
1918 if let Some(err) = self.recv_error.borrow_mut().take() {
1919 return Err(err);
1920 }
1921 self.recv_frames
1922 .borrow_mut()
1923 .pop_front()
1924 .ok_or(MbusError::Timeout)
1925 }
1926
1927 fn is_connected(&self) -> bool {
1928 *self.is_connected_flag.borrow()
1929 }
1930 }
1931
1932 #[derive(Debug, Default)]
1934 struct MockApp {
1935 pub received_coil_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr, Coils), 10>>, pub received_write_single_coil_responses:
1937 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, bool), 10>>,
1938 pub received_write_multiple_coils_responses:
1939 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1940 pub received_discrete_input_responses:
1941 RefCell<Vec<(u16, UnitIdOrSlaveAddr, DiscreteInputs, u16), 10>>,
1942 pub received_holding_register_responses:
1943 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1944 pub received_input_register_responses:
1945 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1946 pub received_write_single_register_responses:
1947 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1948 pub received_write_multiple_register_responses:
1949 RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1950 pub received_read_write_multiple_registers_responses:
1951 RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers), 10>>,
1952 pub received_mask_write_register_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1953 pub received_read_fifo_queue_responses:
1954 RefCell<Vec<(u16, UnitIdOrSlaveAddr, FifoQueue), 10>>,
1955 pub received_read_file_record_responses: RefCell<
1956 Vec<
1957 (
1958 u16,
1959 UnitIdOrSlaveAddr,
1960 Vec<SubRequestParams, MAX_SUB_REQUESTS_PER_PDU>,
1961 ),
1962 10,
1963 >,
1964 >,
1965 pub received_write_file_record_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1966 pub received_read_device_id_responses:
1967 RefCell<Vec<(u16, UnitIdOrSlaveAddr, DeviceIdentificationResponse), 10>>,
1968 pub failed_requests: RefCell<Vec<(u16, UnitIdOrSlaveAddr, MbusError), 10>>,
1969 #[cfg(feature = "traffic")]
1970 pub traffic_events: RefCell<Vec<(TrafficDirection, u16, UnitIdOrSlaveAddr), 32>>,
1971 #[cfg(feature = "traffic")]
1972 pub traffic_error_events:
1973 RefCell<Vec<(TrafficDirection, u16, UnitIdOrSlaveAddr, MbusError), 32>>,
1974
1975 pub current_time: RefCell<u64>, }
1977
1978 impl CoilResponse for MockApp {
1979 fn read_coils_response(
1980 &mut self,
1981 txn_id: u16,
1982 unit_id_slave_addr: UnitIdOrSlaveAddr,
1983 coils: &Coils,
1984 ) {
1985 self.received_coil_responses
1986 .borrow_mut()
1987 .push((txn_id, unit_id_slave_addr, coils.clone()))
1988 .unwrap();
1989 }
1990
1991 fn read_single_coil_response(
1992 &mut self,
1993 txn_id: u16,
1994 unit_id_slave_addr: UnitIdOrSlaveAddr,
1995 address: u16,
1996 value: bool,
1997 ) {
1998 let mut values_vec = [0x00, 1];
2000 values_vec[0] = if value { 0x01 } else { 0x00 }; let coils = Coils::new(address, 1)
2002 .unwrap()
2003 .with_values(&values_vec, 1)
2004 .unwrap();
2005 self.received_coil_responses
2006 .borrow_mut()
2007 .push((txn_id, unit_id_slave_addr, coils))
2008 .unwrap();
2009 }
2010
2011 fn write_single_coil_response(
2012 &mut self,
2013 txn_id: u16,
2014 unit_id_slave_addr: UnitIdOrSlaveAddr,
2015 address: u16,
2016 value: bool,
2017 ) {
2018 self.received_write_single_coil_responses
2019 .borrow_mut()
2020 .push((txn_id, unit_id_slave_addr, address, value))
2021 .unwrap();
2022 }
2023
2024 fn write_multiple_coils_response(
2025 &mut self,
2026 txn_id: u16,
2027 unit_id_slave_addr: UnitIdOrSlaveAddr,
2028 address: u16,
2029 quantity: u16,
2030 ) {
2031 self.received_write_multiple_coils_responses
2032 .borrow_mut()
2033 .push((txn_id, unit_id_slave_addr, address, quantity))
2034 .unwrap();
2035 }
2036 }
2037
2038 impl DiscreteInputResponse for MockApp {
2039 fn read_multiple_discrete_inputs_response(
2040 &mut self,
2041 txn_id: u16,
2042 unit_id_slave_addr: UnitIdOrSlaveAddr,
2043 inputs: &DiscreteInputs,
2044 ) {
2045 self.received_discrete_input_responses
2046 .borrow_mut()
2047 .push((
2048 txn_id,
2049 unit_id_slave_addr,
2050 inputs.clone(),
2051 inputs.quantity(),
2052 ))
2053 .unwrap();
2054 }
2055
2056 fn read_single_discrete_input_response(
2057 &mut self,
2058 txn_id: u16,
2059 unit_id_slave_addr: UnitIdOrSlaveAddr,
2060 address: u16,
2061 value: bool,
2062 ) {
2063 let mut values = [0u8; mbus_core::models::discrete_input::MAX_DISCRETE_INPUT_BYTES];
2064 values[0] = if value { 0x01 } else { 0x00 };
2065 let inputs = DiscreteInputs::new(address, 1)
2066 .unwrap()
2067 .with_values(&values, 1)
2068 .unwrap();
2069 self.received_discrete_input_responses
2070 .borrow_mut()
2071 .push((txn_id, unit_id_slave_addr, inputs, 1))
2072 .unwrap();
2073 }
2074 }
2075
2076 impl RequestErrorNotifier for MockApp {
2077 fn request_failed(
2078 &mut self,
2079 txn_id: u16,
2080 unit_id_slave_addr: UnitIdOrSlaveAddr,
2081 error: MbusError,
2082 ) {
2083 self.failed_requests
2084 .borrow_mut()
2085 .push((txn_id, unit_id_slave_addr, error))
2086 .unwrap();
2087 }
2088 }
2089
2090 #[cfg(feature = "traffic")]
2091 impl crate::app::TrafficNotifier for MockApp {
2092 fn on_tx_frame(
2093 &mut self,
2094 txn_id: u16,
2095 unit_id_slave_addr: UnitIdOrSlaveAddr,
2096 _frame_bytes: &[u8],
2097 ) {
2098 self.traffic_events
2099 .borrow_mut()
2100 .push((TrafficDirection::Tx, txn_id, unit_id_slave_addr))
2101 .unwrap();
2102 }
2103
2104 fn on_rx_frame(
2105 &mut self,
2106 txn_id: u16,
2107 unit_id_slave_addr: UnitIdOrSlaveAddr,
2108 _frame_bytes: &[u8],
2109 ) {
2110 self.traffic_events
2111 .borrow_mut()
2112 .push((TrafficDirection::Rx, txn_id, unit_id_slave_addr))
2113 .unwrap();
2114 }
2115
2116 fn on_tx_error(
2117 &mut self,
2118 txn_id: u16,
2119 unit_id_slave_addr: UnitIdOrSlaveAddr,
2120 error: MbusError,
2121 _frame_bytes: &[u8],
2122 ) {
2123 self.traffic_error_events
2124 .borrow_mut()
2125 .push((TrafficDirection::Tx, txn_id, unit_id_slave_addr, error))
2126 .unwrap();
2127 }
2128
2129 fn on_rx_error(
2130 &mut self,
2131 txn_id: u16,
2132 unit_id_slave_addr: UnitIdOrSlaveAddr,
2133 error: MbusError,
2134 _frame_bytes: &[u8],
2135 ) {
2136 self.traffic_error_events
2137 .borrow_mut()
2138 .push((TrafficDirection::Rx, txn_id, unit_id_slave_addr, error))
2139 .unwrap();
2140 }
2141 }
2142
2143 impl RegisterResponse for MockApp {
2144 fn read_multiple_holding_registers_response(
2145 &mut self,
2146 txn_id: u16,
2147 unit_id_slave_addr: UnitIdOrSlaveAddr,
2148 registers: &Registers,
2149 ) {
2150 let quantity = registers.quantity();
2151 self.received_holding_register_responses
2152 .borrow_mut()
2153 .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
2154 .unwrap();
2155 }
2156
2157 fn read_single_input_register_response(
2158 &mut self,
2159 txn_id: u16,
2160 unit_id_slave_addr: UnitIdOrSlaveAddr,
2161 address: u16,
2162 value: u16,
2163 ) {
2164 let values = [value];
2166 let registers = Registers::new(address, 1)
2167 .unwrap()
2168 .with_values(&values, 1)
2169 .unwrap();
2170 self.received_input_register_responses
2171 .borrow_mut()
2172 .push((txn_id, unit_id_slave_addr, registers, 1))
2173 .unwrap();
2174 }
2175
2176 fn read_single_holding_register_response(
2177 &mut self,
2178 txn_id: u16,
2179 unit_id_slave_addr: UnitIdOrSlaveAddr,
2180 address: u16,
2181 value: u16,
2182 ) {
2183 let data = [value];
2185 let registers = Registers::new(address, 1)
2187 .unwrap()
2188 .with_values(&data, 1)
2189 .unwrap();
2190
2191 self.received_holding_register_responses
2192 .borrow_mut()
2193 .push((txn_id, unit_id_slave_addr, registers, 1))
2194 .unwrap();
2195 }
2196
2197 fn read_multiple_input_registers_response(
2198 &mut self,
2199 txn_id: u16,
2200 unit_id_slave_addr: UnitIdOrSlaveAddr,
2201 registers: &Registers,
2202 ) {
2203 let quantity = registers.quantity();
2204 self.received_input_register_responses
2205 .borrow_mut()
2206 .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
2207 .unwrap();
2208 }
2209
2210 fn write_single_register_response(
2211 &mut self,
2212 txn_id: u16,
2213 unit_id_slave_addr: UnitIdOrSlaveAddr,
2214 address: u16,
2215 value: u16,
2216 ) {
2217 self.received_write_single_register_responses
2218 .borrow_mut()
2219 .push((txn_id, unit_id_slave_addr, address, value))
2220 .unwrap();
2221 }
2222
2223 fn write_multiple_registers_response(
2224 &mut self,
2225 txn_id: u16,
2226 unit_id_slave_addr: UnitIdOrSlaveAddr,
2227 address: u16,
2228 quantity: u16,
2229 ) {
2230 self.received_write_multiple_register_responses
2231 .borrow_mut()
2232 .push((txn_id, unit_id_slave_addr, address, quantity))
2233 .unwrap();
2234 }
2235
2236 fn read_write_multiple_registers_response(
2237 &mut self,
2238 txn_id: u16,
2239 unit_id_slave_addr: UnitIdOrSlaveAddr,
2240 registers: &Registers,
2241 ) {
2242 self.received_read_write_multiple_registers_responses
2243 .borrow_mut()
2244 .push((txn_id, unit_id_slave_addr, registers.clone()))
2245 .unwrap();
2246 }
2247
2248 fn mask_write_register_response(
2249 &mut self,
2250 txn_id: u16,
2251 unit_id_slave_addr: UnitIdOrSlaveAddr,
2252 ) {
2253 self.received_mask_write_register_responses
2254 .borrow_mut()
2255 .push((txn_id, unit_id_slave_addr))
2256 .unwrap();
2257 }
2258
2259 fn read_single_register_response(
2260 &mut self,
2261 txn_id: u16,
2262 unit_id_slave_addr: UnitIdOrSlaveAddr,
2263 address: u16,
2264 value: u16,
2265 ) {
2266 let data = [value];
2268 let registers = Registers::new(address, 1)
2270 .unwrap()
2271 .with_values(&data, 1)
2272 .unwrap();
2273
2274 self.received_holding_register_responses
2275 .borrow_mut()
2276 .push((txn_id, unit_id_slave_addr, registers, 1))
2277 .unwrap();
2278 }
2279 }
2280
2281 impl FifoQueueResponse for MockApp {
2282 fn read_fifo_queue_response(
2283 &mut self,
2284 txn_id: u16,
2285 unit_id_slave_addr: UnitIdOrSlaveAddr,
2286 fifo_queue: &FifoQueue,
2287 ) {
2288 self.received_read_fifo_queue_responses
2289 .borrow_mut()
2290 .push((txn_id, unit_id_slave_addr, fifo_queue.clone()))
2291 .unwrap();
2292 }
2293 }
2294
2295 impl FileRecordResponse for MockApp {
2296 fn read_file_record_response(
2297 &mut self,
2298 txn_id: u16,
2299 unit_id_slave_addr: UnitIdOrSlaveAddr,
2300 data: &[SubRequestParams],
2301 ) {
2302 let mut vec = Vec::new();
2303 vec.extend_from_slice(data).unwrap();
2304 self.received_read_file_record_responses
2305 .borrow_mut()
2306 .push((txn_id, unit_id_slave_addr, vec))
2307 .unwrap();
2308 }
2309 fn write_file_record_response(
2310 &mut self,
2311 txn_id: u16,
2312 unit_id_slave_addr: UnitIdOrSlaveAddr,
2313 ) {
2314 self.received_write_file_record_responses
2315 .borrow_mut()
2316 .push((txn_id, unit_id_slave_addr))
2317 .unwrap();
2318 }
2319 }
2320
2321 impl DiagnosticsResponse for MockApp {
2322 fn read_device_identification_response(
2323 &mut self,
2324 txn_id: u16,
2325 unit_id_slave_addr: UnitIdOrSlaveAddr,
2326 response: &DeviceIdentificationResponse,
2327 ) {
2328 self.received_read_device_id_responses
2329 .borrow_mut()
2330 .push((txn_id, unit_id_slave_addr, response.clone()))
2331 .unwrap();
2332 }
2333
2334 fn encapsulated_interface_transport_response(
2335 &mut self,
2336 _: u16,
2337 _: UnitIdOrSlaveAddr,
2338 _: EncapsulatedInterfaceType,
2339 _: &[u8],
2340 ) {
2341 }
2342
2343 fn diagnostics_response(
2344 &mut self,
2345 _: u16,
2346 _: UnitIdOrSlaveAddr,
2347 _: DiagnosticSubFunction,
2348 _: &[u16],
2349 ) {
2350 }
2351
2352 fn get_comm_event_counter_response(
2353 &mut self,
2354 _: u16,
2355 _: UnitIdOrSlaveAddr,
2356 _: u16,
2357 _: u16,
2358 ) {
2359 }
2360
2361 fn get_comm_event_log_response(
2362 &mut self,
2363 _: u16,
2364 _: UnitIdOrSlaveAddr,
2365 _: u16,
2366 _: u16,
2367 _: u16,
2368 _: &[u8],
2369 ) {
2370 }
2371
2372 fn read_exception_status_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: u8) {}
2373
2374 fn report_server_id_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: &[u8]) {}
2375 }
2376
2377 impl TimeKeeper for MockApp {
2378 fn current_millis(&self) -> u64 {
2379 *self.current_time.borrow()
2380 }
2381 }
2382
2383 #[test]
2387 fn test_client_services_new_success() {
2388 let transport = MockTransport::default();
2389 let app = MockApp::default();
2390 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2391
2392 let client_services =
2393 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
2394 assert!(client_services.is_ok());
2395 let mut client = client_services.unwrap();
2396 assert!(!client.is_connected());
2397 assert!(client.connect().is_ok());
2398 assert!(client.is_connected());
2399 }
2400
2401 #[test]
2403 fn test_client_services_connect_failure() {
2404 let mut transport = MockTransport::default();
2405 transport.connect_should_fail = true;
2406 let app = MockApp::default();
2407 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2408
2409 let client_services =
2410 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
2411 assert!(client_services.is_ok());
2412 let mut client = client_services.unwrap();
2413 let result = client.connect();
2414 assert!(result.is_err());
2415 assert_eq!(result.unwrap_err(), MbusError::ConnectionFailed);
2416 }
2417
2418 #[test]
2419 fn test_client_services_new_serial_success() {
2420 let transport = MockSerialTransport::default();
2421 let app = MockApp::default();
2422 let serial_config = ModbusSerialConfig {
2423 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
2424 mode: SerialMode::Rtu,
2425 baud_rate: BaudRate::Baud19200,
2426 data_bits: mbus_core::transport::DataBits::Eight,
2427 stop_bits: 1,
2428 parity: Parity::Even,
2429 response_timeout_ms: 1000,
2430 retry_attempts: 1,
2431 retry_backoff_strategy: BackoffStrategy::Immediate,
2432 retry_jitter_strategy: JitterStrategy::None,
2433 retry_random_fn: None,
2434 };
2435
2436 let client_services = ClientServices::<MockSerialTransport, MockApp, 1>::new_serial(
2437 transport,
2438 app,
2439 serial_config,
2440 );
2441 assert!(client_services.is_ok());
2442 let mut client = client_services.unwrap();
2443 assert!(client.connect().is_ok());
2444 }
2445
2446 #[test]
2447 fn test_reconnect_success_flushes_pending_requests() {
2448 let transport = MockTransport::default();
2449 let app = MockApp::default();
2450 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2451 let mut client_services =
2452 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2453 client_services.connect().unwrap();
2454
2455 let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
2456 client_services.read_single_coil(10, unit_id, 0).unwrap();
2457 assert_eq!(client_services.expected_responses.len(), 1);
2458
2459 let reconnect_result = client_services.reconnect();
2460 assert!(reconnect_result.is_ok());
2461 assert!(client_services.is_connected());
2462 assert!(client_services.expected_responses.is_empty());
2463
2464 let failed_requests = client_services.app().failed_requests.borrow();
2465 assert_eq!(failed_requests.len(), 1);
2466 assert_eq!(failed_requests[0].0, 10);
2467 assert_eq!(failed_requests[0].2, MbusError::ConnectionLost);
2468 }
2469
2470 #[test]
2471 fn test_reconnect_failure_propagates_connect_error() {
2472 let transport = MockTransport::default();
2473 let app = MockApp::default();
2474 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2475 let mut client_services =
2476 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2477 client_services.connect().unwrap();
2478
2479 client_services.transport.connect_should_fail = true;
2480 let reconnect_result = client_services.reconnect();
2481
2482 assert!(reconnect_result.is_err());
2483 assert_eq!(reconnect_result.unwrap_err(), MbusError::ConnectionFailed);
2484 assert!(!client_services.is_connected());
2485 }
2486
2487 #[test]
2489 fn test_read_multiple_coils_sends_valid_adu() {
2490 let transport = MockTransport::default();
2491 let app = MockApp::default();
2492 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2493 let mut client_services =
2494 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2495 client_services.connect().unwrap();
2496
2497 let txn_id = 0x0001;
2498 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2499 let address = 0x0000;
2500 let quantity = 8;
2501 client_services
2502 .read_multiple_coils(txn_id, unit_id, address, quantity)
2503 .unwrap();
2504
2505 let sent_frames = client_services.transport.sent_frames.borrow();
2506 assert_eq!(sent_frames.len(), 1);
2507 let sent_adu = sent_frames.front().unwrap();
2508
2509 #[rustfmt::skip]
2511 let expected_adu: [u8; 12] = [
2512 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, ];
2520 assert_eq!(sent_adu.as_slice(), &expected_adu);
2521 }
2522
2523 #[cfg(feature = "traffic")]
2524 #[test]
2525 fn test_traffic_tx_event_emitted_on_submit() {
2526 let transport = MockTransport::default();
2527 let app = MockApp::default();
2528 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2529 let mut client_services =
2530 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2531 client_services.connect().unwrap();
2532
2533 let txn_id = 0x0001;
2534 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2535 client_services
2536 .read_multiple_coils(txn_id, unit_id, 0x0000, 8)
2537 .unwrap();
2538
2539 let events = client_services.app().traffic_events.borrow();
2540 assert!(!events.is_empty());
2541 assert_eq!(events[0].0, TrafficDirection::Tx);
2542 assert_eq!(events[0].1, txn_id);
2543 assert_eq!(events[0].2, unit_id);
2544 }
2545
2546 #[cfg(feature = "traffic")]
2547 #[test]
2548 fn test_traffic_rx_event_emitted_on_dispatch() {
2549 let transport = MockTransport::default();
2550 let app = MockApp::default();
2551 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2552 let mut client_services =
2553 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2554 client_services.connect().unwrap();
2555
2556 let txn_id = 0x0001;
2557 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2558 client_services
2559 .read_multiple_coils(txn_id, unit_id, 0x0000, 8)
2560 .unwrap();
2561
2562 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2563 client_services
2564 .transport
2565 .recv_frames
2566 .borrow_mut()
2567 .push_back(Vec::from_slice(&response_adu).unwrap())
2568 .unwrap();
2569
2570 client_services.poll();
2571
2572 let events = client_services.app().traffic_events.borrow();
2573 assert!(events.len() >= 2);
2574 assert_eq!(events[0].0, TrafficDirection::Tx);
2575 assert_eq!(events[1].0, TrafficDirection::Rx);
2576 assert_eq!(events[1].1, txn_id);
2577 assert_eq!(events[1].2, unit_id);
2578 }
2579
2580 #[cfg(feature = "traffic")]
2581 #[test]
2582 fn test_traffic_tx_error_emitted_on_submit_send_failure() {
2583 let mut transport = MockTransport::default();
2584 transport.send_should_fail = true;
2585 let app = MockApp::default();
2586 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2587 let mut client_services =
2588 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2589 client_services.connect().unwrap();
2590
2591 let txn_id = 0x0066;
2592 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2593 let result = client_services.read_multiple_coils(txn_id, unit_id, 0x0000, 8);
2594 assert_eq!(result.unwrap_err(), MbusError::SendFailed);
2595
2596 let events = client_services.app().traffic_error_events.borrow();
2597 assert!(!events.is_empty());
2598 assert_eq!(events[0].0, TrafficDirection::Tx);
2599 assert_eq!(events[0].1, txn_id);
2600 assert_eq!(events[0].2, unit_id);
2601 assert_eq!(events[0].3, MbusError::SendFailed);
2602 }
2603
2604 #[cfg(feature = "traffic")]
2605 #[test]
2606 fn test_traffic_rx_error_emitted_on_timeout_path() {
2607 let transport = MockTransport::default();
2608 let app = MockApp::default();
2609 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2610 tcp_config.response_timeout_ms = 100;
2611 tcp_config.retry_attempts = 0;
2612 let config = ModbusConfig::Tcp(tcp_config);
2613 let mut client_services =
2614 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2615 client_services.connect().unwrap();
2616
2617 let txn_id = 0x0007;
2618 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2619 client_services
2620 .read_multiple_coils(txn_id, unit_id, 0x0000, 8)
2621 .unwrap();
2622
2623 *client_services.app.current_time.borrow_mut() = 500;
2624 client_services.poll();
2625
2626 let events = client_services.app().traffic_error_events.borrow();
2627 assert!(!events.is_empty());
2628 assert!(events.iter().any(|(direction, _, _, err)| {
2629 *direction == TrafficDirection::Rx
2630 && matches!(err, MbusError::Timeout | MbusError::NoRetriesLeft)
2631 }));
2632 }
2633
2634 #[test]
2636 fn test_read_multiple_coils_invalid_quantity() {
2637 let transport = MockTransport::default();
2638 let app = MockApp::default();
2639 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2640 let mut client_services =
2641 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2642 client_services.connect().unwrap();
2643
2644 let txn_id = 0x0001;
2645 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2646 let address = 0x0000;
2647 let quantity = 0; let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); assert_eq!(result.unwrap_err(), MbusError::InvalidQuantity);
2651 }
2652
2653 #[test]
2655 fn test_read_multiple_coils_send_failure() {
2656 let mut transport = MockTransport::default();
2657 transport.send_should_fail = true;
2658 let app = MockApp::default();
2659 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2660 let mut client_services =
2661 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2662 client_services.connect().unwrap();
2663
2664 let txn_id = 0x0001;
2665 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2666 let address = 0x0000;
2667 let quantity = 8;
2668
2669 let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); assert_eq!(result.unwrap_err(), MbusError::SendFailed);
2671 }
2672
2673 #[test]
2675 fn test_ingest_frame_wrong_fc() {
2676 let transport = MockTransport::default();
2677 let app = MockApp::default();
2678 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2679 let mut client_services =
2680 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2681 client_services.connect().unwrap();
2682
2683 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x03, 0x01, 0xB3];
2685
2686 client_services
2687 .transport
2688 .recv_frames
2689 .borrow_mut()
2690 .push_back(Vec::from_slice(&response_adu).unwrap())
2691 .unwrap();
2692 client_services.poll();
2693
2694 let received_responses = client_services.app().received_coil_responses.borrow();
2695 assert!(received_responses.is_empty());
2696 }
2697
2698 #[test]
2700 fn test_ingest_frame_malformed_adu() {
2701 let transport = MockTransport::default();
2702 let app = MockApp::default();
2703 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2704 let mut client_services =
2705 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2706 client_services.connect().unwrap();
2707
2708 let malformed_adu = [0x01, 0x02, 0x03];
2710
2711 client_services
2712 .transport
2713 .recv_frames
2714 .borrow_mut()
2715 .push_back(Vec::from_slice(&malformed_adu).unwrap())
2716 .unwrap();
2717 client_services.poll();
2718
2719 let received_responses = client_services.app().received_coil_responses.borrow();
2720 assert!(received_responses.is_empty());
2721 }
2722
2723 #[test]
2725 fn test_ingest_frame_unknown_txn_id() {
2726 let transport = MockTransport::default();
2727 let app = MockApp::default();
2728 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2729 let mut client_services =
2730 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2731 client_services.connect().unwrap();
2732
2733 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2735
2736 client_services
2737 .transport
2738 .recv_frames
2739 .borrow_mut()
2740 .push_back(Vec::from_slice(&response_adu).unwrap())
2741 .unwrap();
2742 client_services.poll();
2743
2744 let received_responses = client_services.app().received_coil_responses.borrow();
2745 assert!(received_responses.is_empty());
2746 }
2747
2748 #[test]
2750 fn test_ingest_frame_pdu_parse_failure() {
2751 let transport = MockTransport::default();
2752 let app = MockApp::default();
2753 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2754 let mut client_services =
2755 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2756 client_services.connect().unwrap();
2757
2758 let txn_id = 0x0001;
2759 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2760 let address = 0x0000;
2761 let quantity = 8;
2762 client_services
2763 .read_multiple_coils(txn_id, unit_id, address, quantity) .unwrap();
2765
2766 let response_adu = [
2770 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x01, 0xB3, 0x00,
2771 ]; client_services
2774 .transport
2775 .recv_frames
2776 .borrow_mut()
2777 .push_back(Vec::from_slice(&response_adu).unwrap())
2778 .unwrap();
2779 client_services.poll();
2780
2781 let received_responses = client_services.app().received_coil_responses.borrow();
2782 assert!(received_responses.is_empty());
2783 assert!(client_services.expected_responses.is_empty());
2785 }
2786
2787 #[test]
2789 fn test_client_services_read_single_coil_e2e_success() {
2790 let transport = MockTransport::default();
2791 let app = MockApp::default();
2792 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2793 let mut client_services =
2794 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2795 client_services.connect().unwrap();
2796
2797 let txn_id = 0x0002;
2798 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2799 let address = 0x0005;
2800
2801 client_services .read_single_coil(txn_id, unit_id, address)
2804 .unwrap();
2805
2806 let sent_adu = client_services
2808 .transport
2809 .sent_frames
2810 .borrow_mut()
2811 .pop_front()
2812 .unwrap();
2813 #[rustfmt::skip]
2815 let expected_adu: [u8; 12] = [
2816 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x05, 0x00, 0x01, ];
2824 assert_eq!(sent_adu.as_slice(), &expected_adu);
2825
2826 let response_adu = [0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x01];
2830
2831 client_services
2833 .transport
2834 .recv_frames
2835 .borrow_mut()
2836 .push_back(Vec::from_slice(&response_adu).unwrap())
2837 .unwrap();
2838 client_services.poll();
2839
2840 let received_responses = client_services.app().received_coil_responses.borrow();
2842 assert_eq!(received_responses.len(), 1);
2843
2844 let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2845 let rcv_quantity = rcv_coils.quantity();
2846 assert_eq!(*rcv_txn_id, txn_id);
2847 assert_eq!(*rcv_unit_id, unit_id);
2848 assert_eq!(rcv_coils.from_address(), address);
2849 assert_eq!(rcv_coils.quantity(), 1); assert_eq!(&rcv_coils.values()[..1], &[0x01]); assert_eq!(rcv_quantity, 1);
2852
2853 assert!(client_services.expected_responses.is_empty());
2855 }
2856
2857 #[test]
2859 fn test_read_single_coil_request_sends_valid_adu() {
2860 let transport = MockTransport::default();
2861 let app = MockApp::default();
2862 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2863 let mut client_services =
2864 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2865 client_services.connect().unwrap();
2866
2867 let txn_id = 0x0002;
2868 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2869 let address = 0x0005;
2870
2871 client_services
2872 .read_single_coil(txn_id, unit_id, address) .unwrap();
2874
2875 let sent_frames = client_services.transport.sent_frames.borrow();
2876 assert_eq!(sent_frames.len(), 1);
2877 let sent_adu = sent_frames.front().unwrap();
2878
2879 #[rustfmt::skip]
2881 let expected_adu: [u8; 12] = [
2882 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x05, 0x00, 0x01, ];
2890 assert_eq!(sent_adu.as_slice(), &expected_adu);
2891
2892 assert_eq!(client_services.expected_responses.len(), 1); let single_read = client_services.expected_responses[0]
2895 .operation_meta
2896 .is_single();
2897 assert!(single_read);
2898 }
2899
2900 #[test]
2902 fn test_write_single_coil_sends_valid_adu() {
2903 let transport = MockTransport::default();
2904 let app = MockApp::default();
2905 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2906 let mut client_services =
2907 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2908 client_services.connect().unwrap();
2909
2910 let txn_id = 0x0003;
2911 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2912 let address = 0x000A;
2913 let value = true;
2914
2915 client_services
2916 .write_single_coil(txn_id, unit_id, address, value) .unwrap();
2918
2919 let sent_frames = client_services.transport.sent_frames.borrow();
2920 assert_eq!(sent_frames.len(), 1);
2921 let sent_adu = sent_frames.front().unwrap();
2922
2923 #[rustfmt::skip]
2925 let expected_adu: [u8; 12] = [
2926 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, ];
2934 assert_eq!(sent_adu.as_slice(), &expected_adu);
2935
2936 assert_eq!(client_services.expected_responses.len(), 1);
2938 let expected_address = client_services.expected_responses[0]
2939 .operation_meta
2940 .address();
2941 let expected_value = client_services.expected_responses[0].operation_meta.value() != 0;
2942
2943 assert_eq!(expected_address, address);
2944 assert_eq!(expected_value, value);
2945 }
2946
2947 #[test]
2949 fn test_client_services_write_single_coil_e2e_success() {
2950 let transport = MockTransport::default();
2951 let app = MockApp::default();
2952 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2953 let mut client_services =
2954 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2955 client_services.connect().unwrap();
2956
2957 let txn_id = 0x0003;
2958 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2959 let address = 0x000A;
2960 let value = true;
2961
2962 client_services .write_single_coil(txn_id, unit_id, address, value)
2965 .unwrap();
2966
2967 let sent_adu = client_services
2969 .transport
2970 .sent_frames
2971 .borrow_mut()
2972 .pop_front()
2973 .unwrap();
2974 #[rustfmt::skip]
2975 let expected_request_adu: [u8; 12] = [
2976 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, ];
2984 assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2985
2986 let response_adu = [
2989 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00,
2990 ];
2991
2992 client_services
2994 .transport
2995 .recv_frames
2996 .borrow_mut()
2997 .push_back(Vec::from_slice(&response_adu).unwrap())
2998 .unwrap();
2999 client_services.poll();
3000
3001 let received_responses = client_services
3003 .app
3004 .received_write_single_coil_responses
3005 .borrow();
3006 assert_eq!(received_responses.len(), 1);
3007
3008 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
3009 assert_eq!(*rcv_txn_id, txn_id);
3010 assert_eq!(*rcv_unit_id, unit_id);
3011 assert_eq!(*rcv_address, address);
3012 assert_eq!(*rcv_value, value);
3013
3014 assert!(client_services.expected_responses.is_empty());
3016 }
3017
3018 #[test]
3020 fn test_write_multiple_coils_sends_valid_adu() {
3021 let transport = MockTransport::default();
3022 let app = MockApp::default();
3023 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3024 let mut client_services =
3025 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3026 client_services.connect().unwrap();
3027
3028 let txn_id = 0x0004;
3029 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3030 let address = 0x0000;
3031 let quantity = 10;
3032
3033 let mut values = Coils::new(address, quantity).unwrap();
3035 for i in 0..quantity {
3036 values.set_value(address + i, i % 2 == 0).unwrap();
3037 }
3038
3039 client_services
3040 .write_multiple_coils(txn_id, unit_id, address, &values) .unwrap();
3042
3043 let sent_frames = client_services.transport.sent_frames.borrow();
3044 assert_eq!(sent_frames.len(), 1);
3045 let sent_adu = sent_frames.front().unwrap();
3046
3047 #[rustfmt::skip]
3049 let expected_adu: [u8; 15] = [
3050 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0x55, 0x01, ];
3060 assert_eq!(sent_adu.as_slice(), &expected_adu);
3061
3062 assert_eq!(client_services.expected_responses.len(), 1);
3064 let expected_address = client_services.expected_responses[0]
3065 .operation_meta
3066 .address();
3067 let expected_quantity = client_services.expected_responses[0]
3068 .operation_meta
3069 .quantity();
3070 assert_eq!(expected_address, address);
3071 assert_eq!(expected_quantity, quantity);
3072 }
3073
3074 #[test]
3076 fn test_client_services_write_multiple_coils_e2e_success() {
3077 let transport = MockTransport::default();
3078 let app = MockApp::default();
3079 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3080 let mut client_services =
3081 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3082 client_services.connect().unwrap();
3083
3084 let txn_id = 0x0004;
3085 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3086 let address = 0x0000;
3087 let quantity = 10;
3088
3089 let mut values = Coils::new(address, quantity).unwrap();
3091 for i in 0..quantity {
3092 values.set_value(address + i, i % 2 == 0).unwrap();
3093 }
3094
3095 client_services .write_multiple_coils(txn_id, unit_id, address, &values)
3098 .unwrap();
3099
3100 let sent_adu = client_services
3102 .transport
3103 .sent_frames
3104 .borrow_mut()
3105 .pop_front()
3106 .unwrap();
3107 #[rustfmt::skip]
3108 let expected_request_adu: [u8; 15] = [
3109 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0x55, 0x01, ];
3119 assert_eq!(sent_adu.as_slice(), &expected_request_adu);
3120
3121 let response_adu = [
3124 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A,
3125 ];
3126
3127 client_services
3129 .transport
3130 .recv_frames
3131 .borrow_mut()
3132 .push_back(Vec::from_slice(&response_adu).unwrap())
3133 .unwrap();
3134 client_services.poll();
3135
3136 let received_responses = client_services
3138 .app
3139 .received_write_multiple_coils_responses
3140 .borrow();
3141 assert_eq!(received_responses.len(), 1);
3142
3143 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
3144 assert_eq!(*rcv_txn_id, txn_id);
3145 assert_eq!(*rcv_unit_id, unit_id);
3146 assert_eq!(*rcv_address, address);
3147 assert_eq!(*rcv_quantity, quantity);
3148
3149 assert!(client_services.expected_responses.is_empty());
3151 }
3152
3153 #[test]
3155 fn test_client_services_read_coils_e2e_success() {
3156 let transport = MockTransport::default();
3157 let app = MockApp::default();
3158 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3159 let mut client_services =
3160 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3161 client_services.connect().unwrap();
3162
3163 let txn_id = 0x0001;
3164 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3165 let address = 0x0000;
3166 let quantity = 8;
3167 client_services
3168 .read_multiple_coils(txn_id, unit_id, address, quantity) .unwrap();
3170
3171 let sent_adu = client_services
3173 .transport
3174 .sent_frames
3175 .borrow_mut()
3176 .pop_front()
3177 .unwrap(); assert_eq!(
3180 sent_adu.as_slice(),
3181 &[
3182 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08
3183 ]
3184 );
3185
3186 assert_eq!(client_services.expected_responses.len(), 1); let from_address = client_services.expected_responses[0]
3189 .operation_meta
3190 .address();
3191 let expected_quantity = client_services.expected_responses[0]
3192 .operation_meta
3193 .quantity();
3194
3195 assert_eq!(expected_quantity, quantity);
3196 assert_eq!(from_address, address);
3197
3198 let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
3202
3203 client_services
3205 .transport
3206 .recv_frames
3207 .borrow_mut()
3208 .push_back(Vec::from_slice(&response_adu).unwrap())
3209 .unwrap();
3210 client_services.poll(); let received_responses = client_services.app().received_coil_responses.borrow();
3216 assert_eq!(received_responses.len(), 1);
3217
3218 let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
3219 let rcv_quantity = rcv_coils.quantity();
3220 assert_eq!(*rcv_txn_id, txn_id);
3221 assert_eq!(*rcv_unit_id, unit_id);
3222 assert_eq!(rcv_coils.from_address(), address);
3223 assert_eq!(rcv_coils.quantity(), quantity);
3224 assert_eq!(&rcv_coils.values()[..1], &[0xB3]);
3225 assert_eq!(rcv_quantity, quantity);
3226
3227 assert!(client_services.expected_responses.is_empty());
3229 }
3230
3231 #[test]
3233 fn test_client_services_timeout_with_retry() {
3234 let transport = MockTransport::default();
3235 transport.recv_frames.borrow_mut().clear();
3237 let app = MockApp::default();
3238 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3239 tcp_config.response_timeout_ms = 100; tcp_config.retry_attempts = 1; let config = ModbusConfig::Tcp(tcp_config);
3242
3243 let mut client_services =
3244 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3245 client_services.connect().unwrap();
3246
3247 let txn_id = 0x0005;
3248 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3249 let address = 0x0000;
3250
3251 client_services
3252 .read_single_coil(txn_id, unit_id, address)
3253 .unwrap();
3254
3255 *client_services.app().current_time.borrow_mut() = 150;
3257 client_services.poll(); assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
3262 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;
3267 client_services.poll(); assert!(client_services.expected_responses.is_empty());
3272 }
3274
3275 #[test]
3277 fn test_client_services_concurrent_timeouts() {
3278 let transport = MockTransport::default();
3279 let app = MockApp::default();
3280
3281 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3283 tcp_config.response_timeout_ms = 100;
3284 tcp_config.retry_attempts = 1;
3285 let config = ModbusConfig::Tcp(tcp_config);
3286
3287 let mut client_services =
3288 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3289 client_services.connect().unwrap();
3290
3291 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3292
3293 client_services
3295 .read_single_coil(1, unit_id, 0x0000)
3296 .unwrap();
3297 client_services
3298 .read_single_coil(2, unit_id, 0x0001)
3299 .unwrap();
3300
3301 assert_eq!(client_services.expected_responses.len(), 2);
3303 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
3304
3305 *client_services.app().current_time.borrow_mut() = 150;
3307
3308 client_services.poll();
3310
3311 assert_eq!(client_services.expected_responses.len(), 2);
3313 assert_eq!(client_services.expected_responses[0].retries_left, 0);
3314 assert_eq!(client_services.expected_responses[1].retries_left, 0);
3315
3316 assert_eq!(client_services.transport.sent_frames.borrow().len(), 4);
3318
3319 *client_services.app().current_time.borrow_mut() = 300;
3321
3322 client_services.poll();
3324
3325 assert!(client_services.expected_responses.is_empty());
3327
3328 let failed_requests = client_services.app().failed_requests.borrow();
3330 assert_eq!(failed_requests.len(), 2);
3331
3332 let has_txn_1 = failed_requests
3334 .iter()
3335 .any(|(txn, _, err)| *txn == 1 && *err == MbusError::NoRetriesLeft);
3336 let has_txn_2 = failed_requests
3337 .iter()
3338 .any(|(txn, _, err)| *txn == 2 && *err == MbusError::NoRetriesLeft);
3339 assert!(has_txn_1, "Transaction 1 should have failed");
3340 assert!(has_txn_2, "Transaction 2 should have failed");
3341 }
3342
3343 #[test]
3344 fn test_poll_connection_loss_flushes_pending_requests() {
3345 let transport = MockTransport::default();
3346 let app = MockApp::default();
3347 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3348 let mut client_services =
3349 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3350 client_services.connect().unwrap();
3351
3352 let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
3353 client_services.read_single_coil(1, unit_id, 0).unwrap();
3354 client_services.read_single_coil(2, unit_id, 1).unwrap();
3355 assert_eq!(client_services.expected_responses.len(), 2);
3356
3357 *client_services.transport.is_connected_flag.borrow_mut() = false;
3358 *client_services.transport.recv_error.borrow_mut() = Some(MbusError::ConnectionClosed);
3359
3360 client_services.poll();
3361
3362 assert!(client_services.expected_responses.is_empty());
3363 assert_eq!(client_services.next_timeout_check, None);
3364
3365 let failed_requests = client_services.app().failed_requests.borrow();
3366 assert_eq!(failed_requests.len(), 2);
3367 assert!(
3368 failed_requests
3369 .iter()
3370 .all(|(txn, _, err)| (*txn == 1 || *txn == 2) && *err == MbusError::ConnectionLost)
3371 );
3372 }
3373
3374 #[test]
3375 fn test_fixed_backoff_schedules_and_does_not_retry_early() {
3376 let transport = MockTransport::default();
3377 let app = MockApp::default();
3378 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3379 tcp_config.response_timeout_ms = 100;
3380 tcp_config.retry_attempts = 1;
3381 tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 50 };
3382 let config = ModbusConfig::Tcp(tcp_config);
3383
3384 let mut client_services =
3385 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3386 client_services.connect().unwrap();
3387
3388 client_services
3389 .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3390 .unwrap();
3391 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
3392
3393 *client_services.app().current_time.borrow_mut() = 101;
3394 client_services.poll();
3395 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
3396 assert_eq!(
3397 client_services.expected_responses[0].next_retry_timestamp,
3398 Some(151)
3399 );
3400
3401 *client_services.app().current_time.borrow_mut() = 150;
3402 client_services.poll();
3403 assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
3404
3405 *client_services.app().current_time.borrow_mut() = 151;
3406 client_services.poll();
3407 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
3408 }
3409
3410 #[test]
3411 fn test_exponential_backoff_growth() {
3412 let transport = MockTransport::default();
3413 let app = MockApp::default();
3414 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3415 tcp_config.response_timeout_ms = 100;
3416 tcp_config.retry_attempts = 2;
3417 tcp_config.retry_backoff_strategy = BackoffStrategy::Exponential {
3418 base_delay_ms: 50,
3419 max_delay_ms: 500,
3420 };
3421 let config = ModbusConfig::Tcp(tcp_config);
3422
3423 let mut client_services =
3424 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3425 client_services.connect().unwrap();
3426
3427 client_services
3428 .read_single_coil(7, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3429 .unwrap();
3430
3431 *client_services.app().current_time.borrow_mut() = 101;
3432 client_services.poll();
3433 assert_eq!(
3434 client_services.expected_responses[0].next_retry_timestamp,
3435 Some(151)
3436 );
3437
3438 *client_services.app().current_time.borrow_mut() = 151;
3439 client_services.poll();
3440 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
3441
3442 *client_services.app().current_time.borrow_mut() = 252;
3443 client_services.poll();
3444 assert_eq!(
3445 client_services.expected_responses[0].next_retry_timestamp,
3446 Some(352)
3447 );
3448
3449 *client_services.app().current_time.borrow_mut() = 352;
3450 client_services.poll();
3451 assert_eq!(client_services.transport.sent_frames.borrow().len(), 3);
3452 }
3453
3454 #[test]
3455 fn test_jitter_bounds_with_random_source_lower_bound() {
3456 let transport = MockTransport::default();
3457 let app = MockApp::default();
3458 let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3459 tcp_config.response_timeout_ms = 100;
3460 tcp_config.retry_attempts = 1;
3461 tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
3462 tcp_config.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
3463 tcp_config.retry_random_fn = Some(rand_zero);
3464 let config = ModbusConfig::Tcp(tcp_config);
3465
3466 let mut client_services =
3467 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3468 client_services.connect().unwrap();
3469 client_services
3470 .read_single_coil(10, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3471 .unwrap();
3472
3473 *client_services.app().current_time.borrow_mut() = 101;
3474 client_services.poll();
3475 assert_eq!(
3476 client_services.expected_responses[0].next_retry_timestamp,
3477 Some(181)
3478 );
3479 }
3480
3481 #[test]
3482 fn test_jitter_bounds_with_random_source_upper_bound() {
3483 let transport3 = MockTransport::default();
3484 let app3 = MockApp::default();
3485 let mut tcp_config3 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3486 tcp_config3.response_timeout_ms = 100;
3487 tcp_config3.retry_attempts = 1;
3488 tcp_config3.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
3489 tcp_config3.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
3490 tcp_config3.retry_random_fn = Some(rand_upper_percent_20);
3491 let config3 = ModbusConfig::Tcp(tcp_config3);
3492
3493 let mut client_services3 =
3494 ClientServices::<MockTransport, MockApp, 10>::new(transport3, app3, config3).unwrap();
3495 client_services3.connect().unwrap();
3496 client_services3
3497 .read_single_coil(12, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3498 .unwrap();
3499
3500 *client_services3.app.current_time.borrow_mut() = 101;
3501 client_services3.poll();
3502 assert_eq!(
3503 client_services3.expected_responses[0].next_retry_timestamp,
3504 Some(221)
3505 );
3506 }
3507
3508 #[test]
3509 fn test_jitter_falls_back_without_random_source() {
3510 let transport2 = MockTransport::default();
3511 let app2 = MockApp::default();
3512 let mut tcp_config2 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
3513 tcp_config2.response_timeout_ms = 100;
3514 tcp_config2.retry_attempts = 1;
3515 tcp_config2.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
3516 tcp_config2.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
3517 tcp_config2.retry_random_fn = None;
3518 let config2 = ModbusConfig::Tcp(tcp_config2);
3519
3520 let mut client_services2 =
3521 ClientServices::<MockTransport, MockApp, 10>::new(transport2, app2, config2).unwrap();
3522 client_services2.connect().unwrap();
3523 client_services2
3524 .read_single_coil(11, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3525 .unwrap();
3526
3527 *client_services2.app.current_time.borrow_mut() = 101;
3528 client_services2.poll();
3529 assert_eq!(
3530 client_services2.expected_responses[0].next_retry_timestamp,
3531 Some(201)
3532 );
3533 }
3534
3535 #[test]
3536 fn test_serial_retry_scheduling_uses_backoff() {
3537 let transport = MockSerialTransport::default();
3538 let app = MockApp::default();
3539
3540 let serial_config = ModbusSerialConfig {
3541 port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
3542 mode: SerialMode::Rtu,
3543 baud_rate: BaudRate::Baud9600,
3544 data_bits: mbus_core::transport::DataBits::Eight,
3545 stop_bits: 1,
3546 parity: Parity::None,
3547 response_timeout_ms: 100,
3548 retry_attempts: 1,
3549 retry_backoff_strategy: BackoffStrategy::Fixed { delay_ms: 25 },
3550 retry_jitter_strategy: JitterStrategy::None,
3551 retry_random_fn: None,
3552 };
3553
3554 let mut client_services = ClientServices::<MockSerialTransport, MockApp, 1>::new(
3555 transport,
3556 app,
3557 ModbusConfig::Serial(serial_config),
3558 )
3559 .unwrap();
3560 client_services.connect().unwrap();
3561
3562 client_services
3563 .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
3564 .unwrap();
3565
3566 *client_services.app().current_time.borrow_mut() = 101;
3567 client_services.poll();
3568 assert_eq!(
3569 client_services.expected_responses[0].next_retry_timestamp,
3570 Some(126)
3571 );
3572
3573 *client_services.app().current_time.borrow_mut() = 126;
3574 client_services.poll();
3575 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
3576 }
3577
3578 #[test]
3580 fn test_too_many_requests_error() {
3581 let transport = MockTransport::default();
3582 let app = MockApp::default();
3583 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3584 let mut client_services =
3586 ClientServices::<MockTransport, MockApp, 1>::new(transport, app, config).unwrap();
3587 client_services.connect().unwrap();
3588
3589 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3590 client_services
3592 .read_multiple_coils(1, unit_id, 0, 1)
3593 .unwrap();
3594 assert_eq!(client_services.expected_responses.len(), 1);
3595
3596 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3597 let result = client_services.read_multiple_coils(2, unit_id, 0, 1);
3599 assert!(result.is_err());
3600 assert_eq!(result.unwrap_err(), MbusError::TooManyRequests);
3601 assert_eq!(client_services.expected_responses.len(), 1); }
3603
3604 #[test]
3606 fn test_read_holding_registers_sends_valid_adu() {
3607 let transport = MockTransport::default();
3608 let app = MockApp::default();
3609 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3610 let mut client_services =
3611 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3612 client_services.connect().unwrap();
3613
3614 let txn_id = 0x0005;
3615 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3616 let address = 0x0000;
3617 let quantity = 2;
3618 client_services
3619 .read_holding_registers(txn_id, unit_id, address, quantity)
3620 .unwrap();
3621
3622 let sent_frames = client_services.transport.sent_frames.borrow();
3623 assert_eq!(sent_frames.len(), 1);
3624 let sent_adu = sent_frames.front().unwrap();
3625
3626 #[rustfmt::skip]
3628 let expected_adu: [u8; 12] = [
3629 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, ];
3637 assert_eq!(sent_adu.as_slice(), &expected_adu);
3638 }
3639
3640 #[test]
3642 fn test_client_services_read_holding_registers_e2e_success() {
3643 let transport = MockTransport::default();
3644 let app = MockApp::default();
3645 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3646 let mut client_services =
3647 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3648 client_services.connect().unwrap();
3649
3650 let txn_id = 0x0005;
3651 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3652 let address = 0x0000;
3653 let quantity = 2;
3654 client_services
3655 .read_holding_registers(txn_id, unit_id, address, quantity)
3656 .unwrap();
3657
3658 let response_adu = [
3661 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x01, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78,
3662 ];
3663 client_services
3664 .transport
3665 .recv_frames
3666 .borrow_mut()
3667 .push_back(Vec::from_slice(&response_adu).unwrap())
3668 .unwrap();
3669 client_services.poll();
3670
3671 let received_responses = client_services
3672 .app
3673 .received_holding_register_responses
3674 .borrow();
3675 assert_eq!(received_responses.len(), 1);
3676 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3677 assert_eq!(*rcv_txn_id, txn_id);
3678 assert_eq!(*rcv_unit_id, unit_id);
3679 assert_eq!(rcv_registers.from_address(), address);
3680 assert_eq!(rcv_registers.quantity(), quantity);
3681 assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3682 assert_eq!(*rcv_quantity, quantity);
3683 assert!(client_services.expected_responses.is_empty());
3684 }
3685
3686 #[test]
3688 fn test_read_input_registers_sends_valid_adu() {
3689 let transport = MockTransport::default();
3690 let app = MockApp::default();
3691 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3692 let mut client_services =
3693 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3694 client_services.connect().unwrap();
3695
3696 let txn_id = 0x0006;
3697 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3698 let address = 0x0000;
3699 let quantity = 2;
3700 client_services
3701 .read_input_registers(txn_id, unit_id, address, quantity)
3702 .unwrap();
3703
3704 let sent_frames = client_services.transport.sent_frames.borrow();
3705 assert_eq!(sent_frames.len(), 1);
3706 let sent_adu = sent_frames.front().unwrap();
3707
3708 #[rustfmt::skip]
3710 let expected_adu: [u8; 12] = [
3711 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x00, 0x00, 0x02, ];
3719 assert_eq!(sent_adu.as_slice(), &expected_adu);
3720 }
3721
3722 #[test]
3724 fn test_client_services_read_input_registers_e2e_success() {
3725 let transport = MockTransport::default();
3726 let app = MockApp::default();
3727 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3728 let mut client_services =
3729 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3730 client_services.connect().unwrap();
3731
3732 let txn_id = 0x0006;
3733 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3734 let address = 0x0000;
3735 let quantity = 2;
3736 client_services
3737 .read_input_registers(txn_id, unit_id, address, quantity)
3738 .unwrap();
3739
3740 let response_adu = [
3743 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x01, 0x04, 0x04, 0xAA, 0xBB, 0xCC, 0xDD,
3744 ];
3745 client_services
3746 .transport
3747 .recv_frames
3748 .borrow_mut()
3749 .push_back(Vec::from_slice(&response_adu).unwrap())
3750 .unwrap();
3751 client_services.poll();
3752
3753 let received_responses = client_services
3754 .app
3755 .received_input_register_responses
3756 .borrow();
3757 assert_eq!(received_responses.len(), 1);
3758 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3759 assert_eq!(*rcv_txn_id, txn_id);
3760 assert_eq!(*rcv_unit_id, unit_id);
3761 assert_eq!(rcv_registers.from_address(), address);
3762 assert_eq!(rcv_registers.quantity(), quantity);
3763 assert_eq!(&rcv_registers.values()[..2], &[0xAABB, 0xCCDD]);
3764 assert_eq!(*rcv_quantity, quantity);
3765 assert!(client_services.expected_responses.is_empty());
3766 }
3767
3768 #[test]
3770 fn test_write_single_register_sends_valid_adu() {
3771 let transport = MockTransport::default();
3772 let app = MockApp::default();
3773 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3774 let mut client_services =
3775 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3776 client_services.connect().unwrap();
3777
3778 let txn_id = 0x0007;
3779 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3780 let address = 0x0001;
3781 let value = 0x1234;
3782 client_services
3783 .write_single_register(txn_id, unit_id, address, value)
3784 .unwrap();
3785
3786 let sent_frames = client_services.transport.sent_frames.borrow();
3787 assert_eq!(sent_frames.len(), 1);
3788 let sent_adu = sent_frames.front().unwrap();
3789
3790 #[rustfmt::skip]
3792 let expected_adu: [u8; 12] = [
3793 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34, ];
3801 assert_eq!(sent_adu.as_slice(), &expected_adu);
3802 }
3803
3804 #[test]
3806 fn test_client_services_write_single_register_e2e_success() {
3807 let transport = MockTransport::default();
3808 let app = MockApp::default();
3809 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3810 let mut client_services =
3811 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3812 client_services.connect().unwrap();
3813
3814 let txn_id = 0x0007;
3815 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3816 let address = 0x0001;
3817 let value = 0x1234;
3818 client_services
3819 .write_single_register(txn_id, unit_id, address, value)
3820 .unwrap();
3821
3822 let response_adu = [
3825 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34,
3826 ];
3827 client_services
3828 .transport
3829 .recv_frames
3830 .borrow_mut()
3831 .push_back(Vec::from_slice(&response_adu).unwrap())
3832 .unwrap();
3833 client_services.poll();
3834
3835 let received_responses = client_services
3836 .app
3837 .received_write_single_register_responses
3838 .borrow();
3839 assert_eq!(received_responses.len(), 1);
3840 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
3841 assert_eq!(*rcv_txn_id, txn_id);
3842 assert_eq!(*rcv_unit_id, unit_id);
3843 assert_eq!(*rcv_address, address);
3844 assert_eq!(*rcv_value, value);
3845 assert!(client_services.expected_responses.is_empty());
3846 }
3847
3848 #[test]
3850 fn test_write_multiple_registers_sends_valid_adu() {
3851 let transport = MockTransport::default();
3852 let app = MockApp::default();
3853 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3854 let mut client_services =
3855 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3856 client_services.connect().unwrap();
3857
3858 let txn_id = 0x0008;
3859 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3860 let address = 0x0001;
3861 let quantity = 2;
3862 let values = [0x1234, 0x5678];
3863 client_services
3864 .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3865 .unwrap();
3866
3867 let sent_frames = client_services.transport.sent_frames.borrow();
3868 assert_eq!(sent_frames.len(), 1);
3869 let sent_adu = sent_frames.front().unwrap();
3870
3871 #[rustfmt::skip]
3873 let expected_adu: [u8; 17] = [ 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x34, 0x56, 0x78, ];
3884 assert_eq!(sent_adu.as_slice(), &expected_adu);
3885 }
3886
3887 #[test]
3889 fn test_client_services_write_multiple_registers_e2e_success() {
3890 let transport = MockTransport::default();
3891 let app = MockApp::default();
3892 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3893 let mut client_services =
3894 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3895 client_services.connect().unwrap();
3896
3897 let txn_id = 0x0008;
3898 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3899 let address = 0x0001;
3900 let quantity = 2;
3901 let values = [0x1234, 0x5678];
3902 client_services
3903 .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3904 .unwrap();
3905
3906 let response_adu = [
3909 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02,
3910 ];
3911 client_services
3912 .transport
3913 .recv_frames
3914 .borrow_mut()
3915 .push_back(Vec::from_slice(&response_adu).unwrap())
3916 .unwrap();
3917 client_services.poll();
3918
3919 let received_responses = client_services
3920 .app
3921 .received_write_multiple_register_responses
3922 .borrow();
3923 assert_eq!(received_responses.len(), 1);
3924 let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
3925 assert_eq!(*rcv_txn_id, txn_id);
3926 assert_eq!(*rcv_unit_id, unit_id);
3927 assert_eq!(*rcv_address, address);
3928 assert_eq!(*rcv_quantity, quantity);
3929 assert!(client_services.expected_responses.is_empty());
3930 }
3931
3932 #[test]
3934 fn test_client_services_handles_exception_response() {
3935 let transport = MockTransport::default();
3936 let app = MockApp::default();
3937 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3938 let mut client_services =
3939 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3940 client_services.connect().unwrap();
3941
3942 let txn_id = 0x0009;
3943 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3944 let address = 0x0000;
3945 let quantity = 1;
3946
3947 client_services
3948 .read_holding_registers(txn_id, unit_id, address, quantity)
3949 .unwrap();
3950
3951 let exception_adu = [
3954 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x01, 0x83, 0x02, ];
3961 client_services
3962 .transport
3963 .recv_frames
3964 .borrow_mut()
3965 .push_back(Vec::from_slice(&exception_adu).unwrap())
3966 .unwrap();
3967 client_services.poll();
3968
3969 assert!(
3971 client_services
3972 .app
3973 .received_holding_register_responses
3974 .borrow()
3975 .is_empty()
3976 );
3977 assert_eq!(client_services.app().failed_requests.borrow().len(), 1);
3979 let (failed_txn, failed_unit, failed_err) =
3980 &client_services.app().failed_requests.borrow()[0];
3981 assert_eq!(*failed_txn, txn_id);
3982 assert_eq!(*failed_unit, unit_id);
3983 assert_eq!(*failed_err, MbusError::ModbusException(0x02));
3984 }
3985
3986 #[test]
3987 fn test_serial_exception_coil_response_fails_immediately_with_request_txn_id() {
3988 let mut client_services = make_serial_client();
3989
3990 let txn_id = 0x2001;
3991 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3992 let mut values = Coils::new(0x0000, 10).unwrap();
3993 values.set_value(0x0000, true).unwrap();
3994 values.set_value(0x0001, false).unwrap();
3995 values.set_value(0x0002, true).unwrap();
3996 values.set_value(0x0003, false).unwrap();
3997 values.set_value(0x0004, true).unwrap();
3998 values.set_value(0x0005, false).unwrap();
3999 values.set_value(0x0006, true).unwrap();
4000 values.set_value(0x0007, false).unwrap();
4001 values.set_value(0x0008, true).unwrap();
4002 values.set_value(0x0009, false).unwrap();
4003
4004 client_services
4005 .write_multiple_coils(txn_id, unit_id, 0x0000, &values)
4006 .unwrap();
4007
4008 let exception_adu = make_rtu_exception_adu(unit_id, 0x0F, 0x01);
4009 client_services
4010 .transport
4011 .recv_frames
4012 .borrow_mut()
4013 .push_back(exception_adu)
4014 .unwrap();
4015
4016 client_services.poll();
4017
4018 let failed = client_services.app().failed_requests.borrow();
4019 assert_eq!(failed.len(), 1);
4020 assert_eq!(failed[0].0, txn_id);
4021 assert_eq!(failed[0].1, unit_id);
4022 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
4023 assert!(
4024 client_services
4025 .app
4026 .received_write_multiple_coils_responses
4027 .borrow()
4028 .is_empty()
4029 );
4030 assert!(client_services.expected_responses.is_empty());
4031 }
4032
4033 #[test]
4034 fn test_serial_exception_register_response_fails_immediately_with_request_txn_id() {
4035 let mut client_services = make_serial_client();
4036
4037 let txn_id = 0x2002;
4038 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4039 client_services
4040 .read_holding_registers(txn_id, unit_id, 0x0000, 1)
4041 .unwrap();
4042
4043 let exception_adu = make_rtu_exception_adu(unit_id, 0x03, 0x02);
4044 client_services
4045 .transport
4046 .recv_frames
4047 .borrow_mut()
4048 .push_back(exception_adu)
4049 .unwrap();
4050
4051 client_services.poll();
4052
4053 let failed = client_services.app().failed_requests.borrow();
4054 assert_eq!(failed.len(), 1);
4055 assert_eq!(failed[0].0, txn_id);
4056 assert_eq!(failed[0].1, unit_id);
4057 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
4058 assert!(
4059 client_services
4060 .app
4061 .received_holding_register_responses
4062 .borrow()
4063 .is_empty()
4064 );
4065 assert!(client_services.expected_responses.is_empty());
4066 }
4067
4068 #[test]
4069 fn test_serial_exception_discrete_input_response_fails_immediately_with_request_txn_id() {
4070 let mut client_services = make_serial_client();
4071
4072 let txn_id = 0x2003;
4073 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4074 client_services
4075 .read_discrete_inputs(txn_id, unit_id, 0x0000, 8)
4076 .unwrap();
4077
4078 let exception_adu = make_rtu_exception_adu(unit_id, 0x02, 0x02);
4079 client_services
4080 .transport
4081 .recv_frames
4082 .borrow_mut()
4083 .push_back(exception_adu)
4084 .unwrap();
4085
4086 client_services.poll();
4087
4088 let failed = client_services.app().failed_requests.borrow();
4089 assert_eq!(failed.len(), 1);
4090 assert_eq!(failed[0].0, txn_id);
4091 assert_eq!(failed[0].1, unit_id);
4092 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
4093 assert!(
4094 client_services
4095 .app
4096 .received_discrete_input_responses
4097 .borrow()
4098 .is_empty()
4099 );
4100 assert!(client_services.expected_responses.is_empty());
4101 }
4102
4103 #[test]
4104 fn test_serial_exception_fifo_response_fails_immediately_with_request_txn_id() {
4105 let mut client_services = make_serial_client();
4106
4107 let txn_id = 0x2004;
4108 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4109 client_services
4110 .read_fifo_queue(txn_id, unit_id, 0x0001)
4111 .unwrap();
4112
4113 let exception_adu = make_rtu_exception_adu(unit_id, 0x18, 0x01);
4114 client_services
4115 .transport
4116 .recv_frames
4117 .borrow_mut()
4118 .push_back(exception_adu)
4119 .unwrap();
4120
4121 client_services.poll();
4122
4123 let failed = client_services.app().failed_requests.borrow();
4124 assert_eq!(failed.len(), 1);
4125 assert_eq!(failed[0].0, txn_id);
4126 assert_eq!(failed[0].1, unit_id);
4127 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
4128 assert!(
4129 client_services
4130 .app
4131 .received_read_fifo_queue_responses
4132 .borrow()
4133 .is_empty()
4134 );
4135 assert!(client_services.expected_responses.is_empty());
4136 }
4137
4138 #[test]
4139 fn test_serial_exception_file_record_response_fails_immediately_with_request_txn_id() {
4140 let mut client_services = make_serial_client();
4141
4142 let txn_id = 0x2005;
4143 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4144 let mut sub_req = SubRequest::new();
4145 sub_req.add_read_sub_request(4, 1, 2).unwrap();
4146 client_services
4147 .read_file_record(txn_id, unit_id, &sub_req)
4148 .unwrap();
4149
4150 let exception_adu = make_rtu_exception_adu(unit_id, 0x14, 0x02);
4151 client_services
4152 .transport
4153 .recv_frames
4154 .borrow_mut()
4155 .push_back(exception_adu)
4156 .unwrap();
4157
4158 client_services.poll();
4159
4160 let failed = client_services.app().failed_requests.borrow();
4161 assert_eq!(failed.len(), 1);
4162 assert_eq!(failed[0].0, txn_id);
4163 assert_eq!(failed[0].1, unit_id);
4164 assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
4165 assert!(
4166 client_services
4167 .app
4168 .received_read_file_record_responses
4169 .borrow()
4170 .is_empty()
4171 );
4172 assert!(client_services.expected_responses.is_empty());
4173 }
4174
4175 #[test]
4176 fn test_serial_exception_diagnostic_response_fails_immediately_with_request_txn_id() {
4177 let mut client_services = make_serial_client();
4178
4179 let txn_id = 0x2006;
4180 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4181 client_services
4182 .read_device_identification(
4183 txn_id,
4184 unit_id,
4185 ReadDeviceIdCode::Basic,
4186 ObjectId::from(0x00),
4187 )
4188 .unwrap();
4189
4190 let exception_adu = make_rtu_exception_adu(unit_id, 0x2B, 0x01);
4191 client_services
4192 .transport
4193 .recv_frames
4194 .borrow_mut()
4195 .push_back(exception_adu)
4196 .unwrap();
4197
4198 client_services.poll();
4199
4200 let failed = client_services.app().failed_requests.borrow();
4201 assert_eq!(failed.len(), 1);
4202 assert_eq!(failed[0].0, txn_id);
4203 assert_eq!(failed[0].1, unit_id);
4204 assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
4205 assert!(
4206 client_services
4207 .app
4208 .received_read_device_id_responses
4209 .borrow()
4210 .is_empty()
4211 );
4212 assert!(client_services.expected_responses.is_empty());
4213 }
4214
4215 #[test]
4217 fn test_read_single_holding_register_sends_valid_adu() {
4218 let transport = MockTransport::default();
4219 let app = MockApp::default();
4220 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4221 let mut client_services =
4222 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4223 client_services.connect().unwrap();
4224
4225 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4226 client_services
4227 .read_single_holding_register(10, unit_id, 100)
4228 .unwrap();
4229
4230 let sent_frames = client_services.transport.sent_frames.borrow();
4231 assert_eq!(sent_frames.len(), 1);
4232 let sent_adu = sent_frames.front().unwrap();
4233
4234 #[rustfmt::skip]
4235 let expected_adu: [u8; 12] = [
4236 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x64, 0x00, 0x01, ];
4244 assert_eq!(sent_adu.as_slice(), &expected_adu);
4245
4246 assert_eq!(client_services.expected_responses.len(), 1);
4248 let single_read = client_services.expected_responses[0]
4249 .operation_meta
4250 .is_single();
4251 assert!(single_read);
4252 }
4253
4254 #[test]
4256 fn test_client_services_read_single_holding_register_e2e_success() {
4257 let transport = MockTransport::default();
4258 let app = MockApp::default();
4259 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4260 let mut client_services =
4261 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4262 client_services.connect().unwrap();
4263
4264 let txn_id = 10;
4265 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4266 let address = 100;
4267
4268 client_services
4269 .read_single_holding_register(txn_id, unit_id, address)
4270 .unwrap();
4271
4272 let response_adu = [
4274 0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x03, 0x02, 0x12, 0x34,
4275 ];
4276 client_services
4277 .transport
4278 .recv_frames
4279 .borrow_mut()
4280 .push_back(Vec::from_slice(&response_adu).unwrap())
4281 .unwrap();
4282 client_services.poll();
4283
4284 let received_responses = client_services
4285 .app
4286 .received_holding_register_responses
4287 .borrow();
4288 assert_eq!(received_responses.len(), 1);
4289 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
4290 assert_eq!(*rcv_txn_id, txn_id);
4291 assert_eq!(*rcv_unit_id, unit_id);
4292 assert_eq!(rcv_registers.from_address(), address);
4293 assert_eq!(rcv_registers.quantity(), 1);
4294 assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
4295 assert_eq!(*rcv_quantity, 1);
4296 }
4297
4298 #[test]
4300 fn test_read_single_input_register_sends_valid_adu() {
4301 let transport = MockTransport::default();
4302 let app = MockApp::default();
4303 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4304 let mut client_services =
4305 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4306 client_services.connect().unwrap();
4307
4308 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4309 client_services
4310 .read_single_input_register(10, unit_id, 100)
4311 .unwrap();
4312
4313 let sent_frames = client_services.transport.sent_frames.borrow();
4314 assert_eq!(sent_frames.len(), 1);
4315 let sent_adu = sent_frames.front().unwrap();
4316
4317 #[rustfmt::skip]
4318 let expected_adu: [u8; 12] = [
4319 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x64, 0x00, 0x01, ];
4327 assert_eq!(sent_adu.as_slice(), &expected_adu);
4328
4329 assert_eq!(client_services.expected_responses.len(), 1);
4331 let single_read = client_services.expected_responses[0]
4332 .operation_meta
4333 .is_single();
4334 assert!(single_read);
4335 }
4336
4337 #[test]
4339 fn test_client_services_read_single_input_register_e2e_success() {
4340 let transport = MockTransport::default();
4341 let app = MockApp::default();
4342 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4343 let mut client_services =
4344 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4345 client_services.connect().unwrap();
4346
4347 let txn_id = 10;
4348 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4349 let address = 100;
4350
4351 client_services
4352 .read_single_input_register(txn_id, unit_id, address)
4353 .unwrap();
4354
4355 let response_adu = [
4358 0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x04, 0x02, 0x12, 0x34,
4359 ];
4360 client_services
4361 .transport
4362 .recv_frames
4363 .borrow_mut()
4364 .push_back(Vec::from_slice(&response_adu).unwrap())
4365 .unwrap();
4366 client_services.poll();
4367
4368 let received_responses = client_services
4369 .app
4370 .received_input_register_responses
4371 .borrow();
4372 assert_eq!(received_responses.len(), 1);
4373 let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
4374 assert_eq!(*rcv_txn_id, txn_id);
4375 assert_eq!(*rcv_unit_id, unit_id);
4376 assert_eq!(rcv_registers.from_address(), address);
4377 assert_eq!(rcv_registers.quantity(), 1);
4378 assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
4379 assert_eq!(*rcv_quantity, 1);
4380 }
4381
4382 #[test]
4384 fn test_read_write_multiple_registers_sends_valid_adu() {
4385 let transport = MockTransport::default();
4386 let app = MockApp::default();
4387 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4388 let mut client_services =
4389 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4390 client_services.connect().unwrap();
4391
4392 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4393 let write_values = [0xAAAA, 0xBBBB];
4394 client_services
4395 .read_write_multiple_registers(11, unit_id, 10, 2, 20, &write_values)
4396 .unwrap();
4397
4398 let sent_frames = client_services.transport.sent_frames.borrow();
4399 assert_eq!(sent_frames.len(), 1);
4400 let sent_adu = sent_frames.front().unwrap();
4401
4402 #[rustfmt::skip]
4403 let expected_adu: [u8; 21] = [
4404 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x17, 0x00, 0x0A, 0x00, 0x02, 0x00, 0x14, 0x00, 0x02, 0x04, 0xAA, 0xAA, 0xBB, 0xBB, ];
4417 assert_eq!(sent_adu.as_slice(), &expected_adu);
4418 }
4419
4420 #[test]
4422 fn test_mask_write_register_sends_valid_adu() {
4423 let transport = MockTransport::default();
4424 let app = MockApp::default();
4425 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4426 let mut client_services =
4427 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4428 client_services.connect().unwrap();
4429
4430 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4431 client_services
4432 .mask_write_register(12, unit_id, 30, 0xF0F0, 0x0F0F)
4433 .unwrap();
4434
4435 let sent_frames = client_services.transport.sent_frames.borrow();
4436 assert_eq!(sent_frames.len(), 1);
4437 let sent_adu = sent_frames.front().unwrap();
4438
4439 #[rustfmt::skip]
4440 let expected_adu: [u8; 14] = [
4441 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F, ];
4450 assert_eq!(sent_adu.as_slice(), &expected_adu);
4451 }
4452
4453 #[test]
4455 fn test_client_services_read_write_multiple_registers_e2e_success() {
4456 let transport = MockTransport::default();
4457 let app = MockApp::default();
4458 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4459 let mut client_services =
4460 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4461 client_services.connect().unwrap();
4462
4463 let txn_id = 11;
4464 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4465 let read_address = 10;
4466 let read_quantity = 2;
4467 let write_address = 20;
4468 let write_values = [0xAAAA, 0xBBBB];
4469
4470 client_services
4471 .read_write_multiple_registers(
4472 txn_id,
4473 unit_id,
4474 read_address,
4475 read_quantity,
4476 write_address,
4477 &write_values,
4478 )
4479 .unwrap();
4480
4481 let response_adu = [
4483 0x00, 0x0B, 0x00, 0x00, 0x00, 0x07, 0x01, 0x17, 0x04, 0x12, 0x34, 0x56, 0x78,
4484 ];
4485 client_services
4486 .transport
4487 .recv_frames
4488 .borrow_mut()
4489 .push_back(Vec::from_slice(&response_adu).unwrap())
4490 .unwrap();
4491 client_services.poll();
4492
4493 let received_responses = client_services
4494 .app
4495 .received_read_write_multiple_registers_responses
4496 .borrow();
4497 assert_eq!(received_responses.len(), 1);
4498 let (rcv_txn_id, rcv_unit_id, rcv_registers) = &received_responses[0];
4499 assert_eq!(*rcv_txn_id, txn_id);
4500 assert_eq!(*rcv_unit_id, unit_id);
4501 assert_eq!(rcv_registers.from_address(), read_address);
4502 assert_eq!(rcv_registers.quantity(), read_quantity);
4503 assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
4504 }
4505
4506 #[test]
4508 fn test_client_services_mask_write_register_e2e_success() {
4509 let transport = MockTransport::default();
4510 let app = MockApp::default();
4511 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4512 let mut client_services =
4513 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4514 client_services.connect().unwrap();
4515
4516 let txn_id = 12;
4517 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4518 let address = 30;
4519 let and_mask = 0xF0F0;
4520 let or_mask = 0x0F0F;
4521
4522 client_services
4523 .mask_write_register(txn_id, unit_id, address, and_mask, or_mask)
4524 .unwrap();
4525
4526 let response_adu = [
4528 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F,
4529 ];
4530 client_services
4531 .transport
4532 .recv_frames
4533 .borrow_mut()
4534 .push_back(Vec::from_slice(&response_adu).unwrap())
4535 .unwrap();
4536 client_services.poll();
4537
4538 let received_responses = client_services
4539 .app
4540 .received_mask_write_register_responses
4541 .borrow();
4542 assert_eq!(received_responses.len(), 1);
4543 let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
4544 assert_eq!(*rcv_txn_id, txn_id);
4545 assert_eq!(*rcv_unit_id, unit_id);
4546 }
4547
4548 #[test]
4550 fn test_client_services_read_fifo_queue_e2e_success() {
4551 let transport = MockTransport::default();
4552 let app = MockApp::default();
4553 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4554 let mut client_services =
4555 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4556 client_services.connect().unwrap();
4557
4558 let txn_id = 13;
4559 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4560 let address = 40;
4561
4562 client_services
4563 .read_fifo_queue(txn_id, unit_id, address)
4564 .unwrap();
4565
4566 #[rustfmt::skip]
4568 let response_adu = [
4569 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x18, 0x00, 0x06, 0x00, 0x02, 0xAA, 0xAA, 0xBB, 0xBB, ];
4579 client_services
4580 .transport
4581 .recv_frames
4582 .borrow_mut()
4583 .push_back(Vec::from_slice(&response_adu).unwrap())
4584 .unwrap();
4585 client_services.poll();
4586
4587 let received_responses = client_services
4588 .app
4589 .received_read_fifo_queue_responses
4590 .borrow();
4591 assert_eq!(received_responses.len(), 1);
4592 let (rcv_txn_id, rcv_unit_id, rcv_fifo_queue) = &received_responses[0];
4593 assert_eq!(*rcv_txn_id, txn_id);
4594 assert_eq!(*rcv_unit_id, unit_id);
4595 assert_eq!(rcv_fifo_queue.length(), 2);
4596 assert_eq!(&rcv_fifo_queue.queue()[..2], &[0xAAAA, 0xBBBB]);
4597 }
4598
4599 #[test]
4601 fn test_client_services_read_file_record_e2e_success() {
4602 let transport = MockTransport::default();
4603 let app = MockApp::default();
4604 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4605 let mut client_services =
4606 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4607 client_services.connect().unwrap();
4608
4609 let txn_id = 14;
4610 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4611 let mut sub_req = SubRequest::new();
4612 sub_req.add_read_sub_request(4, 1, 2).unwrap();
4613
4614 client_services
4615 .read_file_record(txn_id, unit_id, &sub_req)
4616 .unwrap();
4617
4618 let response_adu = [
4624 0x00, 0x0E, 0x00, 0x00, 0x00, 0x09, 0x01, 0x14, 0x06, 0x05, 0x06, 0x12, 0x34, 0x56,
4625 0x78,
4626 ];
4627
4628 client_services
4629 .transport
4630 .recv_frames
4631 .borrow_mut()
4632 .push_back(Vec::from_slice(&response_adu).unwrap())
4633 .unwrap();
4634 client_services.poll();
4635
4636 let received_responses = client_services
4637 .app
4638 .received_read_file_record_responses
4639 .borrow();
4640 assert_eq!(received_responses.len(), 1);
4641 let (rcv_txn_id, rcv_unit_id, rcv_data) = &received_responses[0];
4642 assert_eq!(*rcv_txn_id, txn_id);
4643 assert_eq!(*rcv_unit_id, unit_id);
4644 assert_eq!(rcv_data.len(), 1);
4645 assert_eq!(
4646 rcv_data[0].record_data.as_ref().unwrap().as_slice(),
4647 &[0x1234, 0x5678]
4648 );
4649 }
4650
4651 #[test]
4653 fn test_client_services_write_file_record_e2e_success() {
4654 let transport = MockTransport::default();
4655 let app = MockApp::default();
4656 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4657 let mut client_services =
4658 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4659 client_services.connect().unwrap();
4660
4661 let txn_id = 15;
4662 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4663 let mut sub_req = SubRequest::new();
4664 let mut data = Vec::new();
4665 data.push(0x1122).unwrap();
4666 sub_req.add_write_sub_request(4, 1, 1, data).unwrap();
4667
4668 client_services
4669 .write_file_record(txn_id, unit_id, &sub_req)
4670 .unwrap();
4671
4672 let response_adu = [
4675 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0C, 0x01, 0x15, 0x09, 0x06, 0x00, 0x04, 0x00, 0x01,
4676 0x00, 0x01, 0x11, 0x22,
4677 ];
4678
4679 client_services
4680 .transport
4681 .recv_frames
4682 .borrow_mut()
4683 .push_back(Vec::from_slice(&response_adu).unwrap())
4684 .unwrap();
4685 client_services.poll();
4686
4687 let received_responses = client_services
4688 .app
4689 .received_write_file_record_responses
4690 .borrow();
4691 assert_eq!(received_responses.len(), 1);
4692 let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
4693 assert_eq!(*rcv_txn_id, txn_id);
4694 assert_eq!(*rcv_unit_id, unit_id);
4695 }
4696
4697 #[test]
4699 fn test_client_services_read_discrete_inputs_e2e_success() {
4700 let transport = MockTransport::default();
4701 let app = MockApp::default();
4702 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4703 let mut client_services =
4704 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4705 client_services.connect().unwrap();
4706
4707 let txn_id = 16;
4708 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4709 let address = 50;
4710 let quantity = 8;
4711
4712 client_services
4713 .read_discrete_inputs(txn_id, unit_id, address, quantity)
4714 .unwrap();
4715
4716 let response_adu = [0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0xAA];
4718
4719 client_services
4720 .transport
4721 .recv_frames
4722 .borrow_mut()
4723 .push_back(Vec::from_slice(&response_adu).unwrap())
4724 .unwrap();
4725 client_services.poll();
4726
4727 let received_responses = client_services
4728 .app
4729 .received_discrete_input_responses
4730 .borrow();
4731 assert_eq!(received_responses.len(), 1);
4732 let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
4733 assert_eq!(*rcv_txn_id, txn_id);
4734 assert_eq!(*rcv_unit_id, unit_id);
4735 assert_eq!(rcv_inputs.from_address(), address);
4736 assert_eq!(rcv_inputs.quantity(), quantity);
4737 assert_eq!(rcv_inputs.values(), &[0xAA]);
4738 assert_eq!(*rcv_quantity, quantity);
4739 }
4740
4741 #[test]
4743 fn test_client_services_read_single_discrete_input_e2e_success() {
4744 let transport = MockTransport::default();
4745 let app = MockApp::default();
4746 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4747 let mut client_services =
4748 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4749 client_services.connect().unwrap();
4750
4751 let txn_id = 17;
4752 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4753 let address = 10;
4754
4755 client_services
4756 .read_single_discrete_input(txn_id, unit_id, address)
4757 .unwrap();
4758
4759 let sent_frames = client_services.transport.sent_frames.borrow();
4761 assert_eq!(sent_frames.len(), 1);
4762 let expected_request = [
4766 0x00, 0x11, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x0A, 0x00, 0x01,
4767 ];
4768 assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
4769 drop(sent_frames);
4770
4771 let response_adu = [0x00, 0x11, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0x01];
4773
4774 client_services
4775 .transport
4776 .recv_frames
4777 .borrow_mut()
4778 .push_back(Vec::from_slice(&response_adu).unwrap())
4779 .unwrap();
4780 client_services.poll();
4781
4782 let received_responses = client_services
4783 .app
4784 .received_discrete_input_responses
4785 .borrow();
4786 assert_eq!(received_responses.len(), 1);
4787 let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
4788 assert_eq!(*rcv_txn_id, txn_id);
4789 assert_eq!(*rcv_unit_id, unit_id);
4790 assert_eq!(rcv_inputs.from_address(), address);
4791 assert_eq!(rcv_inputs.quantity(), 1);
4792 assert_eq!(rcv_inputs.value(address).unwrap(), true);
4793 assert_eq!(*rcv_quantity, 1);
4794 }
4795
4796 #[test]
4798 fn test_client_services_read_device_identification_e2e_success() {
4799 let transport = MockTransport::default();
4800 let app = MockApp::default();
4801 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4802 let mut client_services =
4803 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4804 client_services.connect().unwrap();
4805
4806 let txn_id = 20;
4807 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4808 let read_code = ReadDeviceIdCode::Basic;
4809 let object_id = ObjectId::from(0x00);
4810
4811 client_services
4812 .read_device_identification(txn_id, unit_id, read_code, object_id)
4813 .unwrap();
4814
4815 let sent_frames = client_services.transport.sent_frames.borrow();
4817 assert_eq!(sent_frames.len(), 1);
4818 let expected_request = [
4822 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2B, 0x0E, 0x01, 0x00,
4823 ];
4824 assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
4825 drop(sent_frames);
4826
4827 let response_adu = [
4832 0x00, 0x14, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x01,
4833 0x00, 0x03, 0x46, 0x6F, 0x6F,
4834 ];
4835
4836 client_services
4837 .transport
4838 .recv_frames
4839 .borrow_mut()
4840 .push_back(Vec::from_slice(&response_adu).unwrap())
4841 .unwrap();
4842 client_services.poll();
4843
4844 let received_responses = client_services
4845 .app
4846 .received_read_device_id_responses
4847 .borrow();
4848 assert_eq!(received_responses.len(), 1);
4849 let (rcv_txn_id, rcv_unit_id, rcv_resp) = &received_responses[0];
4850 assert_eq!(*rcv_txn_id, txn_id);
4851 assert_eq!(*rcv_unit_id, unit_id);
4852 assert_eq!(rcv_resp.read_device_id_code, ReadDeviceIdCode::Basic);
4853 assert_eq!(
4854 rcv_resp.conformity_level,
4855 ConformityLevel::BasicStreamAndIndividual
4856 );
4857 assert_eq!(rcv_resp.number_of_objects, 1);
4858
4859 assert_eq!(&rcv_resp.objects_data[..5], &[0x00, 0x03, 0x46, 0x6F, 0x6F]);
4861 }
4862
4863 #[test]
4865 fn test_client_services_read_device_identification_multi_transaction() {
4866 let transport = MockTransport::default();
4867 let app = MockApp::default();
4868 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4869 let mut client_services =
4870 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4871 client_services.connect().unwrap();
4872
4873 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4874 let txn_id_1 = 21;
4876 client_services
4877 .read_device_identification(
4878 txn_id_1,
4879 unit_id,
4880 ReadDeviceIdCode::Basic,
4881 ObjectId::from(0x00),
4882 )
4883 .unwrap();
4884
4885 let txn_id_2 = 22;
4887 client_services
4888 .read_device_identification(
4889 txn_id_2,
4890 unit_id,
4891 ReadDeviceIdCode::Regular,
4892 ObjectId::from(0x00),
4893 )
4894 .unwrap();
4895
4896 assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
4897
4898 let response_adu_2 = [
4902 0x00, 0x16, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x82, 0x00, 0x00, 0x00,
4903 ];
4904 client_services
4905 .transport
4906 .recv_frames
4907 .borrow_mut()
4908 .push_back(Vec::from_slice(&response_adu_2).unwrap())
4909 .unwrap();
4910
4911 client_services.poll();
4912
4913 {
4914 let received_responses = client_services
4915 .app
4916 .received_read_device_id_responses
4917 .borrow();
4918 assert_eq!(received_responses.len(), 1);
4919 assert_eq!(received_responses[0].0, txn_id_2);
4920 assert_eq!(
4921 received_responses[0].2.read_device_id_code,
4922 ReadDeviceIdCode::Regular
4923 );
4924 }
4925
4926 let response_adu_1 = [
4929 0x00, 0x15, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x00,
4930 ];
4931 client_services
4932 .transport
4933 .recv_frames
4934 .borrow_mut()
4935 .push_back(Vec::from_slice(&response_adu_1).unwrap())
4936 .unwrap();
4937
4938 client_services.poll();
4939
4940 {
4941 let received_responses = client_services
4942 .app
4943 .received_read_device_id_responses
4944 .borrow();
4945 assert_eq!(received_responses.len(), 2);
4946 assert_eq!(received_responses[1].0, txn_id_1);
4947 assert_eq!(
4948 received_responses[1].2.read_device_id_code,
4949 ReadDeviceIdCode::Basic
4950 );
4951 }
4952 }
4953
4954 #[test]
4956 fn test_client_services_read_device_identification_mismatch_code() {
4957 let transport = MockTransport::default();
4958 let app = MockApp::default();
4959 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4960 let mut client_services =
4961 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4962 client_services.connect().unwrap();
4963
4964 let txn_id = 30;
4965 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4966 client_services
4968 .read_device_identification(
4969 txn_id,
4970 unit_id,
4971 ReadDeviceIdCode::Basic,
4972 ObjectId::from(0x00),
4973 )
4974 .unwrap();
4975
4976 let response_adu = [
4979 0x00, 0x1E, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x81, 0x00, 0x00, 0x00,
4980 ];
4981
4982 client_services
4983 .transport
4984 .recv_frames
4985 .borrow_mut()
4986 .push_back(Vec::from_slice(&response_adu).unwrap())
4987 .unwrap();
4988
4989 client_services.poll();
4990
4991 assert!(
4993 client_services
4994 .app
4995 .received_read_device_id_responses
4996 .borrow()
4997 .is_empty()
4998 );
4999
5000 let failed = client_services.app().failed_requests.borrow();
5002 assert_eq!(failed.len(), 1);
5003 assert_eq!(failed[0].2, MbusError::InvalidDeviceIdentification);
5004 }
5005
5006 #[test]
5008 fn test_client_services_read_exception_status_e2e_success() {
5009 let transport = MockTransport::default();
5010 let app = MockApp::default();
5011 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5012 let mut client_services =
5013 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5014 client_services.connect().unwrap();
5015
5016 let txn_id = 40;
5017 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
5018
5019 let err = client_services.read_exception_status(txn_id, unit_id).err();
5020 assert_eq!(err, Some(MbusError::InvalidTransport));
5022 }
5023
5024 #[test]
5026 fn test_client_services_diagnostics_query_data_success() {
5027 let transport = MockTransport::default();
5028 let app = MockApp::default();
5029 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5030 let mut client_services =
5031 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5032 client_services.connect().unwrap();
5033
5034 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
5035 let data = [0x1234, 0x5678];
5036 let sub_function = DiagnosticSubFunction::ReturnQueryData;
5037 let err = client_services
5038 .diagnostics(50, unit_id, sub_function, &data)
5039 .err();
5040 assert_eq!(err, Some(MbusError::InvalidTransport));
5041 }
5042
5043 #[test]
5045 fn test_client_services_get_comm_event_counter_success() {
5046 let transport = MockTransport::default();
5047 let app = MockApp::default();
5048 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5049 let mut client_services =
5050 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5051 client_services.connect().unwrap();
5052 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
5053 let err = client_services.get_comm_event_counter(60, unit_id).err();
5054
5055 assert_eq!(err, Some(MbusError::InvalidTransport));
5056 }
5057
5058 #[test]
5060 fn test_client_services_report_server_id_success() {
5061 let transport = MockTransport::default();
5062 let app = MockApp::default();
5063 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5064 let mut client_services =
5065 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5066 client_services.connect().unwrap();
5067
5068 let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
5069 let err = client_services.report_server_id(70, unit_id).err();
5070
5071 assert_eq!(err, Some(MbusError::InvalidTransport));
5072 }
5073
5074 #[test]
5078 fn test_broadcast_read_multiple_coils_not_allowed() {
5079 let transport = MockTransport::default();
5080 let app = MockApp::default();
5081 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5082 let mut client_services =
5083 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5084 client_services.connect().unwrap();
5085
5086 let txn_id = 0x0001;
5087 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
5088 let address = 0x0000;
5089 let quantity = 8;
5090 let res = client_services.read_multiple_coils(txn_id, unit_id, address, quantity);
5091 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
5092 }
5093
5094 #[test]
5096 fn test_broadcast_write_single_coil_tcp_not_allowed() {
5097 let transport = MockTransport::default();
5098 let app = MockApp::default();
5099 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5100 let mut client_services =
5101 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5102 client_services.connect().unwrap();
5103
5104 let txn_id = 0x0002;
5105 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
5106 let res = client_services.write_single_coil(txn_id, unit_id, 0x0000, true);
5107 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
5108 }
5109
5110 #[test]
5112 fn test_broadcast_write_multiple_coils_tcp_not_allowed() {
5113 let transport = MockTransport::default();
5114 let app = MockApp::default();
5115 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5116 let mut client_services =
5117 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5118 client_services.connect().unwrap();
5119
5120 let txn_id = 0x0003;
5121 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
5122 let mut values = Coils::new(0x0000, 2).unwrap();
5123 values.set_value(0x0000, true).unwrap();
5124 values.set_value(0x0001, false).unwrap();
5125
5126 let res = client_services.write_multiple_coils(txn_id, unit_id, 0x0000, &values);
5127 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
5128 }
5129
5130 #[test]
5132 fn test_broadcast_read_discrete_inputs_not_allowed() {
5133 let transport = MockTransport::default();
5134 let app = MockApp::default();
5135 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5136 let mut client_services =
5137 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5138 client_services.connect().unwrap();
5139
5140 let txn_id = 0x0006;
5141 let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
5142 let res = client_services.read_discrete_inputs(txn_id, unit_id, 0x0000, 2);
5143 assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
5144 }
5145
5146 #[test]
5149 fn test_client_services_clears_buffer_on_overflow() {
5150 let transport = MockTransport::default();
5151 let app = MockApp::default();
5152 let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
5153 let mut client_services =
5154 ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
5155 client_services.connect().unwrap();
5156
5157 let initial_garbage = [0xFF; MAX_ADU_FRAME_LEN - 10];
5159 client_services
5160 .rxed_frame
5161 .extend_from_slice(&initial_garbage)
5162 .unwrap();
5163
5164 let chunk = [0xAA; 20];
5166 client_services
5167 .transport
5168 .recv_frames
5169 .borrow_mut()
5170 .push_back(Vec::from_slice(&chunk).unwrap())
5171 .unwrap();
5172
5173 client_services.poll();
5175
5176 assert!(
5177 client_services.rxed_frame.is_empty(),
5178 "Buffer should be cleared on overflow to prevent crashing and recover from stream noise."
5179 );
5180 }
5181}