Skip to main content

rustbac_client/
walk.rs

1//! Device discovery walk — reads the object list and common properties for
2//! every object on a BACnet device.
3
4use crate::{BacnetClient, ClientDataValue, ClientError};
5use rustbac_core::types::{ObjectId, ObjectType, PropertyId};
6use rustbac_datalink::{DataLink, DataLinkAddress};
7
8/// Summary of a single object on a device.
9#[derive(Debug, Clone)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct ObjectSummary {
12    pub object_id: ObjectId,
13    pub object_name: Option<String>,
14    pub object_type: ObjectType,
15    pub present_value: Option<ClientDataValue>,
16    pub description: Option<String>,
17    pub units: Option<u32>,
18    pub status_flags: Option<ClientDataValue>,
19}
20
21/// Result of a full device walk.
22#[derive(Debug, Clone)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct DeviceWalkResult {
25    pub device_id: ObjectId,
26    pub objects: Vec<ObjectSummary>,
27}
28
29/// Walk a BACnet device: read its object list, then batch-read common
30/// properties for each object.
31pub async fn walk_device<D: DataLink>(
32    client: &BacnetClient<D>,
33    addr: DataLinkAddress,
34    device_id: ObjectId,
35) -> Result<DeviceWalkResult, ClientError> {
36    // 1. Read the object list.
37    let object_list_value = client
38        .read_property(addr, device_id, PropertyId::ObjectList)
39        .await?;
40
41    let object_ids = extract_object_ids(&object_list_value);
42
43    // 2. For each object, read common properties via ReadPropertyMultiple.
44    let properties = &[
45        PropertyId::ObjectName,
46        PropertyId::ObjectType,
47        PropertyId::PresentValue,
48        PropertyId::Description,
49        PropertyId::Units,
50        PropertyId::StatusFlags,
51    ];
52
53    let mut objects = Vec::with_capacity(object_ids.len());
54    for &oid in &object_ids {
55        let props = client.read_property_multiple(addr, oid, properties).await;
56
57        let summary = match props {
58            Ok(prop_values) => build_summary(oid, &prop_values),
59            Err(_) => ObjectSummary {
60                object_id: oid,
61                object_name: None,
62                object_type: oid.object_type(),
63                present_value: None,
64                description: None,
65                units: None,
66                status_flags: None,
67            },
68        };
69        objects.push(summary);
70    }
71
72    Ok(DeviceWalkResult { device_id, objects })
73}
74
75fn extract_object_ids(value: &ClientDataValue) -> Vec<ObjectId> {
76    match value {
77        ClientDataValue::ObjectId(oid) => vec![*oid],
78        ClientDataValue::Constructed { values, .. } => values
79            .iter()
80            .filter_map(|v| {
81                if let ClientDataValue::ObjectId(oid) = v {
82                    Some(*oid)
83                } else {
84                    None
85                }
86            })
87            .collect(),
88        _ => vec![],
89    }
90}
91
92fn build_summary(oid: ObjectId, props: &[(PropertyId, ClientDataValue)]) -> ObjectSummary {
93    let mut summary = ObjectSummary {
94        object_id: oid,
95        object_name: None,
96        object_type: oid.object_type(),
97        present_value: None,
98        description: None,
99        units: None,
100        status_flags: None,
101    };
102
103    for (pid, val) in props {
104        match pid {
105            PropertyId::ObjectName => {
106                if let ClientDataValue::CharacterString(s) = val {
107                    summary.object_name = Some(s.clone());
108                }
109            }
110            PropertyId::ObjectType => {
111                if let ClientDataValue::Enumerated(v) = val {
112                    summary.object_type = ObjectType::from_u16(*v as u16);
113                }
114            }
115            PropertyId::PresentValue => {
116                summary.present_value = Some(val.clone());
117            }
118            PropertyId::Description => {
119                if let ClientDataValue::CharacterString(s) = val {
120                    summary.description = Some(s.clone());
121                }
122            }
123            PropertyId::Units => {
124                if let ClientDataValue::Enumerated(v) = val {
125                    summary.units = Some(*v);
126                }
127            }
128            PropertyId::StatusFlags => {
129                summary.status_flags = Some(val.clone());
130            }
131            _ => {}
132        }
133    }
134
135    summary
136}