1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6use crate::registers::RegisterStoreConfig;
7use crate::types::WordOrder;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct UnitManagerConfig {
12 pub max_units: usize,
16
17 pub default_word_order: WordOrder,
21
22 pub broadcast_mode: BroadcastMode,
24
25 pub auto_create_units: bool,
30
31 #[serde(default)]
33 pub default_register_config: RegisterStoreConfig,
34
35 pub enable_unit_metrics: bool,
37}
38
39impl Default for UnitManagerConfig {
40 fn default() -> Self {
41 Self {
42 max_units: 247,
43 default_word_order: WordOrder::BigEndian,
44 broadcast_mode: BroadcastMode::WriteAll,
45 auto_create_units: false,
46 default_register_config: RegisterStoreConfig::default(),
47 enable_unit_metrics: true,
48 }
49 }
50}
51
52impl UnitManagerConfig {
53 pub fn with_max_units(mut self, max_units: usize) -> Self {
55 self.max_units = max_units;
56 self
57 }
58
59 pub fn with_word_order(mut self, word_order: WordOrder) -> Self {
61 self.default_word_order = word_order;
62 self
63 }
64
65 pub fn with_broadcast_mode(mut self, mode: BroadcastMode) -> Self {
67 self.broadcast_mode = mode;
68 self
69 }
70
71 pub fn with_auto_create(mut self, auto_create: bool) -> Self {
73 self.auto_create_units = auto_create;
74 self
75 }
76
77 pub fn with_register_config(mut self, config: RegisterStoreConfig) -> Self {
79 self.default_register_config = config;
80 self
81 }
82
83 pub fn for_testing() -> Self {
85 Self {
86 max_units: 10,
87 default_word_order: WordOrder::BigEndian,
88 broadcast_mode: BroadcastMode::WriteAll,
89 auto_create_units: true,
90 default_register_config: RegisterStoreConfig::minimal(),
91 enable_unit_metrics: false,
92 }
93 }
94
95 pub fn for_large_scale() -> Self {
97 Self {
98 max_units: 247,
99 default_word_order: WordOrder::BigEndian,
100 broadcast_mode: BroadcastMode::WriteAll,
101 auto_create_units: false,
102 default_register_config: RegisterStoreConfig::large_scale(),
103 enable_unit_metrics: true,
104 }
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
113#[serde(rename_all = "snake_case")]
114pub enum BroadcastMode {
115 WriteAll,
120
121 Disabled,
125
126 #[serde(skip)]
130 SelectiveList(Vec<u8>),
131
132 EchoToUnit(u8),
136}
137
138impl Default for BroadcastMode {
139 fn default() -> Self {
140 Self::WriteAll
141 }
142}
143
144impl fmt::Display for BroadcastMode {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 match self {
147 Self::WriteAll => write!(f, "Write to all units"),
148 Self::Disabled => write!(f, "Disabled"),
149 Self::SelectiveList(units) => write!(f, "Selective ({} units)", units.len()),
150 Self::EchoToUnit(id) => write!(f, "Echo to unit {}", id),
151 }
152 }
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct UnitConfig {
158 pub name: String,
160
161 #[serde(default)]
163 pub description: String,
164
165 pub word_order: Option<WordOrder>,
167
168 #[serde(default)]
170 pub register_config: Option<RegisterStoreConfig>,
171
172 #[serde(default = "default_broadcast_enabled")]
174 pub broadcast_enabled: bool,
175
176 #[serde(default)]
178 pub response_delay_us: u64,
179
180 #[serde(default = "default_enabled")]
182 pub enabled: bool,
183
184 #[serde(default)]
186 pub metadata: std::collections::HashMap<String, String>,
187}
188
189fn default_broadcast_enabled() -> bool {
190 true
191}
192
193fn default_enabled() -> bool {
194 true
195}
196
197impl Default for UnitConfig {
198 fn default() -> Self {
199 Self {
200 name: "Unnamed Unit".to_string(),
201 description: String::new(),
202 word_order: None,
203 register_config: None,
204 broadcast_enabled: true,
205 response_delay_us: 0,
206 enabled: true,
207 metadata: std::collections::HashMap::new(),
208 }
209 }
210}
211
212impl UnitConfig {
213 pub fn new(name: impl Into<String>) -> Self {
215 Self {
216 name: name.into(),
217 ..Default::default()
218 }
219 }
220
221 pub fn with_word_order(name: impl Into<String>, word_order: WordOrder) -> Self {
223 Self {
224 name: name.into(),
225 word_order: Some(word_order),
226 ..Default::default()
227 }
228 }
229
230 pub fn with_description(mut self, description: impl Into<String>) -> Self {
232 self.description = description.into();
233 self
234 }
235
236 pub fn with_register_config(mut self, config: RegisterStoreConfig) -> Self {
238 self.register_config = Some(config);
239 self
240 }
241
242 pub fn with_response_delay_us(mut self, delay: u64) -> Self {
244 self.response_delay_us = delay;
245 self
246 }
247
248 pub fn with_broadcast(mut self, enabled: bool) -> Self {
250 self.broadcast_enabled = enabled;
251 self
252 }
253
254 pub fn with_enabled(mut self, enabled: bool) -> Self {
256 self.enabled = enabled;
257 self
258 }
259
260 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
262 self.metadata.insert(key.into(), value.into());
263 self
264 }
265
266 pub fn effective_word_order(&self, default: WordOrder) -> WordOrder {
268 self.word_order.unwrap_or(default)
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn test_unit_manager_config_defaults() {
278 let config = UnitManagerConfig::default();
279 assert_eq!(config.max_units, 247);
280 assert_eq!(config.default_word_order, WordOrder::BigEndian);
281 assert!(!config.auto_create_units);
282 }
283
284 #[test]
285 fn test_unit_manager_config_builder() {
286 let config = UnitManagerConfig::default()
287 .with_max_units(100)
288 .with_word_order(WordOrder::LittleEndian)
289 .with_auto_create(true);
290
291 assert_eq!(config.max_units, 100);
292 assert_eq!(config.default_word_order, WordOrder::LittleEndian);
293 assert!(config.auto_create_units);
294 }
295
296 #[test]
297 fn test_broadcast_mode_display() {
298 assert_eq!(BroadcastMode::WriteAll.to_string(), "Write to all units");
299 assert_eq!(BroadcastMode::Disabled.to_string(), "Disabled");
300 assert_eq!(BroadcastMode::EchoToUnit(5).to_string(), "Echo to unit 5");
301 }
302
303 #[test]
304 fn test_unit_config_builder() {
305 let config = UnitConfig::new("Pump #1")
306 .with_description("Main circulation pump")
307 .with_response_delay_us(1000)
308 .with_metadata("location", "Building A");
309
310 assert_eq!(config.name, "Pump #1");
311 assert_eq!(config.description, "Main circulation pump");
312 assert_eq!(config.response_delay_us, 1000);
313 assert_eq!(config.metadata.get("location"), Some(&"Building A".to_string()));
314 }
315
316 #[test]
317 fn test_unit_config_effective_word_order() {
318 let config1 = UnitConfig::new("Test1");
319 let config2 = UnitConfig::with_word_order("Test2", WordOrder::LittleEndian);
320
321 assert_eq!(config1.effective_word_order(WordOrder::BigEndian), WordOrder::BigEndian);
322 assert_eq!(config2.effective_word_order(WordOrder::BigEndian), WordOrder::LittleEndian);
323 }
324
325 #[test]
326 fn test_serde_roundtrip() {
327 let config = UnitManagerConfig::default()
328 .with_max_units(50)
329 .with_word_order(WordOrder::BigEndianWordSwap);
330
331 let json = serde_json::to_string(&config).unwrap();
332 let parsed: UnitManagerConfig = serde_json::from_str(&json).unwrap();
333
334 assert_eq!(parsed.max_units, 50);
335 assert_eq!(parsed.default_word_order, WordOrder::BigEndianWordSwap);
336 }
337}