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 pub location: Option<String>,
29 pub description: Option<String>,
30 pub max_apdu_length: Option<u32>,
31 pub segmentation_supported: Option<u32>,
32 pub protocol_version: Option<u32>,
33 pub application_software_version: Option<String>,
34}
35
36#[derive(Debug, Clone)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39pub struct DeviceWalkResult {
40 pub device_id: ObjectId,
41 pub device_info: DeviceInfo,
42 pub objects: Vec<ObjectSummary>,
43}
44
45pub async fn walk_device<D: DataLink>(
48 client: &BacnetClient<D>,
49 addr: DataLinkAddress,
50 device_id: ObjectId,
51) -> Result<DeviceWalkResult, ClientError> {
52 let object_list_value = client
54 .read_property(addr, device_id, PropertyId::ObjectList)
55 .await?;
56
57 let object_ids = extract_object_ids(&object_list_value);
58
59 let properties = &[
61 PropertyId::ObjectName,
62 PropertyId::ObjectType,
63 PropertyId::PresentValue,
64 PropertyId::Description,
65 PropertyId::Units,
66 PropertyId::StatusFlags,
67 ];
68
69 let mut objects = Vec::with_capacity(object_ids.len());
70 for &oid in &object_ids {
71 let props = client.read_property_multiple(addr, oid, properties).await;
72
73 let summary = match props {
74 Ok(prop_values) => build_summary(oid, &prop_values),
75 Err(_) => ObjectSummary {
76 object_id: oid,
77 object_name: None,
78 object_type: oid.object_type(),
79 present_value: None,
80 description: None,
81 units: None,
82 status_flags: None,
83 },
84 };
85 objects.push(summary);
86 }
87
88 let device_info = read_device_info(client, addr, device_id).await;
90
91 Ok(DeviceWalkResult {
92 device_id,
93 device_info,
94 objects,
95 })
96}
97
98async fn read_device_info<D: DataLink>(
99 client: &BacnetClient<D>,
100 addr: DataLinkAddress,
101 device_id: ObjectId,
102) -> DeviceInfo {
103 let info_props = &[
104 PropertyId::VendorName,
105 PropertyId::ModelName,
106 PropertyId::FirmwareRevision,
107 PropertyId::Location,
108 PropertyId::Description,
109 PropertyId::MaxApduLengthAccepted,
110 PropertyId::SegmentationSupported,
111 PropertyId::ProtocolVersion,
112 PropertyId::ApplicationSoftwareVersion,
113 ];
114
115 let prop_values = match client
116 .read_property_multiple(addr, device_id, info_props)
117 .await
118 {
119 Ok(v) => v,
120 Err(_) => return DeviceInfo::default(),
121 };
122
123 let mut info = DeviceInfo::default();
124 for (pid, val) in &prop_values {
125 match (pid, val) {
126 (PropertyId::VendorName, ClientDataValue::CharacterString(s)) => {
127 info.vendor_name = Some(s.clone());
128 }
129 (PropertyId::ModelName, ClientDataValue::CharacterString(s)) => {
130 info.model_name = Some(s.clone());
131 }
132 (PropertyId::FirmwareRevision, ClientDataValue::CharacterString(s)) => {
133 info.firmware_revision = Some(s.clone());
134 }
135 (PropertyId::Location, ClientDataValue::CharacterString(s)) => {
136 info.location = Some(s.clone());
137 }
138 (PropertyId::Description, ClientDataValue::CharacterString(s)) => {
139 info.description = Some(s.clone());
140 }
141 (PropertyId::MaxApduLengthAccepted, ClientDataValue::Unsigned(v)) => {
142 info.max_apdu_length = Some(*v);
143 }
144 (PropertyId::SegmentationSupported, ClientDataValue::Enumerated(v)) => {
145 info.segmentation_supported = Some(*v);
146 }
147 (PropertyId::ProtocolVersion, ClientDataValue::Unsigned(v)) => {
148 info.protocol_version = Some(*v);
149 }
150 (PropertyId::ApplicationSoftwareVersion, ClientDataValue::CharacterString(s)) => {
151 info.application_software_version = Some(s.clone());
152 }
153 _ => {}
154 }
155 }
156 info
157}
158
159fn extract_object_ids(value: &ClientDataValue) -> Vec<ObjectId> {
160 match value {
161 ClientDataValue::ObjectId(oid) => vec![*oid],
162 ClientDataValue::Constructed { values, .. } => values
163 .iter()
164 .filter_map(|v| {
165 if let ClientDataValue::ObjectId(oid) = v {
166 Some(*oid)
167 } else {
168 None
169 }
170 })
171 .collect(),
172 _ => vec![],
173 }
174}
175
176fn build_summary(oid: ObjectId, props: &[(PropertyId, ClientDataValue)]) -> ObjectSummary {
177 let mut summary = ObjectSummary {
178 object_id: oid,
179 object_name: None,
180 object_type: oid.object_type(),
181 present_value: None,
182 description: None,
183 units: None,
184 status_flags: None,
185 };
186
187 for (pid, val) in props {
188 match pid {
189 PropertyId::ObjectName => {
190 if let ClientDataValue::CharacterString(s) = val {
191 summary.object_name = Some(s.clone());
192 }
193 }
194 PropertyId::ObjectType => {
195 if let ClientDataValue::Enumerated(v) = val {
196 summary.object_type = ObjectType::from_u16(*v as u16);
197 }
198 }
199 PropertyId::PresentValue => {
200 summary.present_value = Some(val.clone());
201 }
202 PropertyId::Description => {
203 if let ClientDataValue::CharacterString(s) = val {
204 summary.description = Some(s.clone());
205 }
206 }
207 PropertyId::Units => {
208 if let ClientDataValue::Enumerated(v) = val {
209 summary.units = Some(*v);
210 }
211 }
212 PropertyId::StatusFlags => {
213 summary.status_flags = Some(val.clone());
214 }
215 _ => {}
216 }
217 }
218
219 summary
220}