Skip to main content

bacnet_rs/object/
database.rs

1//! BACnet Object Database Module
2//!
3//! This module provides a database for storing and managing BACnet objects locally.
4//! It supports CRUD operations, property access, and efficient object lookup.
5
6#[cfg(feature = "std")]
7use std::{
8    collections::HashMap,
9    sync::{Arc, RwLock},
10    time::Instant,
11};
12
13#[cfg(not(feature = "std"))]
14use alloc::{boxed::Box, collections::BTreeMap as HashMap, string::String, sync::Arc, vec::Vec};
15
16use super::{
17    BacnetObject, Device, ObjectError, ObjectIdentifier, ObjectType, PropertyIdentifier,
18    PropertyValue, Result,
19};
20
21/// Object database for managing BACnet objects
22#[cfg(feature = "std")]
23pub struct ObjectDatabase {
24    /// Objects stored by identifier
25    objects: Arc<RwLock<HashMap<ObjectIdentifier, Box<dyn BacnetObject>>>>,
26    /// Index by object type for fast lookup
27    type_index: Arc<RwLock<HashMap<ObjectType, Vec<ObjectIdentifier>>>>,
28    /// Object name index for fast lookup by name
29    name_index: Arc<RwLock<HashMap<String, ObjectIdentifier>>>,
30    /// Database revision (incremented on changes)
31    revision: Arc<RwLock<u32>>,
32    /// Last modification time
33    last_modified: Arc<RwLock<Instant>>,
34    /// Device object reference (must always exist)
35    device_id: ObjectIdentifier,
36}
37
38#[cfg(feature = "std")]
39impl ObjectDatabase {
40    /// Create a new object database with a device object
41    pub fn new(device: Device) -> Self {
42        let device_id = device.identifier();
43        let mut objects = HashMap::new();
44        let mut type_index = HashMap::new();
45        let mut name_index = HashMap::new();
46
47        // Add device to indices
48        type_index
49            .entry(ObjectType::Device)
50            .or_insert_with(Vec::new)
51            .push(device_id);
52        name_index.insert(device.object_name.clone(), device_id);
53
54        // Store device object
55        objects.insert(device_id, Box::new(device) as Box<dyn BacnetObject>);
56
57        Self {
58            objects: Arc::new(RwLock::new(objects)),
59            type_index: Arc::new(RwLock::new(type_index)),
60            name_index: Arc::new(RwLock::new(name_index)),
61            revision: Arc::new(RwLock::new(1)),
62            last_modified: Arc::new(RwLock::new(Instant::now())),
63            device_id,
64        }
65    }
66
67    /// Add an object to the database
68    pub fn add_object(&self, object: Box<dyn BacnetObject>) -> Result<()> {
69        let identifier = object.identifier();
70
71        // Check if object already exists
72        {
73            let objects = self.objects.read().unwrap();
74            if objects.contains_key(&identifier) {
75                return Err(ObjectError::InvalidConfiguration(format!(
76                    "Object {} already exists",
77                    identifier.instance
78                )));
79            }
80        }
81
82        // Get object name for indexing
83        let object_name = match object.get_property(PropertyIdentifier::ObjectName)? {
84            PropertyValue::CharacterString(name) => name,
85            _ => return Err(ObjectError::InvalidPropertyType),
86        };
87
88        // Update all indices and storage atomically
89        {
90            let mut objects = self.objects.write().unwrap();
91            let mut type_index = self.type_index.write().unwrap();
92            let mut name_index = self.name_index.write().unwrap();
93
94            // Add to type index
95            type_index
96                .entry(identifier.object_type)
97                .or_default()
98                .push(identifier);
99
100            // Add to name index
101            name_index.insert(object_name, identifier);
102
103            // Store object
104            objects.insert(identifier, object);
105
106            // Update database revision
107            self.increment_revision();
108        }
109
110        Ok(())
111    }
112
113    /// Remove an object from the database
114    pub fn remove_object(&self, identifier: ObjectIdentifier) -> Result<()> {
115        // Cannot remove device object
116        if identifier == self.device_id {
117            return Err(ObjectError::WriteAccessDenied);
118        }
119
120        // Get object name for index removal
121        let object_name = {
122            let objects = self.objects.read().unwrap();
123            match objects.get(&identifier) {
124                Some(obj) => match obj.get_property(PropertyIdentifier::ObjectName)? {
125                    PropertyValue::CharacterString(name) => name,
126                    _ => return Err(ObjectError::InvalidPropertyType),
127                },
128                None => return Err(ObjectError::NotFound),
129            }
130        };
131
132        // Remove from all indices and storage
133        {
134            let mut objects = self.objects.write().unwrap();
135            let mut type_index = self.type_index.write().unwrap();
136            let mut name_index = self.name_index.write().unwrap();
137
138            // Remove from object storage
139            objects.remove(&identifier);
140
141            // Remove from type index
142            if let Some(type_list) = type_index.get_mut(&identifier.object_type) {
143                type_list.retain(|&id| id != identifier);
144            }
145
146            // Remove from name index
147            name_index.remove(&object_name);
148
149            // Update database revision
150            self.increment_revision();
151        }
152
153        Ok(())
154    }
155
156    /// Get a property value from an object
157    pub fn get_property(
158        &self,
159        identifier: ObjectIdentifier,
160        property: PropertyIdentifier,
161    ) -> Result<PropertyValue> {
162        let objects = self.objects.read().unwrap();
163        match objects.get(&identifier) {
164            Some(obj) => obj.get_property(property),
165            None => Err(ObjectError::NotFound),
166        }
167    }
168
169    /// Set a property value on an object
170    pub fn set_property(
171        &self,
172        identifier: ObjectIdentifier,
173        property: PropertyIdentifier,
174        value: PropertyValue,
175    ) -> Result<()> {
176        let mut objects = self.objects.write().unwrap();
177        match objects.get_mut(&identifier) {
178            Some(obj) => {
179                let result = obj.set_property(property, value);
180                if result.is_ok() {
181                    self.increment_revision();
182                }
183                result
184            }
185            None => Err(ObjectError::NotFound),
186        }
187    }
188
189    /// Get an object by name
190    pub fn get_object_by_name(&self, name: &str) -> Result<ObjectIdentifier> {
191        let name_index = self.name_index.read().unwrap();
192        match name_index.get(name) {
193            Some(&identifier) => Ok(identifier),
194            None => Err(ObjectError::NotFound),
195        }
196    }
197
198    /// Get all objects of a specific type
199    pub fn get_objects_by_type(&self, object_type: ObjectType) -> Vec<ObjectIdentifier> {
200        let type_index = self.type_index.read().unwrap();
201        type_index.get(&object_type).cloned().unwrap_or_default()
202    }
203
204    /// Get all object identifiers in the database
205    pub fn get_all_objects(&self) -> Vec<ObjectIdentifier> {
206        let objects = self.objects.read().unwrap();
207        objects.keys().cloned().collect()
208    }
209
210    /// Get object count
211    pub fn object_count(&self) -> usize {
212        let objects = self.objects.read().unwrap();
213        objects.len()
214    }
215
216    /// Get object count by type
217    pub fn object_count_by_type(&self, object_type: ObjectType) -> usize {
218        let type_index = self.type_index.read().unwrap();
219        type_index
220            .get(&object_type)
221            .map(|list| list.len())
222            .unwrap_or(0)
223    }
224
225    /// Get the device object identifier
226    pub fn get_device_id(&self) -> ObjectIdentifier {
227        self.device_id
228    }
229
230    /// Get the current database revision
231    pub fn revision(&self) -> u32 {
232        *self.revision.read().unwrap()
233    }
234
235    /// Get the last modification time
236    pub fn last_modified(&self) -> Instant {
237        *self.last_modified.read().unwrap()
238    }
239
240    /// Check if an object exists
241    pub fn contains(&self, identifier: ObjectIdentifier) -> bool {
242        let objects = self.objects.read().unwrap();
243        objects.contains_key(&identifier)
244    }
245
246    /// Check if an object name exists
247    pub fn contains_name(&self, name: &str) -> bool {
248        let name_index = self.name_index.read().unwrap();
249        name_index.contains_key(name)
250    }
251
252    /// Find the next available instance number for an object type
253    pub fn next_instance(&self, object_type: ObjectType) -> u32 {
254        let type_index = self.type_index.read().unwrap();
255        if let Some(objects) = type_index.get(&object_type) {
256            let max_instance = objects.iter().map(|id| id.instance).max().unwrap_or(0);
257            max_instance.saturating_add(1)
258        } else {
259            0
260        }
261    }
262
263    /// Search objects by property value
264    pub fn search_by_property(
265        &self,
266        property: PropertyIdentifier,
267        value: &PropertyValue,
268    ) -> Vec<ObjectIdentifier> {
269        let objects = self.objects.read().unwrap();
270        let mut results = Vec::new();
271
272        for (&id, obj) in objects.iter() {
273            if let Ok(prop_value) = obj.get_property(property) {
274                if Self::property_values_equal(&prop_value, value) {
275                    results.push(id);
276                }
277            }
278        }
279
280        results
281    }
282
283    /// Compare property values for equality
284    fn property_values_equal(a: &PropertyValue, b: &PropertyValue) -> bool {
285        match (a, b) {
286            (PropertyValue::Null, PropertyValue::Null) => true,
287            (PropertyValue::Boolean(a), PropertyValue::Boolean(b)) => a == b,
288            (PropertyValue::UnsignedInteger(a), PropertyValue::UnsignedInteger(b)) => a == b,
289            (PropertyValue::SignedInt(a), PropertyValue::SignedInt(b)) => a == b,
290            (PropertyValue::Real(a), PropertyValue::Real(b)) => (a - b).abs() < f32::EPSILON,
291            (PropertyValue::Double(a), PropertyValue::Double(b)) => (a - b).abs() < f64::EPSILON,
292            (PropertyValue::CharacterString(a), PropertyValue::CharacterString(b)) => a == b,
293            (PropertyValue::Enumerated(a), PropertyValue::Enumerated(b)) => a == b,
294            (PropertyValue::ObjectIdentifier(a), PropertyValue::ObjectIdentifier(b)) => a == b,
295            _ => false,
296        }
297    }
298
299    /// Increment database revision
300    fn increment_revision(&self) {
301        let mut revision = self.revision.write().unwrap();
302        *revision = revision.wrapping_add(1);
303
304        let mut last_modified = self.last_modified.write().unwrap();
305        *last_modified = Instant::now();
306    }
307
308    /// Export database statistics
309    pub fn statistics(&self) -> DatabaseStatistics {
310        let objects = self.objects.read().unwrap();
311        let type_index = self.type_index.read().unwrap();
312
313        let mut type_counts = HashMap::new();
314        for (object_type, identifiers) in type_index.iter() {
315            type_counts.insert(*object_type, identifiers.len());
316        }
317
318        DatabaseStatistics {
319            total_objects: objects.len(),
320            object_types: type_index.len(),
321            type_counts,
322            revision: self.revision(),
323            last_modified: self.last_modified(),
324        }
325    }
326}
327
328/// Database statistics
329#[cfg(feature = "std")]
330#[derive(Debug, Clone)]
331pub struct DatabaseStatistics {
332    pub total_objects: usize,
333    pub object_types: usize,
334    pub type_counts: HashMap<ObjectType, usize>,
335    pub revision: u32,
336    pub last_modified: Instant,
337}
338
339/// Object database builder for convenient setup
340#[cfg(feature = "std")]
341#[derive(Default)]
342pub struct DatabaseBuilder {
343    device: Option<Device>,
344    objects: Vec<Box<dyn BacnetObject>>,
345}
346
347#[cfg(feature = "std")]
348impl DatabaseBuilder {
349    /// Create a new database builder
350    pub fn new() -> Self {
351        Self::default()
352    }
353
354    /// Set the device object
355    pub fn with_device(mut self, device: Device) -> Self {
356        self.device = Some(device);
357        self
358    }
359
360    /// Add an object to be included in the database
361    pub fn add_object(mut self, object: Box<dyn BacnetObject>) -> Self {
362        self.objects.push(object);
363        self
364    }
365
366    /// Build the database
367    pub fn build(self) -> Result<ObjectDatabase> {
368        let device = self.device.ok_or_else(|| {
369            ObjectError::InvalidConfiguration("Device object is required".to_string())
370        })?;
371
372        let database = ObjectDatabase::new(device);
373
374        // Add all objects
375        for object in self.objects {
376            database.add_object(object)?;
377        }
378
379        Ok(database)
380    }
381}
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386    use crate::object::{
387        analog::{AnalogInput, AnalogValue},
388        binary::BinaryInput,
389    };
390
391    #[test]
392    fn test_database_creation() {
393        let device = Device::new(1234, "Test Device".to_string());
394        let db = ObjectDatabase::new(device);
395
396        assert_eq!(db.object_count(), 1);
397        assert_eq!(db.revision(), 1);
398        assert!(db.contains(ObjectIdentifier::new(ObjectType::Device, 1234)));
399    }
400
401    #[test]
402    fn test_add_remove_objects() {
403        let device = Device::new(1234, "Test Device".to_string());
404        let db = ObjectDatabase::new(device);
405
406        // Add analog input
407        let ai = AnalogInput::new(1, "Temperature".to_string());
408        db.add_object(Box::new(ai)).unwrap();
409
410        assert_eq!(db.object_count(), 2);
411        assert_eq!(db.object_count_by_type(ObjectType::AnalogInput), 1);
412
413        // Add binary input
414        let bi = BinaryInput::new(1, "Door Sensor".to_string());
415        db.add_object(Box::new(bi)).unwrap();
416
417        assert_eq!(db.object_count(), 3);
418
419        // Remove analog input
420        let ai_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
421        db.remove_object(ai_id).unwrap();
422
423        assert_eq!(db.object_count(), 2);
424        assert_eq!(db.object_count_by_type(ObjectType::AnalogInput), 0);
425    }
426
427    #[test]
428    fn test_object_lookup() {
429        let device = Device::new(1234, "Test Device".to_string());
430        let db = ObjectDatabase::new(device);
431
432        let av = AnalogValue::new(100, "Setpoint".to_string());
433        db.add_object(Box::new(av)).unwrap();
434
435        // Lookup by identifier
436        let av_id = ObjectIdentifier::new(ObjectType::AnalogValue, 100);
437        assert!(db.contains(av_id));
438
439        // Lookup by name
440        let found_id = db.get_object_by_name("Setpoint").unwrap();
441        assert_eq!(found_id, av_id);
442
443        // Lookup by type
444        let objects = db.get_objects_by_type(ObjectType::AnalogValue);
445        assert_eq!(objects.len(), 1);
446        assert_eq!(objects[0], av_id);
447    }
448
449    #[test]
450    fn test_property_search() {
451        let device = Device::new(1234, "Test Device".to_string());
452        let db = ObjectDatabase::new(device);
453
454        // Add multiple analog values
455        for i in 0..5 {
456            let mut av = AnalogValue::new(i, format!("AV{}", i));
457            av.present_value = 20.0 + i as f32;
458            db.add_object(Box::new(av)).unwrap();
459        }
460
461        // Search for specific present value
462        let results =
463            db.search_by_property(PropertyIdentifier::PresentValue, &PropertyValue::Real(22.0));
464
465        assert_eq!(results.len(), 1);
466        assert_eq!(results[0].instance, 2);
467    }
468
469    #[test]
470    fn test_database_builder() {
471        let db = DatabaseBuilder::new()
472            .with_device(Device::new(5000, "Built Device".to_string()))
473            .add_object(Box::new(AnalogInput::new(1, "AI1".to_string())))
474            .add_object(Box::new(AnalogInput::new(2, "AI2".to_string())))
475            .add_object(Box::new(BinaryInput::new(1, "BI1".to_string())))
476            .build()
477            .unwrap();
478
479        assert_eq!(db.object_count(), 4); // Device + 3 objects
480        assert_eq!(db.object_count_by_type(ObjectType::AnalogInput), 2);
481        assert_eq!(db.object_count_by_type(ObjectType::BinaryInput), 1);
482    }
483
484    #[test]
485    fn test_next_instance() {
486        let device = Device::new(1234, "Test Device".to_string());
487        let db = ObjectDatabase::new(device);
488
489        // No analog inputs yet
490        assert_eq!(db.next_instance(ObjectType::AnalogInput), 0);
491
492        // Add some analog inputs
493        db.add_object(Box::new(AnalogInput::new(5, "AI5".to_string())))
494            .unwrap();
495        db.add_object(Box::new(AnalogInput::new(10, "AI10".to_string())))
496            .unwrap();
497        db.add_object(Box::new(AnalogInput::new(3, "AI3".to_string())))
498            .unwrap();
499
500        // Next instance should be max + 1
501        assert_eq!(db.next_instance(ObjectType::AnalogInput), 11);
502    }
503}