Skip to main content

bacnet_client/client/
cov.rs

1use super::*;
2use bacnet_services::cov::{SubscribeCOVPropertyRequest, SubscribeCOVRequest};
3use bacnet_types::enums::PropertyIdentifier;
4use bacnet_types::primitives::ObjectIdentifier;
5use std::num::NonZeroU32;
6
7/// Parameters of a single-property COV subscription with an explicit finite lifetime.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct CovPropertySubscription {
10    /// Subscriber process identifier echoed in notifications.
11    pub subscriber_process_identifier: u32,
12    /// Object whose property is monitored.
13    pub monitored_object_identifier: ObjectIdentifier,
14    /// Property to monitor.
15    pub monitored_property_identifier: PropertyIdentifier,
16    /// Optional array index of the monitored property.
17    pub monitored_property_array_index: Option<u32>,
18    /// Whether notifications are confirmed.
19    pub confirmed: bool,
20    /// Subscription lifetime in seconds; must be positive.
21    pub lifetime: NonZeroU32,
22    /// Optional COV increment for numeric properties.
23    pub cov_increment: Option<f32>,
24}
25
26impl CovPropertySubscription {
27    pub(super) fn into_request(self) -> SubscribeCOVPropertyRequest {
28        SubscribeCOVPropertyRequest {
29            subscriber_process_identifier: self.subscriber_process_identifier,
30            monitored_object_identifier: self.monitored_object_identifier,
31            issue_confirmed_notifications: Some(self.confirmed),
32            lifetime: Some(self.lifetime.get()),
33            monitored_property_identifier: self.monitored_property_identifier,
34            monitored_property_array_index: self.monitored_property_array_index,
35            cov_increment: self.cov_increment,
36        }
37    }
38}
39
40impl<T: TransportPort + 'static> BACnetClient<T> {
41    pub(super) fn subscribe_cov_request(
42        subscriber_process_identifier: u32,
43        monitored_object_identifier: ObjectIdentifier,
44        confirmed: Option<bool>,
45        lifetime: Option<u32>,
46    ) -> SubscribeCOVRequest {
47        SubscribeCOVRequest {
48            subscriber_process_identifier,
49            monitored_object_identifier,
50            issue_confirmed_notifications: confirmed,
51            lifetime,
52        }
53    }
54
55    pub(super) async fn send_subscribe_cov_request(
56        &self,
57        target: ConfirmedTarget<'_>,
58        request: SubscribeCOVRequest,
59    ) -> Result<(), Error> {
60        let mut buf = BytesMut::new();
61        request.encode(&mut buf)?;
62
63        let _ = self
64            .confirmed_request_inner(target, ConfirmedServiceChoice::SUBSCRIBE_COV, &buf)
65            .await?;
66
67        Ok(())
68    }
69
70    fn subscribe_cov_property_request(
71        subscriber_process_identifier: u32,
72        monitored_object_identifier: ObjectIdentifier,
73        confirmed: Option<bool>,
74        lifetime: Option<u32>,
75        monitored_property_identifier: PropertyIdentifier,
76        monitored_property_array_index: Option<u32>,
77        cov_increment: Option<f32>,
78    ) -> SubscribeCOVPropertyRequest {
79        SubscribeCOVPropertyRequest {
80            subscriber_process_identifier,
81            monitored_object_identifier,
82            issue_confirmed_notifications: confirmed,
83            lifetime,
84            monitored_property_identifier,
85            monitored_property_array_index,
86            cov_increment,
87        }
88    }
89
90    pub(super) async fn send_subscribe_cov_property_request(
91        &self,
92        target: ConfirmedTarget<'_>,
93        request: SubscribeCOVPropertyRequest,
94    ) -> Result<(), Error> {
95        let mut buf = BytesMut::new();
96        request.encode(&mut buf)?;
97
98        let _ = self
99            .confirmed_request_inner(target, ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY, &buf)
100            .await?;
101
102        Ok(())
103    }
104
105    /// Subscribe to COV notifications for an object at a directly reachable MAC address.
106    pub async fn subscribe_cov(
107        &self,
108        destination_mac: &[u8],
109        subscriber_process_identifier: u32,
110        monitored_object_identifier: ObjectIdentifier,
111        confirmed: bool,
112        lifetime: Option<u32>,
113    ) -> Result<(), Error> {
114        let request = Self::subscribe_cov_request(
115            subscriber_process_identifier,
116            monitored_object_identifier,
117            Some(confirmed),
118            lifetime,
119        );
120
121        self.send_subscribe_cov_request(
122            ConfirmedTarget::Local {
123                mac: destination_mac,
124            },
125            request,
126        )
127        .await
128    }
129
130    /// Subscribe to COV notifications for a discovered device, auto-routing if needed.
131    pub async fn subscribe_cov_to_device(
132        &self,
133        device_instance: u32,
134        subscriber_process_identifier: u32,
135        monitored_object_identifier: ObjectIdentifier,
136        confirmed: bool,
137        lifetime: Option<u32>,
138    ) -> Result<(), Error> {
139        let request = Self::subscribe_cov_request(
140            subscriber_process_identifier,
141            monitored_object_identifier,
142            Some(confirmed),
143            lifetime,
144        );
145
146        request.validate()?;
147        let (mac, routing) = self.resolve_device(device_instance).await?;
148
149        if let Some((dnet, dadr)) = routing {
150            self.send_subscribe_cov_request(
151                ConfirmedTarget::Routed {
152                    router_mac: &mac,
153                    dest_network: dnet,
154                    dest_mac: &dadr,
155                },
156                request,
157            )
158            .await
159        } else {
160            self.send_subscribe_cov_request(ConfirmedTarget::Local { mac: &mac }, request)
161                .await
162        }
163    }
164
165    /// Cancel a COV subscription on a remote device.
166    pub async fn unsubscribe_cov(
167        &self,
168        destination_mac: &[u8],
169        subscriber_process_identifier: u32,
170        monitored_object_identifier: ObjectIdentifier,
171    ) -> Result<(), Error> {
172        let request = Self::subscribe_cov_request(
173            subscriber_process_identifier,
174            monitored_object_identifier,
175            None,
176            None,
177        );
178
179        self.send_subscribe_cov_request(
180            ConfirmedTarget::Local {
181                mac: destination_mac,
182            },
183            request,
184        )
185        .await
186    }
187
188    /// Cancel a COV subscription for a discovered device, auto-routing if needed.
189    pub async fn unsubscribe_cov_to_device(
190        &self,
191        device_instance: u32,
192        subscriber_process_identifier: u32,
193        monitored_object_identifier: ObjectIdentifier,
194    ) -> Result<(), Error> {
195        let request = Self::subscribe_cov_request(
196            subscriber_process_identifier,
197            monitored_object_identifier,
198            None,
199            None,
200        );
201
202        request.validate()?;
203        let (mac, routing) = self.resolve_device(device_instance).await?;
204
205        if let Some((dnet, dadr)) = routing {
206            self.send_subscribe_cov_request(
207                ConfirmedTarget::Routed {
208                    router_mac: &mac,
209                    dest_network: dnet,
210                    dest_mac: &dadr,
211                },
212                request,
213            )
214            .await
215        } else {
216            self.send_subscribe_cov_request(ConfirmedTarget::Local { mac: &mac }, request)
217                .await
218        }
219    }
220
221    /// Subscribe to COV notifications for a single property at a directly reachable MAC address.
222    /// Lifetime must be positive; use `unsubscribe_cov_property` for cancellation.
223    pub async fn subscribe_cov_property(
224        &self,
225        destination_mac: &[u8],
226        subscription: CovPropertySubscription,
227    ) -> Result<(), Error> {
228        let request = subscription.into_request();
229
230        self.send_subscribe_cov_property_request(
231            ConfirmedTarget::Local {
232                mac: destination_mac,
233            },
234            request,
235        )
236        .await
237    }
238
239    /// Subscribe to COV notifications for a single property on a discovered device,
240    /// auto-routing if needed. Lifetime must be positive; cancellation has a separate method.
241    pub async fn subscribe_cov_property_to_device(
242        &self,
243        device_instance: u32,
244        subscription: CovPropertySubscription,
245    ) -> Result<(), Error> {
246        let request = subscription.into_request();
247
248        request.validate()?;
249        let (mac, routing) = self.resolve_device(device_instance).await?;
250        if let Some((dnet, dadr)) = routing {
251            self.send_subscribe_cov_property_request(
252                ConfirmedTarget::Routed {
253                    router_mac: &mac,
254                    dest_network: dnet,
255                    dest_mac: &dadr,
256                },
257                request,
258            )
259            .await
260        } else {
261            self.send_subscribe_cov_property_request(ConfirmedTarget::Local { mac: &mac }, request)
262                .await
263        }
264    }
265
266    /// Cancel a single-property COV subscription at a directly reachable MAC address.
267    pub async fn unsubscribe_cov_property(
268        &self,
269        destination_mac: &[u8],
270        subscriber_process_identifier: u32,
271        monitored_object_identifier: ObjectIdentifier,
272        monitored_property_identifier: PropertyIdentifier,
273        monitored_property_array_index: Option<u32>,
274    ) -> Result<(), Error> {
275        let request = Self::subscribe_cov_property_request(
276            subscriber_process_identifier,
277            monitored_object_identifier,
278            None,
279            None,
280            monitored_property_identifier,
281            monitored_property_array_index,
282            None,
283        );
284
285        self.send_subscribe_cov_property_request(
286            ConfirmedTarget::Local {
287                mac: destination_mac,
288            },
289            request,
290        )
291        .await
292    }
293
294    /// Cancel a single-property COV subscription for a discovered device,
295    /// auto-routing if needed.
296    pub async fn unsubscribe_cov_property_to_device(
297        &self,
298        device_instance: u32,
299        subscriber_process_identifier: u32,
300        monitored_object_identifier: ObjectIdentifier,
301        monitored_property_identifier: PropertyIdentifier,
302        monitored_property_array_index: Option<u32>,
303    ) -> Result<(), Error> {
304        let request = Self::subscribe_cov_property_request(
305            subscriber_process_identifier,
306            monitored_object_identifier,
307            None,
308            None,
309            monitored_property_identifier,
310            monitored_property_array_index,
311            None,
312        );
313
314        request.validate()?;
315        let (mac, routing) = self.resolve_device(device_instance).await?;
316
317        if let Some((dnet, dadr)) = routing {
318            self.send_subscribe_cov_property_request(
319                ConfirmedTarget::Routed {
320                    router_mac: &mac,
321                    dest_network: dnet,
322                    dest_mac: &dadr,
323                },
324                request,
325            )
326            .await
327        } else {
328            self.send_subscribe_cov_property_request(ConfirmedTarget::Local { mac: &mac }, request)
329                .await
330        }
331    }
332
333    /// Cancel a COV subscription at `target`, the device a
334    /// [`manage_cov_subscription`](Self::manage_cov_subscription) went to.
335    /// The cancel goes the way the subscription did, with no device-table
336    /// lookup, so it reaches the same device even if the table has since
337    /// dropped or moved its entry.
338    pub async fn unsubscribe_cov_target(
339        &self,
340        target: &CovTarget,
341        subscriber_process_identifier: u32,
342        monitored_object_identifier: ObjectIdentifier,
343    ) -> Result<(), Error> {
344        let request = Self::subscribe_cov_request(
345            subscriber_process_identifier,
346            monitored_object_identifier,
347            None,
348            None,
349        );
350        self.send_subscribe_cov_request(target.confirmed(), request)
351            .await
352    }
353
354    /// Cancel a single-property COV subscription at `target`:
355    /// [`unsubscribe_cov_target`](Self::unsubscribe_cov_target) for one
356    /// property.
357    pub async fn unsubscribe_cov_property_target(
358        &self,
359        target: &CovTarget,
360        subscriber_process_identifier: u32,
361        monitored_object_identifier: ObjectIdentifier,
362        monitored_property_identifier: PropertyIdentifier,
363        monitored_property_array_index: Option<u32>,
364    ) -> Result<(), Error> {
365        let request = Self::subscribe_cov_property_request(
366            subscriber_process_identifier,
367            monitored_object_identifier,
368            None,
369            None,
370            monitored_property_identifier,
371            monitored_property_array_index,
372            None,
373        );
374        self.send_subscribe_cov_property_request(target.confirmed(), request)
375            .await
376    }
377
378    /// Get a receiver for incoming COV notifications. Each call returns a new
379    /// independent receiver.
380    pub fn cov_notifications(&self) -> broadcast::Receiver<ReceivedCOVNotification> {
381        self.cov_tx.subscribe()
382    }
383}
384
385#[cfg(test)]
386#[path = "cov_property_validation_tests.rs"]
387mod property_validation_tests;
388
389#[cfg(test)]
390#[path = "cov_request_validation_tests.rs"]
391mod request_validation_tests;