Skip to main content

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}