1use crate::{BacnetClient, ClientDataValue, ClientError};
5use rustbac_core::types::{ObjectId, ObjectType, PropertyId};
6use rustbac_datalink::{DataLink, DataLinkAddress};
7
8#[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#[derive(Debug, Clone, Default)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct DeviceInfo {
25 pub vendor_name: Option<String>,
26 pub model_name: Option<String>,
27 pub firmware_revision: Option<String>,
28}
29
30#[derive(Debug, Clone)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct DeviceWalkResult {
34 pub device_id: ObjectId,
35 pub device_info: DeviceInfo,
36 pub objects: Vec<ObjectSummary>,
37}
38
39pub async fn walk_device<D: DataLink>(
42 client: &BacnetClient<D>,
43 addr: DataLinkAddress,
44 device_id: ObjectId,
45) -> Result<DeviceWalkResult, ClientError> {
46 let object_list_value = client
48 .read_property(addr, device_id, PropertyId::ObjectList)
49 .await?;
50
51 let object_ids = extract_object_ids(&object_list_value);
52
53 let properties = &[
55 PropertyId::ObjectName,
56 PropertyId::ObjectType,
57 PropertyId::PresentValue,
58 PropertyId::Description,
59 PropertyId::Units,
60 PropertyId::StatusFlags,
61 ];
62
63 let mut objects = Vec::with_capacity(object_ids.len());
64 for &oid in &object_ids {
65 let props = client.read_property_multiple(addr, oid, properties).await;
66
67 let summary = match props {
68 Ok(prop_values) => build_summary(oid, &prop_values),
69 Err(_) => ObjectSummary {
70 object_id: oid,
71 object_name: None,
72 object_type: oid.object_type(),
73 present_value: None,
74 description: None,
75 units: None,
76 status_flags: None,
77 },
78 };
79 objects.push(summary);
80 }
81
82 let device_info = read_device_info(client, addr, device_id).await;
84
85 Ok(DeviceWalkResult {
86 device_id,
87 device_info,
88 objects,
89 })
90}
91
92async fn read_device_info<D: DataLink>(
93 client: &BacnetClient<D>,
94 addr: DataLinkAddress,
95 device_id: ObjectId,
96) -> DeviceInfo {
97 let info_props = &[
98 PropertyId::VendorName,
99 PropertyId::ModelName,
100 PropertyId::FirmwareRevision,
101 ];
102
103 let prop_values = match client
104 .read_property_multiple(addr, device_id, info_props)
105 .await
106 {
107 Ok(v) => v,
108 Err(_) => return DeviceInfo::default(),
109 };
110
111 let mut info = DeviceInfo::default();
112 for (pid, val) in &prop_values {
113 if let ClientDataValue::CharacterString(s) = val {
114 match pid {
115 PropertyId::VendorName => info.vendor_name = Some(s.clone()),
116 PropertyId::ModelName => info.model_name = Some(s.clone()),
117 PropertyId::FirmwareRevision => info.firmware_revision = Some(s.clone()),
118 _ => {}
119 }
120 }
121 }
122 info
123}
124
125fn extract_object_ids(value: &ClientDataValue) -> Vec<ObjectId> {
126 match value {
127 ClientDataValue::ObjectId(oid) => vec![*oid],
128 ClientDataValue::Constructed { values, .. } => values
129 .iter()
130 .filter_map(|v| {
131 if let ClientDataValue::ObjectId(oid) = v {
132 Some(*oid)
133 } else {
134 None
135 }
136 })
137 .collect(),
138 _ => vec![],
139 }
140}
141
142fn build_summary(oid: ObjectId, props: &[(PropertyId, ClientDataValue)]) -> ObjectSummary {
143 let mut summary = ObjectSummary {
144 object_id: oid,
145 object_name: None,
146 object_type: oid.object_type(),
147 present_value: None,
148 description: None,
149 units: None,
150 status_flags: None,
151 };
152
153 for (pid, val) in props {
154 match pid {
155 PropertyId::ObjectName => {
156 if let ClientDataValue::CharacterString(s) = val {
157 summary.object_name = Some(s.clone());
158 }
159 }
160 PropertyId::ObjectType => {
161 if let ClientDataValue::Enumerated(v) = val {
162 summary.object_type = ObjectType::from_u16(*v as u16);
163 }
164 }
165 PropertyId::PresentValue => {
166 summary.present_value = Some(val.clone());
167 }
168 PropertyId::Description => {
169 if let ClientDataValue::CharacterString(s) = val {
170 summary.description = Some(s.clone());
171 }
172 }
173 PropertyId::Units => {
174 if let ClientDataValue::Enumerated(v) = val {
175 summary.units = Some(*v);
176 }
177 }
178 PropertyId::StatusFlags => {
179 summary.status_flags = Some(val.clone());
180 }
181 _ => {}
182 }
183 }
184
185 summary
186}