Skip to main content

bacnet_rs/object/
device.rs

1//! Device Object and Object Functions API
2//!
3//! This module implements the BACnet Device object and provides an extensible
4//! API for registering and managing object type implementations.
5//!
6//! # Overview
7//!
8//! The Device Object API provides:
9//! - Centralized registry of object function handlers by type
10//! - Plugin-style architecture for custom object implementations
11//! - Function dispatch for property operations
12//! - Object instance management and validation
13//!
14//! This architecture mirrors the C reference implementation's `Device_Object_Functions_Find()`
15//! and `Device_Object_Functions()` APIs (bacnet-stack commit 5b7932ee6).
16
17#[cfg(not(feature = "std"))]
18use alloc::{string::String, vec::Vec};
19
20use crate::object::{ObjectIdentifier, ObjectType, PropertyIdentifier, PropertyValue, Result};
21
22/// Object function handlers for a specific object type
23///
24/// This structure provides a function pointer table for all operations
25/// that can be performed on objects of a specific type.
26#[derive(Clone)]
27pub struct ObjectFunctions {
28    /// The object type these functions handle
29    pub object_type: ObjectType,
30
31    /// Count the number of instances of this object type
32    pub count: fn() -> usize,
33
34    /// Convert index to instance number
35    pub index_to_instance: fn(usize) -> Option<u32>,
36
37    /// Check if an instance number is valid
38    pub valid_instance: fn(u32) -> bool,
39
40    /// Get the object name for an instance
41    pub object_name: fn(u32) -> Option<String>,
42
43    /// Read a property from an object instance
44    pub read_property: fn(u32, PropertyIdentifier) -> Result<PropertyValue>,
45
46    /// Write a property to an object instance
47    pub write_property: fn(u32, PropertyIdentifier, PropertyValue) -> Result<()>,
48
49    /// Check if a property is writable
50    pub is_property_writable: fn(u32, PropertyIdentifier) -> bool,
51
52    /// Get the list of properties for an object instance
53    pub property_list: fn(u32) -> Vec<PropertyIdentifier>,
54}
55
56impl core::fmt::Debug for ObjectFunctions {
57    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58        f.debug_struct("ObjectFunctions")
59            .field("object_type", &self.object_type)
60            .field("count", &"fn()")
61            .field("index_to_instance", &"fn(usize) -> Option<u32>")
62            .field("valid_instance", &"fn(u32) -> bool")
63            .field("object_name", &"fn(u32) -> Option<String>")
64            .field(
65                "read_property",
66                &"fn(u32, PropertyIdentifier) -> Result<PropertyValue>",
67            )
68            .field(
69                "write_property",
70                &"fn(u32, PropertyIdentifier, PropertyValue) -> Result<()>",
71            )
72            .field(
73                "is_property_writable",
74                &"fn(u32, PropertyIdentifier) -> bool",
75            )
76            .field("property_list", &"fn(u32) -> Vec<PropertyIdentifier>")
77            .finish()
78    }
79}
80
81/// Device Object that manages all object types and instances
82///
83/// The Device Object maintains a registry of object function handlers and
84/// provides centralized dispatch for all object operations.
85#[derive(Debug, Clone)]
86pub struct DeviceObject {
87    /// Object function registry indexed by object type
88    object_table: Vec<ObjectFunctions>,
89
90    /// Device instance number
91    device_instance: u32,
92
93    /// Device object identifier
94    device_identifier: String,
95
96    /// Device name
97    device_name: String,
98
99    /// Device description
100    device_description: String,
101
102    /// Vendor identifier
103    vendor_identifier: u16,
104
105    /// Vendor name
106    vendor_name: String,
107
108    /// Model name
109    model_name: String,
110
111    /// Firmware revision
112    firmware_revision: String,
113
114    /// Application software version
115    application_software_version: String,
116
117    /// Protocol version (1 for BACnet)
118    protocol_version: u8,
119
120    /// Protocol revision (ASHRAE 135 revision number)
121    protocol_revision: u8,
122}
123
124impl DeviceObject {
125    /// Create a new Device Object
126    ///
127    /// # Arguments
128    ///
129    /// * `device_instance` - The device instance number (0-4194302)
130    /// * `device_name` - The name of this device
131    ///
132    /// # Example
133    ///
134    /// ```rust,ignore
135    /// let device = DeviceObject::new(12345, "BACnet Device".to_string());
136    /// ```
137    pub fn new(device_instance: u32, device_name: String) -> Self {
138        Self {
139            object_table: Vec::new(),
140            device_instance,
141            device_identifier: format!("Device-{}", device_instance),
142            device_name,
143            device_description: String::new(),
144            vendor_identifier: 0,
145            vendor_name: String::from("Unknown"),
146            model_name: String::from("BACnet-RS Device"),
147            firmware_revision: String::from("1.0"),
148            application_software_version: String::from("1.0"),
149            protocol_version: 1,
150            protocol_revision: 30, // Protocol Revision 30 (latest)
151        }
152    }
153
154    /// Find object functions for a specific object type
155    ///
156    /// Returns a reference to the `ObjectFunctions` for the specified type,
157    /// or `None` if no handler is registered for that type.
158    ///
159    /// This mirrors the C reference implementation's `Device_Object_Functions_Find()`
160    ///
161    /// # Example
162    ///
163    /// ```rust,ignore
164    /// if let Some(funcs) = device.find_object_functions(ObjectType::AnalogInput) {
165    ///     let count = (funcs.count)();
166    ///     println!("Found {} analog inputs", count);
167    /// }
168    /// ```
169    pub fn find_object_functions(&self, object_type: ObjectType) -> Option<&ObjectFunctions> {
170        self.object_table
171            .iter()
172            .find(|f| f.object_type == object_type)
173    }
174
175    /// Get the entire object function table
176    ///
177    /// Returns a slice containing all registered object function handlers.
178    ///
179    /// This mirrors the C reference implementation's `Device_Object_Functions()`
180    ///
181    /// # Example
182    ///
183    /// ```rust,ignore
184    /// for funcs in device.object_functions() {
185    ///     let count = (funcs.count)();
186    ///     println!("{:?}: {} instances", funcs.object_type, count);
187    /// }
188    /// ```
189    pub fn object_functions(&self) -> &[ObjectFunctions] {
190        &self.object_table
191    }
192
193    /// Register object functions for a specific object type
194    ///
195    /// Adds or replaces the object function handler for the specified type.
196    /// This allows for custom object implementations and overriding default behavior.
197    ///
198    /// # Arguments
199    ///
200    /// * `functions` - The object function handlers to register
201    ///
202    /// # Example
203    ///
204    /// ```rust,ignore
205    /// // Register custom Analog Input implementation
206    /// device.register_object_functions(ObjectFunctions {
207    ///     object_type: ObjectType::AnalogInput,
208    ///     count: my_ai_count,
209    ///     index_to_instance: my_ai_index,
210    ///     valid_instance: my_ai_valid,
211    ///     object_name: my_ai_name,
212    ///     read_property: my_ai_read,
213    ///     write_property: my_ai_write,
214    ///     is_property_writable: my_ai_writable,
215    ///     property_list: my_ai_props,
216    /// });
217    /// ```
218    pub fn register_object_functions(&mut self, functions: ObjectFunctions) {
219        // Remove existing entry if present
220        self.object_table
221            .retain(|f| f.object_type != functions.object_type);
222        // Add new entry
223        self.object_table.push(functions);
224    }
225
226    /// Get the device instance number
227    pub fn device_instance(&self) -> u32 {
228        self.device_instance
229    }
230
231    /// Get the device name
232    pub fn device_name(&self) -> &str {
233        &self.device_name
234    }
235
236    /// Get the device identifier string
237    pub fn device_identifier(&self) -> &str {
238        &self.device_identifier
239    }
240
241    /// Get the application software version
242    pub fn application_software_version(&self) -> &str {
243        &self.application_software_version
244    }
245
246    /// Get the protocol version
247    pub fn protocol_version(&self) -> u8 {
248        self.protocol_version
249    }
250
251    /// Get the protocol revision
252    pub fn protocol_revision(&self) -> u8 {
253        self.protocol_revision
254    }
255
256    /// Set the device description
257    pub fn set_device_description(&mut self, description: String) {
258        self.device_description = description;
259    }
260
261    /// Set vendor information
262    pub fn set_vendor_info(&mut self, vendor_id: u16, vendor_name: String) {
263        self.vendor_identifier = vendor_id;
264        self.vendor_name = vendor_name;
265    }
266
267    /// Set model information
268    pub fn set_model_info(&mut self, model_name: String, firmware_revision: String) {
269        self.model_name = model_name;
270        self.firmware_revision = firmware_revision;
271    }
272
273    /// Read a property from any object managed by this device
274    ///
275    /// # Arguments
276    ///
277    /// * `object_id` - The object identifier (type + instance)
278    /// * `property` - The property identifier
279    ///
280    /// # Returns
281    ///
282    /// The property value, or an error if the object or property is not found
283    pub fn read_object_property(
284        &self,
285        object_id: ObjectIdentifier,
286        property: PropertyIdentifier,
287    ) -> Result<PropertyValue> {
288        // Find the object functions for this type
289        if let Some(funcs) = self.find_object_functions(object_id.object_type) {
290            // Validate the instance
291            if !(funcs.valid_instance)(object_id.instance) {
292                return Err(crate::object::ObjectError::InstanceNotFound);
293            }
294            // Read the property
295            (funcs.read_property)(object_id.instance, property)
296        } else {
297            Err(crate::object::ObjectError::TypeNotSupported)
298        }
299    }
300
301    /// Write a property to any object managed by this device
302    ///
303    /// # Arguments
304    ///
305    /// * `object_id` - The object identifier (type + instance)
306    /// * `property` - The property identifier
307    /// * `value` - The value to write
308    ///
309    /// # Returns
310    ///
311    /// Ok(()) on success, or an error if the object, property is not found or not writable
312    pub fn write_object_property(
313        &self,
314        object_id: ObjectIdentifier,
315        property: PropertyIdentifier,
316        value: PropertyValue,
317    ) -> Result<()> {
318        // Find the object functions for this type
319        if let Some(funcs) = self.find_object_functions(object_id.object_type) {
320            // Validate the instance
321            if !(funcs.valid_instance)(object_id.instance) {
322                return Err(crate::object::ObjectError::InstanceNotFound);
323            }
324            // Check if writable
325            if !(funcs.is_property_writable)(object_id.instance, property) {
326                return Err(crate::object::ObjectError::PropertyNotWritable);
327            }
328            // Write the property
329            (funcs.write_property)(object_id.instance, property, value)
330        } else {
331            Err(crate::object::ObjectError::TypeNotSupported)
332        }
333    }
334
335    /// Get the total object count across all types
336    pub fn total_object_count(&self) -> usize {
337        self.object_table.iter().map(|funcs| (funcs.count)()).sum()
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    // Mock functions for testing
346    fn mock_count() -> usize {
347        2
348    }
349
350    fn mock_index_to_instance(index: usize) -> Option<u32> {
351        match index {
352            0 => Some(1),
353            1 => Some(2),
354            _ => None,
355        }
356    }
357
358    fn mock_valid_instance(instance: u32) -> bool {
359        instance == 1 || instance == 2
360    }
361
362    fn mock_object_name(instance: u32) -> Option<String> {
363        if mock_valid_instance(instance) {
364            Some(format!("Test Object {}", instance))
365        } else {
366            None
367        }
368    }
369
370    fn mock_read_property(_instance: u32, property: PropertyIdentifier) -> Result<PropertyValue> {
371        match property {
372            PropertyIdentifier::PresentValue => Ok(PropertyValue::Real(42.0)),
373            _ => Err(crate::object::ObjectError::UnknownProperty),
374        }
375    }
376
377    fn mock_write_property(
378        _instance: u32,
379        _property: PropertyIdentifier,
380        _value: PropertyValue,
381    ) -> Result<()> {
382        Ok(())
383    }
384
385    fn mock_is_writable(_instance: u32, property: PropertyIdentifier) -> bool {
386        property == PropertyIdentifier::PresentValue
387    }
388
389    fn mock_property_list(_instance: u32) -> Vec<PropertyIdentifier> {
390        vec![PropertyIdentifier::PresentValue]
391    }
392
393    #[test]
394    fn test_device_object_creation() {
395        let device = DeviceObject::new(123, "Test Device".to_string());
396        assert_eq!(device.device_instance(), 123);
397        assert_eq!(device.device_name(), "Test Device");
398        assert_eq!(device.total_object_count(), 0);
399    }
400
401    #[test]
402    fn test_register_object_functions() {
403        let mut device = DeviceObject::new(123, "Test Device".to_string());
404
405        let functions = ObjectFunctions {
406            object_type: ObjectType::AnalogInput,
407            count: mock_count,
408            index_to_instance: mock_index_to_instance,
409            valid_instance: mock_valid_instance,
410            object_name: mock_object_name,
411            read_property: mock_read_property,
412            write_property: mock_write_property,
413            is_property_writable: mock_is_writable,
414            property_list: mock_property_list,
415        };
416
417        device.register_object_functions(functions);
418
419        assert_eq!(device.total_object_count(), 2);
420        assert!(device
421            .find_object_functions(ObjectType::AnalogInput)
422            .is_some());
423    }
424
425    #[test]
426    fn test_find_object_functions() {
427        let mut device = DeviceObject::new(123, "Test Device".to_string());
428
429        let functions = ObjectFunctions {
430            object_type: ObjectType::AnalogInput,
431            count: mock_count,
432            index_to_instance: mock_index_to_instance,
433            valid_instance: mock_valid_instance,
434            object_name: mock_object_name,
435            read_property: mock_read_property,
436            write_property: mock_write_property,
437            is_property_writable: mock_is_writable,
438            property_list: mock_property_list,
439        };
440
441        device.register_object_functions(functions);
442
443        // Should find registered type
444        assert!(device
445            .find_object_functions(ObjectType::AnalogInput)
446            .is_some());
447
448        // Should not find unregistered type
449        assert!(device
450            .find_object_functions(ObjectType::AnalogOutput)
451            .is_none());
452    }
453
454    #[test]
455    fn test_read_object_property() {
456        let mut device = DeviceObject::new(123, "Test Device".to_string());
457
458        let functions = ObjectFunctions {
459            object_type: ObjectType::AnalogInput,
460            count: mock_count,
461            index_to_instance: mock_index_to_instance,
462            valid_instance: mock_valid_instance,
463            object_name: mock_object_name,
464            read_property: mock_read_property,
465            write_property: mock_write_property,
466            is_property_writable: mock_is_writable,
467            property_list: mock_property_list,
468        };
469
470        device.register_object_functions(functions);
471
472        let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
473        let result = device.read_object_property(object_id, PropertyIdentifier::PresentValue);
474
475        assert!(result.is_ok());
476        if let Ok(PropertyValue::Real(val)) = result {
477            assert_eq!(val, 42.0);
478        } else {
479            panic!("Expected Real property value");
480        }
481    }
482}