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    /// Write a property 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    /// Write multiple properties on one or more objects on a remote device.
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    /// Write a property on a discovered device, auto-routing if needed.
162    pub async fn read_property_from_devices(
163        &self,
164        requests: Vec<DeviceReadRequest>,
165        max_concurrent: Option<usize>,
166    ) -> Vec<DeviceReadResult> {
167        use futures_util::stream::{self, StreamExt};
168
169        let concurrency = max_concurrent.unwrap_or(DEFAULT_BATCH_CONCURRENCY);
170
171        stream::iter(requests)
172            .map(|req| async move {
173                let result = self
174                    .read_property_from_device(
175                        req.device_instance,
176                        req.object_identifier,
177                        req.property_identifier,
178                        req.property_array_index,
179                    )
180                    .await;
181                DeviceReadResult {
182                    device_instance: req.device_instance,
183                    result,
184                }
185            })
186            .buffer_unordered(concurrency)
187            .collect()
188            .await
189    }
190
191    /// Read multiple properties from multiple devices concurrently (RPM batch).
192    ///
193    /// Sends an RPM to each device concurrently. This is the most efficient
194    /// way to poll many properties across many devices — RPM batches within
195    /// a single device, and this method batches across devices.
196    pub async fn read_property_multiple_from_devices(
197        &self,
198        requests: Vec<DeviceRpmRequest>,
199        max_concurrent: Option<usize>,
200    ) -> Vec<DeviceRpmResult> {
201        use futures_util::stream::{self, StreamExt};
202
203        let concurrency = max_concurrent.unwrap_or(DEFAULT_BATCH_CONCURRENCY);
204
205        stream::iter(requests)
206            .map(|req| async move {
207                let result = self
208                    .read_property_multiple_from_device(req.device_instance, req.specs)
209                    .await;
210                DeviceRpmResult {
211                    device_instance: req.device_instance,
212                    result,
213                }
214            })
215            .buffer_unordered(concurrency)
216            .collect()
217            .await
218    }
219
220    /// Write a property on multiple devices concurrently.
221    ///
222    /// All writes are dispatched concurrently (up to `max_concurrent`,
223    /// default 32). Results are returned in completion order.
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    /// Read multiple properties from 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    /// Read multiple properties from 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    pub async fn write_property_to_devices(
365        &self,
366        requests: Vec<DeviceWriteRequest>,
367        max_concurrent: Option<usize>,
368    ) -> Vec<DeviceWriteResult> {
369        use futures_util::stream::{self, StreamExt};
370
371        let concurrency = max_concurrent.unwrap_or(DEFAULT_BATCH_CONCURRENCY);
372
373        stream::iter(requests)
374            .map(|req| async move {
375                let result = self
376                    .write_property_to_device(
377                        req.device_instance,
378                        req.object_identifier,
379                        req.property_identifier,
380                        req.property_array_index,
381                        req.property_value,
382                        req.priority,
383                    )
384                    .await;
385                DeviceWriteResult {
386                    device_instance: req.device_instance,
387                    result,
388                }
389            })
390            .buffer_unordered(concurrency)
391            .collect()
392            .await
393    }
394}