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