Skip to main content

bacnet_objects/
forwarder.rs

1//! NotificationForwarder object (type 51) per ASHRAE 135-2020 Clause 12.51.
2
3use bacnet_types::enums::{ObjectType, PropertyIdentifier};
4use bacnet_types::error::Error;
5use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
6use std::borrow::Cow;
7
8use crate::common::{self, read_common_properties};
9use crate::traits::BACnetObject;
10
11/// BACnet NotificationForwarder object (type 51).
12///
13/// Forwards event notifications to remote devices. Filters which
14/// notifications to forward based on process identifier and local-only flags.
15pub struct NotificationForwarderObject {
16    oid: ObjectIdentifier,
17    name: String,
18    description: String,
19    status_flags: StatusFlags,
20    out_of_service: bool,
21    reliability: u32,
22    /// Process identifier filter — list of process IDs to forward for.
23    pub process_identifier_filter: Vec<u32>,
24    /// Number of subscribed recipients (stored as count).
25    pub subscribed_recipients: u32,
26    /// Whether to forward only local notifications.
27    pub local_forwarding_only: bool,
28    /// Whether event detection is enabled.
29    pub event_detection_enable: bool,
30}
31
32impl NotificationForwarderObject {
33    /// Create a new NotificationForwarder object.
34    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
35        let oid = ObjectIdentifier::new(ObjectType::NOTIFICATION_FORWARDER, instance)?;
36        Ok(Self {
37            oid,
38            name: name.into(),
39            description: String::new(),
40            status_flags: StatusFlags::empty(),
41            out_of_service: false,
42            reliability: 0,
43            process_identifier_filter: Vec::new(),
44            subscribed_recipients: 0,
45            local_forwarding_only: false,
46            event_detection_enable: true,
47        })
48    }
49}
50
51impl BACnetObject for NotificationForwarderObject {
52    fn object_identifier(&self) -> ObjectIdentifier {
53        self.oid
54    }
55
56    fn object_name(&self) -> &str {
57        &self.name
58    }
59
60    fn read_property(
61        &self,
62        property: PropertyIdentifier,
63        array_index: Option<u32>,
64    ) -> Result<PropertyValue, Error> {
65        if let Some(result) = read_common_properties!(self, property, array_index) {
66            return result;
67        }
68        match property {
69            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
70                ObjectType::NOTIFICATION_FORWARDER.to_raw(),
71            )),
72            p if p == PropertyIdentifier::PROCESS_IDENTIFIER_FILTER => Ok(PropertyValue::List(
73                self.process_identifier_filter
74                    .iter()
75                    .map(|id| PropertyValue::Unsigned(*id as u64))
76                    .collect(),
77            )),
78            p if p == PropertyIdentifier::SUBSCRIBED_RECIPIENTS => {
79                Ok(PropertyValue::Unsigned(self.subscribed_recipients as u64))
80            }
81            p if p == PropertyIdentifier::LOCAL_FORWARDING_ONLY => {
82                Ok(PropertyValue::Boolean(self.local_forwarding_only))
83            }
84            p if p == PropertyIdentifier::EVENT_DETECTION_ENABLE => {
85                Ok(PropertyValue::Boolean(self.event_detection_enable))
86            }
87            p if p == PropertyIdentifier::RECIPIENT_LIST => {
88                Ok(PropertyValue::List(Vec::new())) // Empty recipient list by default
89            }
90            p if p == PropertyIdentifier::PROCESS_IDENTIFIER_FILTER => {
91                Ok(PropertyValue::List(Vec::new()))
92            }
93            _ => Err(common::unknown_property_error()),
94        }
95    }
96
97    fn write_property(
98        &mut self,
99        property: PropertyIdentifier,
100        _array_index: Option<u32>,
101        value: PropertyValue,
102        _priority: Option<u8>,
103    ) -> Result<(), Error> {
104        if property == PropertyIdentifier::LOCAL_FORWARDING_ONLY {
105            if let PropertyValue::Boolean(v) = value {
106                self.local_forwarding_only = v;
107                return Ok(());
108            }
109            return Err(common::invalid_data_type_error());
110        }
111        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
112            if let PropertyValue::Boolean(v) = value {
113                self.event_detection_enable = v;
114                return Ok(());
115            }
116            return Err(common::invalid_data_type_error());
117        }
118        if let Some(result) =
119            common::write_out_of_service(&mut self.out_of_service, property, &value)
120        {
121            return result;
122        }
123        if let Some(result) = common::write_description(&mut self.description, property, &value) {
124            return result;
125        }
126        Err(common::write_access_denied_error())
127    }
128
129    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
130        static PROPS: &[PropertyIdentifier] = &[
131            PropertyIdentifier::OBJECT_IDENTIFIER,
132            PropertyIdentifier::OBJECT_NAME,
133            PropertyIdentifier::DESCRIPTION,
134            PropertyIdentifier::OBJECT_TYPE,
135            PropertyIdentifier::STATUS_FLAGS,
136            PropertyIdentifier::OUT_OF_SERVICE,
137            PropertyIdentifier::RELIABILITY,
138            PropertyIdentifier::PROCESS_IDENTIFIER_FILTER,
139            PropertyIdentifier::SUBSCRIBED_RECIPIENTS,
140            PropertyIdentifier::LOCAL_FORWARDING_ONLY,
141            PropertyIdentifier::EVENT_DETECTION_ENABLE,
142        ];
143        Cow::Borrowed(PROPS)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn create_notification_forwarder() {
153        let nf = NotificationForwarderObject::new(1, "NF-1").unwrap();
154        assert_eq!(
155            nf.object_identifier().object_type(),
156            ObjectType::NOTIFICATION_FORWARDER
157        );
158        assert_eq!(nf.object_identifier().instance_number(), 1);
159        assert_eq!(nf.object_name(), "NF-1");
160    }
161
162    #[test]
163    fn object_type() {
164        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
165        let val = nf
166            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
167            .unwrap();
168        assert_eq!(
169            val,
170            PropertyValue::Enumerated(ObjectType::NOTIFICATION_FORWARDER.to_raw())
171        );
172    }
173
174    #[test]
175    fn process_identifier_filter_empty() {
176        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
177        let val = nf
178            .read_property(PropertyIdentifier::PROCESS_IDENTIFIER_FILTER, None)
179            .unwrap();
180        if let PropertyValue::List(items) = val {
181            assert!(items.is_empty());
182        } else {
183            panic!("Expected List");
184        }
185    }
186
187    #[test]
188    fn process_identifier_filter_with_values() {
189        let mut nf = NotificationForwarderObject::new(1, "NF").unwrap();
190        nf.process_identifier_filter.push(100);
191        nf.process_identifier_filter.push(200);
192
193        let val = nf
194            .read_property(PropertyIdentifier::PROCESS_IDENTIFIER_FILTER, None)
195            .unwrap();
196        if let PropertyValue::List(items) = val {
197            assert_eq!(items.len(), 2);
198            assert_eq!(items[0], PropertyValue::Unsigned(100));
199            assert_eq!(items[1], PropertyValue::Unsigned(200));
200        } else {
201            panic!("Expected List");
202        }
203    }
204
205    #[test]
206    fn local_forwarding_only_default() {
207        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
208        let val = nf
209            .read_property(PropertyIdentifier::LOCAL_FORWARDING_ONLY, None)
210            .unwrap();
211        assert_eq!(val, PropertyValue::Boolean(false));
212    }
213
214    #[test]
215    fn write_local_forwarding_only() {
216        let mut nf = NotificationForwarderObject::new(1, "NF").unwrap();
217        nf.write_property(
218            PropertyIdentifier::LOCAL_FORWARDING_ONLY,
219            None,
220            PropertyValue::Boolean(true),
221            None,
222        )
223        .unwrap();
224        let val = nf
225            .read_property(PropertyIdentifier::LOCAL_FORWARDING_ONLY, None)
226            .unwrap();
227        assert_eq!(val, PropertyValue::Boolean(true));
228    }
229
230    #[test]
231    fn write_local_forwarding_only_wrong_type() {
232        let mut nf = NotificationForwarderObject::new(1, "NF").unwrap();
233        let result = nf.write_property(
234            PropertyIdentifier::LOCAL_FORWARDING_ONLY,
235            None,
236            PropertyValue::Unsigned(1),
237            None,
238        );
239        assert!(result.is_err());
240    }
241
242    #[test]
243    fn event_detection_enable_default() {
244        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
245        let val = nf
246            .read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
247            .unwrap();
248        assert_eq!(val, PropertyValue::Boolean(true));
249    }
250
251    #[test]
252    fn write_event_detection_enable() {
253        let mut nf = NotificationForwarderObject::new(1, "NF").unwrap();
254        nf.write_property(
255            PropertyIdentifier::EVENT_DETECTION_ENABLE,
256            None,
257            PropertyValue::Boolean(false),
258            None,
259        )
260        .unwrap();
261        let val = nf
262            .read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
263            .unwrap();
264        assert_eq!(val, PropertyValue::Boolean(false));
265    }
266
267    #[test]
268    fn subscribed_recipients_default() {
269        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
270        let val = nf
271            .read_property(PropertyIdentifier::SUBSCRIBED_RECIPIENTS, None)
272            .unwrap();
273        assert_eq!(val, PropertyValue::Unsigned(0));
274    }
275
276    #[test]
277    fn property_list() {
278        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
279        let props = nf.property_list();
280        assert!(props.contains(&PropertyIdentifier::PROCESS_IDENTIFIER_FILTER));
281        assert!(props.contains(&PropertyIdentifier::SUBSCRIBED_RECIPIENTS));
282        assert!(props.contains(&PropertyIdentifier::LOCAL_FORWARDING_ONLY));
283        assert!(props.contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
284    }
285}