oms_modbus/options.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//!
3//! Shared client options — capture, reconnect, timeout.
4//!
5//! Used by all transport clients for a consistent API.
6
7use std::sync::Arc;
8use std::time::Duration;
9
10use crate::bus_timing::BusTiming;
11use crate::reconnect::ReconnectConfig;
12use crate::wire_tap::WireTap;
13
14/// Common options for all Modbus clients — timeout, WireTap, reconnect, timing.
15///
16/// # Examples
17///
18/// ```no_run
19/// use oms_modbus::*;
20/// use std::time::Duration;
21///
22/// let opts = ClientOptions::default()
23/// .with_timeout(Duration::from_secs(3))
24/// .with_tap(std::sync::Arc::new(BusCapture::unbounded()))
25/// .with_reconnect(3, Duration::from_millis(100));
26///
27/// # let port = tokio::io::duplex(64).0;
28/// let client = rtu::with_options(port, opts);
29/// ```
30#[derive(Clone)]
31pub struct ClientOptions {
32 pub(crate) timeout: Duration,
33 pub(crate) tap: Option<Arc<dyn WireTap>>,
34 pub(crate) reconnect: Option<ReconnectConfig>,
35 pub(crate) bus_timing: Option<Arc<BusTiming>>,
36 pub(crate) data_channel_capacity: Option<usize>,
37 pub(crate) tap_channel_capacity: Option<usize>,
38}
39
40impl Default for ClientOptions {
41 fn default() -> Self {
42 Self {
43 timeout: Duration::from_secs(1),
44 tap: None,
45 reconnect: None,
46 bus_timing: Some(Arc::new(BusTiming::rtu_35t(9600))),
47 data_channel_capacity: None,
48 tap_channel_capacity: None,
49 }
50 }
51}
52
53impl ClientOptions {
54 /// Create a new `ClientOptions` with default values (1s timeout, 3.5T @ 9600 baud).
55 pub fn new() -> Self {
56 Self::default()
57 }
58 /// Current request timeout.
59 pub fn timeout(&self) -> Duration {
60 self.timeout
61 }
62 /// Configured reconnect parameters, if any.
63 pub fn reconnect(&self) -> Option<&ReconnectConfig> {
64 self.reconnect.as_ref()
65 }
66 /// Configured bus timing, if any.
67 pub fn bus_timing(&self) -> Option<&Arc<BusTiming>> {
68 self.bus_timing.as_ref()
69 }
70 /// Attached WireTap, if any.
71 pub fn tap(&self) -> Option<&Arc<dyn WireTap>> {
72 self.tap.as_ref()
73 }
74
75 /// Set the per-request timeout. Applied to both send and receive.
76 pub fn with_timeout(mut self, timeout: Duration) -> Self {
77 self.timeout = timeout;
78 self
79 }
80
81 /// Attach any [`WireTap`] — `BusCapture`, `FileRecorder`, or custom.
82 pub fn with_tap(mut self, tap: Arc<dyn WireTap>) -> Self {
83 self.tap = Some(tap);
84 self
85 }
86
87 /// Enable auto-reconnect on transport errors with a fixed retry interval.
88 /// `max_retries = 0` means infinite.
89 pub fn with_reconnect(mut self, max_retries: u32, interval: Duration) -> Self {
90 self.reconnect = Some(ReconnectConfig::new(max_retries, interval));
91 self
92 }
93
94 /// Enforce minimum spacing between Modbus frames.
95 ///
96 /// Prevents sending too fast on RS-485 buses where low-performance
97 /// slave devices may crash. Use [`BusTiming::rtu_35t`] for standard
98 /// RTU/ASCII timing, or [`BusTiming::custom`] for a fixed delay.
99 pub fn with_bus_timing(mut self, timing: BusTiming) -> Self {
100 self.bus_timing = Some(Arc::new(timing));
101 self
102 }
103
104 /// Set the SniffIo data channel capacity in bytes (default: 1 MB).
105 ///
106 /// Only meaningful when a tap is attached. The data channel buffers
107 /// bytes between the background reader and `poll_read`. When full,
108 /// oldest chunks are evicted.
109 pub fn with_data_channel_capacity(mut self, bytes: usize) -> Self {
110 self.data_channel_capacity = Some(bytes);
111 self
112 }
113
114 /// Set the SniffIo tap channel capacity in bytes (default: 256 KB).
115 ///
116 /// Only meaningful when a tap is attached. The tap channel buffers
117 /// bytes for the tap forwarder task. When full, oldest chunks are evicted.
118 pub fn with_tap_channel_capacity(mut self, bytes: usize) -> Self {
119 self.tap_channel_capacity = Some(bytes);
120 self
121 }
122}