Skip to main content

mabi_bacnet/
device.rs

1//! BACnet device implementation integrating with the core Device trait.
2
3use std::collections::HashMap;
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use parking_lot::RwLock;
8use tokio::sync::broadcast;
9
10use mabi_core::{
11    device::{Device, DeviceInfo, DeviceState, DeviceStatistics},
12    error::{Error, Result},
13    protocol::Protocol,
14    types::{AccessMode, DataPoint, DataPointDef, DataPointId, DataType},
15    value::Value,
16};
17
18use crate::config::BacnetServerConfig;
19use crate::object::property::{BACnetValue, PropertyId};
20use crate::object::registry::ObjectRegistry;
21use crate::object::standard::{AnalogInput, AnalogOutput, AnalogValue, BinaryInput, BinaryOutput, BinaryValue};
22use crate::object::traits::{ArcObject, BACnetObject};
23use crate::object::types::{ObjectId, ObjectType};
24
25/// BACnet device implementation.
26///
27/// This device wraps an ObjectRegistry and implements the core Device trait,
28/// providing integration between the BACnet object model and the simulator framework.
29pub struct BacnetDevice {
30    info: DeviceInfo,
31    device_instance: u32,
32    objects: Arc<ObjectRegistry>,
33    point_defs: RwLock<HashMap<String, DataPointDef>>,
34    object_to_point: RwLock<HashMap<ObjectId, String>>,
35    stats: RwLock<DeviceStatistics>,
36    event_tx: broadcast::Sender<DataPoint>,
37}
38
39impl BacnetDevice {
40    /// Create a new BACnet device.
41    pub fn new(config: &BacnetServerConfig) -> Self {
42        let (event_tx, _) = broadcast::channel(1000);
43
44        let info = DeviceInfo::new(
45            format!("bacnet-{}", config.device_instance),
46            &config.device_name,
47            Protocol::BacnetIp,
48        )
49        .with_metadata("device_instance", config.device_instance.to_string())
50        .with_metadata("vendor_id", config.vendor_id.to_string());
51
52        Self {
53            info,
54            device_instance: config.device_instance,
55            objects: Arc::new(ObjectRegistry::new()),
56            point_defs: RwLock::new(HashMap::new()),
57            object_to_point: RwLock::new(HashMap::new()),
58            stats: RwLock::new(DeviceStatistics::default()),
59            event_tx,
60        }
61    }
62
63    /// Create a new BACnet device with existing object registry.
64    pub fn with_registry(config: &BacnetServerConfig, registry: ObjectRegistry) -> Self {
65        let mut device = Self::new(config);
66        device.objects = Arc::new(registry);
67        device.sync_point_definitions();
68        device
69    }
70
71    /// Get device instance number.
72    pub fn device_instance(&self) -> u32 {
73        self.device_instance
74    }
75
76    /// Get the object registry.
77    pub fn objects(&self) -> &Arc<ObjectRegistry> {
78        &self.objects
79    }
80
81    /// Register an object and create its point definition.
82    pub fn register_object(&self, object: ArcObject) {
83        let object_id = object.object_identifier();
84        let object_name = object.object_name().to_string();
85
86        // Create point ID
87        let point_id = format!("{}:{}", object_id.object_type as u16, object_id.instance);
88
89        // Determine data type based on object type
90        let data_type = match object_id.object_type {
91            ObjectType::AnalogInput | ObjectType::AnalogOutput | ObjectType::AnalogValue => DataType::Float32,
92            ObjectType::BinaryInput | ObjectType::BinaryOutput | ObjectType::BinaryValue => DataType::Bool,
93            ObjectType::MultiStateInput | ObjectType::MultiStateOutput | ObjectType::MultiStateValue => DataType::UInt32,
94            _ => DataType::String,
95        };
96
97        // Determine access mode
98        let access = match object_id.object_type {
99            ObjectType::AnalogOutput | ObjectType::AnalogValue |
100            ObjectType::BinaryOutput | ObjectType::BinaryValue |
101            ObjectType::MultiStateOutput | ObjectType::MultiStateValue => AccessMode::ReadWrite,
102            _ => AccessMode::ReadOnly,
103        };
104
105        let def = DataPointDef::new(&point_id, &object_name, data_type).with_access(access);
106
107        // Register object
108        self.objects.register(object);
109
110        // Add point definition
111        self.object_to_point.write().insert(object_id, point_id.clone());
112        self.point_defs.write().insert(point_id, def);
113    }
114
115    /// Add an analog input object.
116    pub fn add_analog_input(&self, instance: u32, name: &str) -> ObjectId {
117        let object = Arc::new(AnalogInput::new(instance, name));
118        let id = object.object_identifier();
119        self.register_object(object);
120        id
121    }
122
123    /// Add an analog output object.
124    pub fn add_analog_output(&self, instance: u32, name: &str) -> ObjectId {
125        let object = Arc::new(AnalogOutput::new(instance, name));
126        let id = object.object_identifier();
127        self.register_object(object);
128        id
129    }
130
131    /// Add an analog value object.
132    pub fn add_analog_value(&self, instance: u32, name: &str) -> ObjectId {
133        let object = Arc::new(AnalogValue::new(instance, name));
134        let id = object.object_identifier();
135        self.register_object(object);
136        id
137    }
138
139    /// Add a binary input object.
140    pub fn add_binary_input(&self, instance: u32, name: &str) -> ObjectId {
141        let object = Arc::new(BinaryInput::new(instance, name));
142        let id = object.object_identifier();
143        self.register_object(object);
144        id
145    }
146
147    /// Add a binary output object.
148    pub fn add_binary_output(&self, instance: u32, name: &str) -> ObjectId {
149        let object = Arc::new(BinaryOutput::new(instance, name));
150        let id = object.object_identifier();
151        self.register_object(object);
152        id
153    }
154
155    /// Add a binary value object.
156    pub fn add_binary_value(&self, instance: u32, name: &str) -> ObjectId {
157        let object = Arc::new(BinaryValue::new(instance, name));
158        let id = object.object_identifier();
159        self.register_object(object);
160        id
161    }
162
163    /// Sync point definitions from the object registry.
164    fn sync_point_definitions(&self) {
165        for object in self.objects.iter() {
166            let object_id = object.object_identifier();
167            let object_name = object.object_name().to_string();
168            let point_id = format!("{}:{}", object_id.object_type as u16, object_id.instance);
169
170            let data_type = match object_id.object_type {
171                ObjectType::AnalogInput | ObjectType::AnalogOutput | ObjectType::AnalogValue => DataType::Float32,
172                ObjectType::BinaryInput | ObjectType::BinaryOutput | ObjectType::BinaryValue => DataType::Bool,
173                ObjectType::MultiStateInput | ObjectType::MultiStateOutput | ObjectType::MultiStateValue => DataType::UInt32,
174                _ => DataType::String,
175            };
176
177            let access = match object_id.object_type {
178                ObjectType::AnalogOutput | ObjectType::AnalogValue |
179                ObjectType::BinaryOutput | ObjectType::BinaryValue |
180                ObjectType::MultiStateOutput | ObjectType::MultiStateValue => AccessMode::ReadWrite,
181                _ => AccessMode::ReadOnly,
182            };
183
184            let def = DataPointDef::new(&point_id, &object_name, data_type).with_access(access);
185
186            self.object_to_point.write().insert(object_id, point_id.clone());
187            self.point_defs.write().insert(point_id, def);
188        }
189    }
190
191    /// Convert point ID to object ID.
192    fn point_id_to_object_id(&self, point_id: &str) -> Option<ObjectId> {
193        let parts: Vec<&str> = point_id.split(':').collect();
194        if parts.len() != 2 {
195            return None;
196        }
197
198        let type_code: u16 = parts[0].parse().ok()?;
199        let instance: u32 = parts[1].parse().ok()?;
200        let object_type = ObjectType::from_u16(type_code)?;
201
202        Some(ObjectId::new(object_type, instance))
203    }
204
205    /// Convert BACnet value to core Value.
206    fn bacnet_to_core_value(bacnet: &BACnetValue) -> Option<Value> {
207        match bacnet {
208            BACnetValue::Null => Some(Value::Null),
209            BACnetValue::Boolean(b) => Some(Value::Bool(*b)),
210            BACnetValue::Unsigned(u) => Some(Value::U32(*u)),
211            BACnetValue::Signed(i) => Some(Value::I32(*i)),
212            BACnetValue::Real(f) => Some(Value::F32(*f)),
213            BACnetValue::Double(d) => Some(Value::F64(*d)),
214            BACnetValue::CharacterString(s) => Some(Value::String(s.clone())),
215            BACnetValue::Enumerated(e) => Some(Value::U32(*e)),
216            _ => None,
217        }
218    }
219
220    /// Convert core Value to BACnet value.
221    fn core_to_bacnet_value(value: &Value) -> Option<BACnetValue> {
222        match value {
223            Value::Null => Some(BACnetValue::Null),
224            Value::Bool(b) => Some(BACnetValue::Boolean(*b)),
225            Value::U8(u) => Some(BACnetValue::Unsigned(*u as u32)),
226            Value::U16(u) => Some(BACnetValue::Unsigned(*u as u32)),
227            Value::U32(u) => Some(BACnetValue::Unsigned(*u)),
228            Value::U64(u) => Some(BACnetValue::Unsigned(*u as u32)),
229            Value::I8(i) => Some(BACnetValue::Signed(*i as i32)),
230            Value::I16(i) => Some(BACnetValue::Signed(*i as i32)),
231            Value::I32(i) => Some(BACnetValue::Signed(*i)),
232            Value::I64(i) => Some(BACnetValue::Signed(*i as i32)),
233            Value::F32(f) => Some(BACnetValue::Real(*f)),
234            Value::F64(d) => Some(BACnetValue::Double(*d)),
235            Value::String(s) => Some(BACnetValue::CharacterString(s.clone())),
236            _ => None,
237        }
238    }
239}
240
241#[async_trait]
242impl Device for BacnetDevice {
243    fn info(&self) -> &DeviceInfo {
244        &self.info
245    }
246
247    async fn initialize(&mut self) -> Result<()> {
248        self.info.state = DeviceState::Online;
249        self.info.point_count = self.point_defs.read().len();
250        Ok(())
251    }
252
253    async fn start(&mut self) -> Result<()> {
254        self.info.state = DeviceState::Online;
255        Ok(())
256    }
257
258    async fn stop(&mut self) -> Result<()> {
259        self.info.state = DeviceState::Offline;
260        Ok(())
261    }
262
263    async fn tick(&mut self) -> Result<()> {
264        Ok(())
265    }
266
267    fn point_definitions(&self) -> Vec<&DataPointDef> {
268        // Limitation: returning empty since we use RwLock
269        // In practice, use the objects() method to query directly
270        vec![]
271    }
272
273    fn point_definition(&self, _point_id: &str) -> Option<&DataPointDef> {
274        // Same limitation
275        None
276    }
277
278    async fn read(&self, point_id: &str) -> Result<DataPoint> {
279        let object_id = self
280            .point_id_to_object_id(point_id)
281            .ok_or_else(|| Error::point_not_found(&self.info.id, point_id))?;
282
283        let bacnet_value = self.objects
284            .read_property(&object_id, PropertyId::PresentValue)
285            .map_err(|e| Error::point_not_found(&self.info.id, &e.to_string()))?;
286
287        let value = Self::bacnet_to_core_value(&bacnet_value)
288            .ok_or_else(|| Error::point_not_found(&self.info.id, "Cannot convert BACnet value"))?;
289
290        self.stats.write().record_read();
291        let id = DataPointId::new(&self.info.id, point_id);
292        Ok(DataPoint::new(id, value))
293    }
294
295    async fn write(&mut self, point_id: &str, value: Value) -> Result<()> {
296        let object_id = self
297            .point_id_to_object_id(point_id)
298            .ok_or_else(|| Error::point_not_found(&self.info.id, point_id))?;
299
300        // Check if writable
301        let is_writable = matches!(
302            object_id.object_type,
303            ObjectType::AnalogOutput | ObjectType::AnalogValue |
304            ObjectType::BinaryOutput | ObjectType::BinaryValue |
305            ObjectType::MultiStateOutput | ObjectType::MultiStateValue
306        );
307
308        if !is_writable {
309            return Err(Error::NotSupported(format!(
310                "Object {} is read-only",
311                point_id
312            )));
313        }
314
315        let bacnet_value = Self::core_to_bacnet_value(&value)
316            .ok_or_else(|| Error::NotSupported("Cannot convert value to BACnet format".into()))?;
317
318        self.objects
319            .write_property(&object_id, PropertyId::PresentValue, bacnet_value)
320            .map_err(|e| Error::point_not_found(&self.info.id, &e.to_string()))?;
321
322        self.stats.write().record_write();
323
324        let id = DataPointId::new(&self.info.id, point_id);
325        let _ = self.event_tx.send(DataPoint::new(id, value));
326
327        Ok(())
328    }
329
330    fn subscribe(&self) -> Option<broadcast::Receiver<DataPoint>> {
331        Some(self.event_tx.subscribe())
332    }
333
334    fn statistics(&self) -> DeviceStatistics {
335        self.stats.read().clone()
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[tokio::test]
344    async fn test_bacnet_device() {
345        let config = BacnetServerConfig::default();
346        let device = BacnetDevice::new(&config);
347
348        device.add_analog_input(1, "Temperature");
349        device.add_analog_output(1, "Setpoint");
350
351        assert_eq!(device.objects.len(), 2);
352    }
353
354    #[tokio::test]
355    async fn test_bacnet_read_write() {
356        let config = BacnetServerConfig::default();
357        let mut device = BacnetDevice::new(&config);
358
359        let id = device.add_analog_output(1, "Setpoint");
360        device.initialize().await.unwrap();
361
362        // Write using object registry directly first
363        if let Some(object) = device.objects.get(&id) {
364            let _ = object.write_property(PropertyId::PresentValue, BACnetValue::Real(21.5));
365        }
366
367        // Read
368        let dp = device.read("1:1").await.unwrap();
369        assert!((dp.value.as_f64().unwrap() - 21.5).abs() < 0.01);
370    }
371}