Skip to main content

bacnet_client/client/
property.rs

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