Skip to main content

mabi_modbus/
config.rs

1//! Modbus configuration types.
2
3use std::net::SocketAddr;
4use std::time::Duration;
5
6use mabi_core::tags::Tags;
7use serde::{Deserialize, Serialize};
8
9/// Modbus TCP server configuration.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ModbusServerConfig {
12    /// Bind address.
13    #[serde(default = "default_bind_address")]
14    pub bind_address: SocketAddr,
15
16    /// Maximum concurrent connections.
17    #[serde(default = "default_max_connections")]
18    pub max_connections: usize,
19
20    /// Connection timeout.
21    #[serde(default = "default_timeout_secs")]
22    pub timeout_secs: u64,
23
24    /// Enable TCP keep-alive.
25    #[serde(default = "default_true")]
26    pub keep_alive: bool,
27
28    /// TCP nodelay (disable Nagle algorithm).
29    #[serde(default = "default_true")]
30    pub tcp_nodelay: bool,
31
32    /// Maximum requests per second per connection (0 = unlimited).
33    #[serde(default)]
34    pub rate_limit: u32,
35}
36
37fn default_bind_address() -> SocketAddr {
38    "0.0.0.0:502".parse().unwrap()
39}
40
41fn default_max_connections() -> usize {
42    1000
43}
44
45fn default_timeout_secs() -> u64 {
46    30
47}
48
49fn default_true() -> bool {
50    true
51}
52
53impl Default for ModbusServerConfig {
54    fn default() -> Self {
55        Self {
56            bind_address: default_bind_address(),
57            max_connections: default_max_connections(),
58            timeout_secs: default_timeout_secs(),
59            keep_alive: true,
60            tcp_nodelay: true,
61            rate_limit: 0,
62        }
63    }
64}
65
66impl ModbusServerConfig {
67    /// Create a new config with the specified bind address.
68    pub fn with_bind_address(mut self, addr: SocketAddr) -> Self {
69        self.bind_address = addr;
70        self
71    }
72
73    /// Set maximum connections.
74    pub fn with_max_connections(mut self, max: usize) -> Self {
75        self.max_connections = max;
76        self
77    }
78
79    /// Get timeout as Duration.
80    pub fn timeout(&self) -> Duration {
81        Duration::from_secs(self.timeout_secs)
82    }
83}
84
85/// Modbus device configuration.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ModbusDeviceConfig {
88    /// Unit ID (1-247).
89    pub unit_id: u8,
90
91    /// Device name.
92    pub name: String,
93
94    /// Number of coils.
95    #[serde(default = "default_coils")]
96    pub coils: u16,
97
98    /// Number of discrete inputs.
99    #[serde(default = "default_discrete_inputs")]
100    pub discrete_inputs: u16,
101
102    /// Number of holding registers.
103    #[serde(default = "default_holding_registers")]
104    pub holding_registers: u16,
105
106    /// Number of input registers.
107    #[serde(default = "default_input_registers")]
108    pub input_registers: u16,
109
110    /// Response delay in milliseconds (for simulation).
111    #[serde(default)]
112    pub response_delay_ms: u64,
113
114    /// Device tags for organization and filtering.
115    #[serde(default, skip_serializing_if = "Tags::is_empty")]
116    pub tags: Tags,
117}
118
119fn default_coils() -> u16 {
120    10000
121}
122
123fn default_discrete_inputs() -> u16 {
124    10000
125}
126
127fn default_holding_registers() -> u16 {
128    10000
129}
130
131fn default_input_registers() -> u16 {
132    10000
133}
134
135impl Default for ModbusDeviceConfig {
136    fn default() -> Self {
137        Self {
138            unit_id: 1,
139            name: "Modbus Device".to_string(),
140            coils: default_coils(),
141            discrete_inputs: default_discrete_inputs(),
142            holding_registers: default_holding_registers(),
143            input_registers: default_input_registers(),
144            response_delay_ms: 0,
145            tags: Tags::new(),
146        }
147    }
148}
149
150impl ModbusDeviceConfig {
151    /// Create a new device config with the specified unit ID.
152    pub fn new(unit_id: u8, name: impl Into<String>) -> Self {
153        Self {
154            unit_id,
155            name: name.into(),
156            ..Default::default()
157        }
158    }
159
160    /// Set response delay.
161    pub fn with_response_delay(mut self, delay_ms: u64) -> Self {
162        self.response_delay_ms = delay_ms;
163        self
164    }
165
166    /// Set tags.
167    pub fn with_tags(mut self, tags: Tags) -> Self {
168        self.tags = tags;
169        self
170    }
171
172    /// Add a single tag.
173    pub fn with_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
174        self.tags.insert(key.into(), value.into());
175        self
176    }
177
178    /// Add a label.
179    pub fn with_label(mut self, label: impl Into<String>) -> Self {
180        self.tags.add_label(label.into());
181        self
182    }
183
184    /// Validate unit ID (1-247).
185    pub fn validate(&self) -> Result<(), String> {
186        if self.unit_id == 0 || self.unit_id > 247 {
187            return Err(format!("Invalid unit ID: {} (must be 1-247)", self.unit_id));
188        }
189        Ok(())
190    }
191}