1use std::num::NonZeroUsize;
2
3use super::*;
4
5impl<T: TransportPort + 'static> BACnetClient<T> {
6 pub async fn read_property(
12 &self,
13 destination_mac: &[u8],
14 object_identifier: bacnet_types::primitives::ObjectIdentifier,
15 property_identifier: bacnet_types::enums::PropertyIdentifier,
16 property_array_index: Option<u32>,
17 ) -> Result<bacnet_services::read_property::ReadPropertyACK, Error> {
18 use bacnet_services::read_property::ReadPropertyRequest;
19
20 let request = ReadPropertyRequest {
21 object_identifier,
22 property_identifier,
23 property_array_index,
24 };
25 let mut buf = BytesMut::new();
26 request.encode(&mut buf);
27
28 let response_data = self
29 .confirmed_request(destination_mac, ConfirmedServiceChoice::READ_PROPERTY, &buf)
30 .await?;
31
32 crate::read_property::decode_ack(&request, &response_data)
33 }
34
35 pub async fn read_property_from_device(
37 &self,
38 device_instance: u32,
39 object_identifier: bacnet_types::primitives::ObjectIdentifier,
40 property_identifier: bacnet_types::enums::PropertyIdentifier,
41 property_array_index: Option<u32>,
42 ) -> Result<bacnet_services::read_property::ReadPropertyACK, Error> {
43 let (mac, routing) = {
44 let dt = self.device_table.lock().await;
45 let device = dt.get(device_instance).ok_or_else(|| {
46 Error::Encoding(format!("device {device_instance} not in device table"))
47 })?;
48 let routing = match (&device.source_network, &device.source_address) {
49 (Some(snet), Some(sadr)) => Some((*snet, sadr.to_vec())),
50 _ => None,
51 };
52 (device.mac_address.to_vec(), routing)
53 };
54
55 if let Some((dnet, dadr)) = routing {
56 self.read_property_routed(
57 &mac,
58 dnet,
59 &dadr,
60 object_identifier,
61 property_identifier,
62 property_array_index,
63 )
64 .await
65 } else {
66 self.read_property(
67 &mac,
68 object_identifier,
69 property_identifier,
70 property_array_index,
71 )
72 .await
73 }
74 }
75
76 pub async fn read_property_routed(
78 &self,
79 router_mac: &[u8],
80 dest_network: u16,
81 dest_mac: &[u8],
82 object_identifier: bacnet_types::primitives::ObjectIdentifier,
83 property_identifier: bacnet_types::enums::PropertyIdentifier,
84 property_array_index: Option<u32>,
85 ) -> Result<bacnet_services::read_property::ReadPropertyACK, Error> {
86 use bacnet_services::read_property::ReadPropertyRequest;
87
88 let request = ReadPropertyRequest {
89 object_identifier,
90 property_identifier,
91 property_array_index,
92 };
93 let mut buf = BytesMut::new();
94 request.encode(&mut buf);
95
96 let response_data = self
97 .confirmed_request_routed(
98 router_mac,
99 dest_network,
100 dest_mac,
101 ConfirmedServiceChoice::READ_PROPERTY,
102 &buf,
103 )
104 .await?;
105
106 crate::read_property::decode_ack(&request, &response_data)
107 }
108
109 pub async fn read_property_multiple(
111 &self,
112 destination_mac: &[u8],
113 specs: Vec<bacnet_types::constructed::ReadAccessSpecification>,
114 ) -> Result<bacnet_services::rpm::ReadPropertyMultipleACK, Error> {
115 use bacnet_services::rpm::{ReadPropertyMultipleACK, ReadPropertyMultipleRequest};
116
117 let request = ReadPropertyMultipleRequest {
118 list_of_read_access_specs: specs,
119 };
120 let mut buf = BytesMut::new();
121 request.encode(&mut buf)?;
122
123 let response_data = self
124 .confirmed_request(
125 destination_mac,
126 ConfirmedServiceChoice::READ_PROPERTY_MULTIPLE,
127 &buf,
128 )
129 .await?;
130
131 ReadPropertyMultipleACK::decode(&response_data)
132 }
133
134 pub async fn read_property_multiple_from_device(
136 &self,
137 device_instance: u32,
138 specs: Vec<bacnet_types::constructed::ReadAccessSpecification>,
139 ) -> Result<bacnet_services::rpm::ReadPropertyMultipleACK, Error> {
140 let (mac, routing) = self.resolve_device(device_instance).await?;
141
142 if let Some((dnet, dadr)) = routing {
143 use bacnet_services::rpm::{ReadPropertyMultipleACK, ReadPropertyMultipleRequest};
144
145 let request = ReadPropertyMultipleRequest {
146 list_of_read_access_specs: specs,
147 };
148 let mut buf = BytesMut::new();
149 request.encode(&mut buf)?;
150
151 let response_data = self
152 .confirmed_request_routed(
153 &mac,
154 dnet,
155 &dadr,
156 ConfirmedServiceChoice::READ_PROPERTY_MULTIPLE,
157 &buf,
158 )
159 .await?;
160
161 ReadPropertyMultipleACK::decode(&response_data)
162 } else {
163 self.read_property_multiple(&mac, specs).await
164 }
165 }
166
167 pub async fn read_property_from_devices(
175 &self,
176 requests: Vec<DeviceReadRequest>,
177 max_concurrent: Option<NonZeroUsize>,
178 ) -> Vec<DeviceReadResult> {
179 use futures_util::stream::{self, StreamExt};
180
181 let concurrency = max_concurrent.map_or(DEFAULT_BATCH_CONCURRENCY, NonZeroUsize::get);
182
183 stream::iter(requests.into_iter().enumerate())
184 .map(|(request_index, req)| async move {
185 let result = self
186 .read_property_from_device(
187 req.device_instance,
188 req.object_identifier,
189 req.property_identifier,
190 req.property_array_index,
191 )
192 .await;
193 DeviceReadResult {
194 request_index,
195 device_instance: req.device_instance,
196 result,
197 }
198 })
199 .buffer_unordered(concurrency)
200 .collect()
201 .await
202 }
203
204 pub async fn read_property_multiple_from_devices(
213 &self,
214 requests: Vec<DeviceRpmRequest>,
215 max_concurrent: Option<NonZeroUsize>,
216 ) -> Vec<DeviceRpmResult> {
217 use futures_util::stream::{self, StreamExt};
218
219 let concurrency = max_concurrent.map_or(DEFAULT_BATCH_CONCURRENCY, NonZeroUsize::get);
220
221 stream::iter(requests.into_iter().enumerate())
222 .map(|(request_index, req)| async move {
223 let result = self
224 .read_property_multiple_from_device(req.device_instance, req.specs)
225 .await;
226 DeviceRpmResult {
227 request_index,
228 device_instance: req.device_instance,
229 result,
230 }
231 })
232 .buffer_unordered(concurrency)
233 .collect()
234 .await
235 }
236
237 pub async fn write_property(
239 &self,
240 destination_mac: &[u8],
241 object_identifier: bacnet_types::primitives::ObjectIdentifier,
242 property_identifier: bacnet_types::enums::PropertyIdentifier,
243 property_array_index: Option<u32>,
244 property_value: Vec<u8>,
245 priority: Option<u8>,
246 ) -> Result<(), Error> {
247 use bacnet_services::write_property::WritePropertyRequest;
248
249 let request = WritePropertyRequest {
250 object_identifier,
251 property_identifier,
252 property_array_index,
253 property_value,
254 priority,
255 };
256 let mut buf = BytesMut::new();
257 request.encode(&mut buf)?;
258
259 let _ = self
260 .confirmed_request(
261 destination_mac,
262 ConfirmedServiceChoice::WRITE_PROPERTY,
263 &buf,
264 )
265 .await?;
266
267 Ok(())
268 }
269
270 pub async fn write_property_multiple(
272 &self,
273 destination_mac: &[u8],
274 specs: Vec<bacnet_services::wpm::WriteAccessSpecification>,
275 ) -> Result<(), Error> {
276 use bacnet_services::wpm::WritePropertyMultipleRequest;
277
278 let request = WritePropertyMultipleRequest {
279 list_of_write_access_specs: specs,
280 };
281 let mut buf = BytesMut::new();
282 request.encode(&mut buf)?;
283
284 let _ = self
285 .confirmed_request(
286 destination_mac,
287 ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE,
288 &buf,
289 )
290 .await?;
291
292 Ok(())
293 }
294
295 pub async fn write_property_to_device(
301 &self,
302 device_instance: u32,
303 object_identifier: bacnet_types::primitives::ObjectIdentifier,
304 property_identifier: bacnet_types::enums::PropertyIdentifier,
305 property_array_index: Option<u32>,
306 property_value: Vec<u8>,
307 priority: Option<u8>,
308 ) -> Result<(), Error> {
309 bacnet_services::write_property::validate_priority(priority)?;
310 let (mac, routing) = self.resolve_device(device_instance).await?;
311
312 if let Some((dnet, dadr)) = routing {
313 use bacnet_services::write_property::WritePropertyRequest;
314
315 let request = WritePropertyRequest {
316 object_identifier,
317 property_identifier,
318 property_array_index,
319 property_value,
320 priority,
321 };
322 let mut buf = BytesMut::new();
323 request.encode(&mut buf)?;
324
325 let _ = self
326 .confirmed_request_routed(
327 &mac,
328 dnet,
329 &dadr,
330 ConfirmedServiceChoice::WRITE_PROPERTY,
331 &buf,
332 )
333 .await?;
334 Ok(())
335 } else {
336 self.write_property(
337 &mac,
338 object_identifier,
339 property_identifier,
340 property_array_index,
341 property_value,
342 priority,
343 )
344 .await
345 }
346 }
347
348 pub async fn write_property_multiple_to_device(
350 &self,
351 device_instance: u32,
352 specs: Vec<bacnet_services::wpm::WriteAccessSpecification>,
353 ) -> Result<(), Error> {
354 use bacnet_services::wpm::WritePropertyMultipleRequest;
355 let request = WritePropertyMultipleRequest {
356 list_of_write_access_specs: specs,
357 };
358 let mut buf = BytesMut::new();
359 request.encode(&mut buf)?;
360 let (mac, routing) = self.resolve_device(device_instance).await?;
361 if let Some((dnet, dadr)) = routing {
362 self.confirmed_request_routed(
363 &mac,
364 dnet,
365 &dadr,
366 ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE,
367 &buf,
368 )
369 .await?;
370 } else {
371 self.confirmed_request(&mac, ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE, &buf)
372 .await?;
373 }
374 Ok(())
375 }
376
377 pub async fn write_property_to_devices(
385 &self,
386 requests: Vec<DeviceWriteRequest>,
387 max_concurrent: Option<NonZeroUsize>,
388 ) -> Vec<DeviceWriteResult> {
389 use futures_util::stream::{self, StreamExt};
390
391 let concurrency = max_concurrent.map_or(DEFAULT_BATCH_CONCURRENCY, NonZeroUsize::get);
392
393 stream::iter(requests.into_iter().enumerate())
394 .map(|(request_index, req)| async move {
395 let result = self
396 .write_property_to_device(
397 req.device_instance,
398 req.object_identifier,
399 req.property_identifier,
400 req.property_array_index,
401 req.property_value,
402 req.priority,
403 )
404 .await;
405 DeviceWriteResult {
406 request_index,
407 device_instance: req.device_instance,
408 result,
409 }
410 })
411 .buffer_unordered(concurrency)
412 .collect()
413 .await
414 }
415}
416
417#[cfg(test)]
418mod doc_tests {
419 const SOURCE: &str = include_str!("property.rs");
420
421 fn doc_block_for(fn_name: &str) -> String {
422 let needle = format!("pub async fn {fn_name}(");
423 let lines: Vec<_> = SOURCE.lines().collect();
424 let signature_index = lines
425 .iter()
426 .position(|line| line.trim_start().starts_with(&needle))
427 .unwrap_or_else(|| panic!("missing function {fn_name}"));
428 let mut docs = Vec::new();
429 let mut index = signature_index;
430 while index > 0 {
431 index -= 1;
432 let line = lines[index].trim_start();
433 if let Some(doc) = line.strip_prefix("///") {
434 docs.push(doc.trim());
435 } else {
436 break;
437 }
438 }
439 docs.reverse();
440 docs.join(" ")
441 }
442
443 #[test]
444 fn property_method_docblocks_match_their_methods() {
445 let cases = [
446 (
447 "read_property_multiple",
448 "Read multiple properties from one or more objects on a remote device.",
449 "Write",
450 ),
451 (
452 "read_property_multiple_from_device",
453 "Read multiple properties from a discovered device, auto-routing if needed.",
454 "Write",
455 ),
456 (
457 "read_property_from_devices",
458 "Read one property from multiple discovered devices concurrently.",
459 "Write",
460 ),
461 (
462 "write_property",
463 "Write a property on a remote device.",
464 "multiple devices concurrently",
465 ),
466 (
467 "write_property_multiple",
468 "Write multiple properties on one or more objects on a remote device.",
469 "Read",
470 ),
471 (
472 "write_property_to_device",
473 "Write a property on a discovered device, auto-routing if needed.",
474 "Read",
475 ),
476 (
477 "write_property_to_devices",
478 "Write one property on multiple discovered devices concurrently.",
479 "Read",
480 ),
481 ];
482
483 for (fn_name, expected, forbidden) in cases {
484 let docs = doc_block_for(fn_name);
485 assert!(
486 docs.contains(expected),
487 "{fn_name} docs did not contain expected text: {docs}"
488 );
489 assert!(
490 !docs.contains(forbidden),
491 "{fn_name} docs still contain misattributed text: {docs}"
492 );
493 }
494 }
495}