Skip to main content

mabi_knx/
factory.rs

1//! KNX Device Factory.
2//!
3//! This module provides factory implementations for creating KNX devices.
4
5use std::sync::Arc;
6
7use mabi_core::{
8    DeviceConfig, DeviceFactory, FactoryRegistry, Protocol,
9    Result as CoreResult,
10    device::BoxedDevice,
11};
12
13use crate::address::{GroupAddress, IndividualAddress};
14use crate::config::{GroupObjectConfig, KnxDeviceConfig};
15use crate::device::{KnxDevice, KnxDeviceBuilder};
16use crate::dpt::{DptId, DptRegistry, DptValue};
17use crate::error::KnxError;
18
19/// Factory for creating KNX devices.
20pub struct KnxDeviceFactory {
21    dpt_registry: Arc<DptRegistry>,
22}
23
24impl KnxDeviceFactory {
25    /// Create a new factory.
26    pub fn new() -> Self {
27        Self {
28            dpt_registry: Arc::new(DptRegistry::new()),
29        }
30    }
31
32    /// Create with custom DPT registry.
33    pub fn with_registry(registry: Arc<DptRegistry>) -> Self {
34        Self {
35            dpt_registry: registry,
36        }
37    }
38
39    /// Create a device from configuration.
40    pub fn create_from_config(&self, config: KnxDeviceConfig) -> CoreResult<KnxDevice> {
41        let mut builder = KnxDeviceBuilder::new(&config.id, &config.name)
42            .individual_address(config.individual_address)
43            .description(&config.description)
44            .dpt_registry(self.dpt_registry.clone());
45
46        // Add group objects from config
47        for go_config in &config.group_objects {
48            let address: GroupAddress = go_config
49                .address
50                .parse()
51                .map_err(|e: KnxError| mabi_core::Error::Protocol(e.to_string()))?;
52
53            let dpt_id: DptId = go_config
54                .dpt
55                .parse()
56                .map_err(|e: KnxError| mabi_core::Error::Protocol(e.to_string()))?;
57
58            // Parse initial value if provided
59            if let Some(ref json_value) = go_config.initial_value {
60                let dpt_value = parse_json_to_dpt(json_value, &dpt_id)?;
61                builder = builder.group_object_with_value(address, &go_config.name, dpt_id, dpt_value);
62            } else {
63                builder = builder.group_object(address, &go_config.name, dpt_id);
64            }
65        }
66
67        builder
68            .build()
69            .map_err(|e| mabi_core::Error::Protocol(e.to_string()))
70    }
71
72    /// Quick create a simple device.
73    pub fn create_simple(
74        &self,
75        id: &str,
76        name: &str,
77        individual_address: IndividualAddress,
78    ) -> KnxDevice {
79        KnxDeviceBuilder::new(id, name)
80            .individual_address(individual_address)
81            .dpt_registry(self.dpt_registry.clone())
82            .build()
83            .expect("Simple device creation should not fail")
84    }
85}
86
87impl Default for KnxDeviceFactory {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93impl DeviceFactory for KnxDeviceFactory {
94    fn protocol(&self) -> Protocol {
95        Protocol::KnxIp
96    }
97
98    fn create(&self, config: DeviceConfig) -> CoreResult<BoxedDevice> {
99        // Extract KNX-specific config from generic DeviceConfig
100        let individual_address = config
101            .metadata
102            .get("individual_address")
103            .and_then(|s| s.parse().ok())
104            .unwrap_or_else(|| IndividualAddress::new(1, 1, 1));
105
106        let mut builder = KnxDeviceBuilder::new(&config.id, &config.name)
107            .individual_address(individual_address)
108            .description(&config.description)
109            .dpt_registry(self.dpt_registry.clone());
110
111        // Parse group objects from metadata if present
112        if let Some(group_objects_json) = config.metadata.get("group_objects") {
113            if let Ok(objects) = serde_json::from_str::<Vec<GroupObjectConfig>>(group_objects_json)
114            {
115                for go in objects {
116                    if let (Ok(addr), Ok(dpt)) = (go.address.parse::<GroupAddress>(), go.dpt.parse::<DptId>()) {
117                        builder = builder.group_object(addr, &go.name, dpt);
118                    }
119                }
120            }
121        }
122
123        let device = builder
124            .build()
125            .map_err(|e| mabi_core::Error::Protocol(e.to_string()))?;
126
127        Ok(Box::new(device))
128    }
129}
130
131/// Parse JSON value to DptValue.
132fn parse_json_to_dpt(json: &serde_json::Value, dpt_id: &DptId) -> CoreResult<DptValue> {
133    match dpt_id.main {
134        1 => {
135            // Boolean
136            let b = json
137                .as_bool()
138                .ok_or_else(|| mabi_core::Error::Config("Expected boolean".into()))?;
139            Ok(DptValue::Bool(b))
140        }
141        5 | 6 => {
142            // 8-bit integer
143            let n = json
144                .as_i64()
145                .ok_or_else(|| mabi_core::Error::Config("Expected integer".into()))?;
146            if dpt_id.main == 5 {
147                Ok(DptValue::U8(n as u8))
148            } else {
149                Ok(DptValue::I8(n as i8))
150            }
151        }
152        7 | 8 => {
153            // 16-bit integer
154            let n = json
155                .as_i64()
156                .ok_or_else(|| mabi_core::Error::Config("Expected integer".into()))?;
157            if dpt_id.main == 7 {
158                Ok(DptValue::U16(n as u16))
159            } else {
160                Ok(DptValue::I16(n as i16))
161            }
162        }
163        9 => {
164            // 16-bit float
165            let f = json
166                .as_f64()
167                .ok_or_else(|| mabi_core::Error::Config("Expected number".into()))?;
168            Ok(DptValue::F16(f as f32))
169        }
170        12 | 13 => {
171            // 32-bit integer
172            let n = json
173                .as_i64()
174                .ok_or_else(|| mabi_core::Error::Config("Expected integer".into()))?;
175            if dpt_id.main == 12 {
176                Ok(DptValue::U32(n as u32))
177            } else {
178                Ok(DptValue::I32(n as i32))
179            }
180        }
181        14 => {
182            // 32-bit float
183            let f = json
184                .as_f64()
185                .ok_or_else(|| mabi_core::Error::Config("Expected number".into()))?;
186            Ok(DptValue::F32(f as f32))
187        }
188        16 => {
189            // String
190            let s = json
191                .as_str()
192                .ok_or_else(|| mabi_core::Error::Config("Expected string".into()))?;
193            Ok(DptValue::String(s.to_string()))
194        }
195        _ => {
196            // Try to parse as generic number or bool
197            if let Some(b) = json.as_bool() {
198                Ok(DptValue::Bool(b))
199            } else if let Some(n) = json.as_i64() {
200                Ok(DptValue::I32(n as i32))
201            } else if let Some(f) = json.as_f64() {
202                Ok(DptValue::F32(f as f32))
203            } else if let Some(s) = json.as_str() {
204                Ok(DptValue::String(s.to_string()))
205            } else {
206                Err(mabi_core::Error::Config(format!(
207                    "Cannot parse value for DPT {}",
208                    dpt_id
209                )))
210            }
211        }
212    }
213}
214
215/// Register KNX factory with the registry.
216pub fn register_knx_factory(registry: &FactoryRegistry) -> CoreResult<()> {
217    registry.register(KnxDeviceFactory::new())
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223    use crate::config::GroupObjectFlagsConfig;
224
225    #[test]
226    fn test_factory_create_from_config() {
227        let factory = KnxDeviceFactory::new();
228
229        let config = KnxDeviceConfig {
230            id: "knx-1".to_string(),
231            name: "Living Room Controller".to_string(),
232            description: "Controls living room lights".to_string(),
233            individual_address: IndividualAddress::new(1, 2, 3),
234            group_objects: vec![
235                GroupObjectConfig {
236                    address: "1/0/1".to_string(),
237                    name: "Main Light".to_string(),
238                    dpt: "1.001".to_string(),
239                    flags: GroupObjectFlagsConfig::default(),
240                    initial_value: Some(serde_json::json!(false)),
241                },
242                GroupObjectConfig {
243                    address: "1/0/2".to_string(),
244                    name: "Dimmer".to_string(),
245                    dpt: "5.001".to_string(),
246                    flags: GroupObjectFlagsConfig::default(),
247                    initial_value: Some(serde_json::json!(50)),
248                },
249            ],
250            tick_interval_ms: 100,
251        };
252
253        let device = factory.create_from_config(config).unwrap();
254        assert_eq!(device.individual_address().to_string(), "1.2.3");
255    }
256
257    #[test]
258    fn test_factory_create_simple() {
259        let factory = KnxDeviceFactory::new();
260        let device = factory.create_simple("test", "Test Device", IndividualAddress::new(1, 1, 1));
261        assert_eq!(device.id(), "test");
262    }
263
264    #[test]
265    fn test_factory_trait() {
266        let factory = KnxDeviceFactory::new();
267        assert_eq!(factory.protocol(), Protocol::KnxIp);
268    }
269}