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            _ => Err(common::unknown_property_error()),
88        }
89    }
90
91    fn write_property(
92        &mut self,
93        property: PropertyIdentifier,
94        _array_index: Option<u32>,
95        value: PropertyValue,
96        _priority: Option<u8>,
97    ) -> Result<(), Error> {
98        if property == PropertyIdentifier::LOCAL_FORWARDING_ONLY {
99            if let PropertyValue::Boolean(v) = value {
100                self.local_forwarding_only = v;
101                return Ok(());
102            }
103            return Err(common::invalid_data_type_error());
104        }
105        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
106            if let PropertyValue::Boolean(v) = value {
107                self.event_detection_enable = v;
108                return Ok(());
109            }
110            return Err(common::invalid_data_type_error());
111        }
112        if let Some(result) =
113            common::write_out_of_service(&mut self.out_of_service, property, &value)
114        {
115            return result;
116        }
117        if let Some(result) = common::write_description(&mut self.description, property, &value) {
118            return result;
119        }
120        Err(common::write_access_denied_error())
121    }
122
123    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
124        static PROPS: &[PropertyIdentifier] = &[
125            PropertyIdentifier::OBJECT_IDENTIFIER,
126            PropertyIdentifier::OBJECT_NAME,
127            PropertyIdentifier::DESCRIPTION,
128            PropertyIdentifier::OBJECT_TYPE,
129            PropertyIdentifier::STATUS_FLAGS,
130            PropertyIdentifier::OUT_OF_SERVICE,
131            PropertyIdentifier::RELIABILITY,
132            PropertyIdentifier::PROCESS_IDENTIFIER_FILTER,
133            PropertyIdentifier::SUBSCRIBED_RECIPIENTS,
134            PropertyIdentifier::LOCAL_FORWARDING_ONLY,
135            PropertyIdentifier::EVENT_DETECTION_ENABLE,
136        ];
137        Cow::Borrowed(PROPS)
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn create_notification_forwarder() {
147        let nf = NotificationForwarderObject::new(1, "NF-1").unwrap();
148        assert_eq!(
149            nf.object_identifier().object_type(),
150            ObjectType::NOTIFICATION_FORWARDER
151        );
152        assert_eq!(nf.object_identifier().instance_number(), 1);
153        assert_eq!(nf.object_name(), "NF-1");
154    }
155
156    #[test]
157    fn object_type() {
158        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
159        let val = nf
160            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
161            .unwrap();
162        assert_eq!(
163            val,
164            PropertyValue::Enumerated(ObjectType::NOTIFICATION_FORWARDER.to_raw())
165        );
166    }
167
168    #[test]
169    fn process_identifier_filter_empty() {
170        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
171        let val = nf
172            .read_property(PropertyIdentifier::PROCESS_IDENTIFIER_FILTER, None)
173            .unwrap();
174        if let PropertyValue::List(items) = val {
175            assert!(items.is_empty());
176        } else {
177            panic!("Expected List");
178        }
179    }
180
181    #[test]
182    fn process_identifier_filter_with_values() {
183        let mut nf = NotificationForwarderObject::new(1, "NF").unwrap();
184        nf.process_identifier_filter.push(100);
185        nf.process_identifier_filter.push(200);
186
187        let val = nf
188            .read_property(PropertyIdentifier::PROCESS_IDENTIFIER_FILTER, None)
189            .unwrap();
190        if let PropertyValue::List(items) = val {
191            assert_eq!(items.len(), 2);
192            assert_eq!(items[0], PropertyValue::Unsigned(100));
193            assert_eq!(items[1], PropertyValue::Unsigned(200));
194        } else {
195            panic!("Expected List");
196        }
197    }
198
199    #[test]
200    fn local_forwarding_only_default() {
201        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
202        let val = nf
203            .read_property(PropertyIdentifier::LOCAL_FORWARDING_ONLY, None)
204            .unwrap();
205        assert_eq!(val, PropertyValue::Boolean(false));
206    }
207
208    #[test]
209    fn write_local_forwarding_only() {
210        let mut nf = NotificationForwarderObject::new(1, "NF").unwrap();
211        nf.write_property(
212            PropertyIdentifier::LOCAL_FORWARDING_ONLY,
213            None,
214            PropertyValue::Boolean(true),
215            None,
216        )
217        .unwrap();
218        let val = nf
219            .read_property(PropertyIdentifier::LOCAL_FORWARDING_ONLY, None)
220            .unwrap();
221        assert_eq!(val, PropertyValue::Boolean(true));
222    }
223
224    #[test]
225    fn write_local_forwarding_only_wrong_type() {
226        let mut nf = NotificationForwarderObject::new(1, "NF").unwrap();
227        let result = nf.write_property(
228            PropertyIdentifier::LOCAL_FORWARDING_ONLY,
229            None,
230            PropertyValue::Unsigned(1),
231            None,
232        );
233        assert!(result.is_err());
234    }
235
236    #[test]
237    fn event_detection_enable_default() {
238        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
239        let val = nf
240            .read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
241            .unwrap();
242        assert_eq!(val, PropertyValue::Boolean(true));
243    }
244
245    #[test]
246    fn write_event_detection_enable() {
247        let mut nf = NotificationForwarderObject::new(1, "NF").unwrap();
248        nf.write_property(
249            PropertyIdentifier::EVENT_DETECTION_ENABLE,
250            None,
251            PropertyValue::Boolean(false),
252            None,
253        )
254        .unwrap();
255        let val = nf
256            .read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
257            .unwrap();
258        assert_eq!(val, PropertyValue::Boolean(false));
259    }
260
261    #[test]
262    fn subscribed_recipients_default() {
263        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
264        let val = nf
265            .read_property(PropertyIdentifier::SUBSCRIBED_RECIPIENTS, None)
266            .unwrap();
267        assert_eq!(val, PropertyValue::Unsigned(0));
268    }
269
270    #[test]
271    fn property_list() {
272        let nf = NotificationForwarderObject::new(1, "NF").unwrap();
273        let props = nf.property_list();
274        assert!(props.contains(&PropertyIdentifier::PROCESS_IDENTIFIER_FILTER));
275        assert!(props.contains(&PropertyIdentifier::SUBSCRIBED_RECIPIENTS));
276        assert!(props.contains(&PropertyIdentifier::LOCAL_FORWARDING_ONLY));
277        assert!(props.contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
278    }
279}