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