Skip to main content

bacnet_objects/event_enrollment/
mod.rs

1//! EventEnrollment (type 9) object per ASHRAE 135-2020 Clause 12.12.
2
3use bacnet_types::constructed::{BACnetDeviceObjectPropertyReference, FaultParameters};
4use bacnet_types::enums::{ObjectType, PropertyIdentifier};
5use bacnet_types::error::Error;
6use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
7use std::borrow::Cow;
8
9use crate::common::{self, read_common_properties};
10use crate::traits::BACnetObject;
11
12/// BACnet EventEnrollment object.
13///
14/// Provides algorithmic event detection for a referenced object property.
15/// The event_parameters are stored as raw bytes; full structured decoding
16/// is deferred to a future enhancement.
17pub struct EventEnrollmentObject {
18    oid: ObjectIdentifier,
19    name: String,
20    description: String,
21    event_type: u32,
22    notify_type: u32,
23    event_parameters: Vec<u8>,
24    object_property_reference: Option<BACnetDeviceObjectPropertyReference>,
25    event_state: u32,
26    event_enable: u8,
27    acked_transitions: u8,
28    notification_class: u32,
29    fault_parameters: Option<FaultParameters>,
30    status_flags: StatusFlags,
31    out_of_service: bool,
32    reliability: u32,
33}
34
35impl EventEnrollmentObject {
36    /// Create a new EventEnrollment object.
37    ///
38    /// `event_type` is the BACnet EventType enumeration value.
39    pub fn new(instance: u32, name: impl Into<String>, event_type: u32) -> Result<Self, Error> {
40        let oid = ObjectIdentifier::new(ObjectType::EVENT_ENROLLMENT, instance)?;
41        Ok(Self {
42            oid,
43            name: name.into(),
44            description: String::new(),
45            event_type,
46            notify_type: 0,
47            event_parameters: Vec::new(),
48            object_property_reference: None,
49            event_state: 0,
50            event_enable: 0b111,
51            acked_transitions: 0b111,
52            notification_class: 0,
53            fault_parameters: None,
54            status_flags: StatusFlags::empty(),
55            out_of_service: false,
56            reliability: 0,
57        })
58    }
59
60    /// Set the description string.
61    pub fn set_description(&mut self, desc: impl Into<String>) {
62        self.description = desc.into();
63    }
64
65    /// Set the object property reference.
66    pub fn set_object_property_reference(
67        &mut self,
68        reference: Option<BACnetDeviceObjectPropertyReference>,
69    ) {
70        self.object_property_reference = reference;
71    }
72
73    /// Set the event parameters (raw bytes).
74    pub fn set_event_parameters(&mut self, params: Vec<u8>) {
75        self.event_parameters = params;
76    }
77
78    /// Set the fault parameters for this event enrollment.
79    pub fn set_fault_parameters(&mut self, fp: Option<FaultParameters>) {
80        self.fault_parameters = fp;
81    }
82
83    /// Set the event state (raw u32).
84    pub fn set_event_state(&mut self, state: u32) {
85        self.event_state = state;
86    }
87
88    /// Set the notification class.
89    pub fn set_notification_class(&mut self, nc: u32) {
90        self.notification_class = nc;
91    }
92
93    /// Set the event enable bitmask (3 bits: TO_OFFNORMAL, TO_FAULT, TO_NORMAL).
94    pub fn set_event_enable(&mut self, enable: u8) {
95        self.event_enable = enable & 0x07;
96    }
97}
98
99impl BACnetObject for EventEnrollmentObject {
100    fn object_identifier(&self) -> ObjectIdentifier {
101        self.oid
102    }
103
104    fn object_name(&self) -> &str {
105        &self.name
106    }
107
108    fn read_property(
109        &self,
110        property: PropertyIdentifier,
111        array_index: Option<u32>,
112    ) -> Result<PropertyValue, Error> {
113        if let Some(result) = read_common_properties!(self, property, array_index) {
114            return result;
115        }
116        match property {
117            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
118                ObjectType::EVENT_ENROLLMENT.to_raw(),
119            )),
120            p if p == PropertyIdentifier::EVENT_TYPE => {
121                Ok(PropertyValue::Enumerated(self.event_type))
122            }
123            p if p == PropertyIdentifier::NOTIFY_TYPE => {
124                Ok(PropertyValue::Enumerated(self.notify_type))
125            }
126            p if p == PropertyIdentifier::EVENT_PARAMETERS => {
127                Ok(PropertyValue::OctetString(self.event_parameters.clone()))
128            }
129            p if p == PropertyIdentifier::OBJECT_PROPERTY_REFERENCE => {
130                match &self.object_property_reference {
131                    None => Ok(PropertyValue::Null),
132                    Some(r) => Ok(PropertyValue::List(vec![
133                        PropertyValue::ObjectIdentifier(r.object_identifier),
134                        PropertyValue::Unsigned(r.property_identifier as u64),
135                        match r.property_array_index {
136                            Some(idx) => PropertyValue::Unsigned(idx as u64),
137                            None => PropertyValue::Null,
138                        },
139                        match r.device_identifier {
140                            Some(dev) => PropertyValue::ObjectIdentifier(dev),
141                            None => PropertyValue::Null,
142                        },
143                    ])),
144                }
145            }
146            p if p == PropertyIdentifier::EVENT_STATE => {
147                Ok(PropertyValue::Enumerated(self.event_state))
148            }
149            p if p == PropertyIdentifier::EVENT_ENABLE => Ok(PropertyValue::BitString {
150                unused_bits: 5,
151                data: vec![self.event_enable << 5],
152            }),
153            p if p == PropertyIdentifier::ACKED_TRANSITIONS => Ok(PropertyValue::BitString {
154                unused_bits: 5,
155                data: vec![self.acked_transitions << 5],
156            }),
157            p if p == PropertyIdentifier::NOTIFICATION_CLASS => {
158                Ok(PropertyValue::Unsigned(self.notification_class as u64))
159            }
160            p if p == PropertyIdentifier::FAULT_PARAMETERS => match &self.fault_parameters {
161                None => Ok(PropertyValue::Null),
162                Some(fp) => {
163                    let variant_tag = match fp {
164                        FaultParameters::FaultNone => 0u64,
165                        FaultParameters::FaultCharacterString { .. } => 1,
166                        FaultParameters::FaultExtended { .. } => 2,
167                        FaultParameters::FaultLifeSafety { .. } => 3,
168                        FaultParameters::FaultState { .. } => 4,
169                        FaultParameters::FaultStatusFlags { .. } => 5,
170                        FaultParameters::FaultOutOfRange { .. } => 6,
171                        FaultParameters::FaultListed { .. } => 7,
172                    };
173                    Ok(PropertyValue::Unsigned(variant_tag))
174                }
175            },
176            _ => Err(common::unknown_property_error()),
177        }
178    }
179
180    fn write_property(
181        &mut self,
182        property: PropertyIdentifier,
183        _array_index: Option<u32>,
184        value: PropertyValue,
185        _priority: Option<u8>,
186    ) -> Result<(), Error> {
187        if property == PropertyIdentifier::NOTIFY_TYPE {
188            if let PropertyValue::Enumerated(v) = value {
189                self.notify_type = v;
190                return Ok(());
191            }
192            return Err(common::invalid_data_type_error());
193        }
194        if property == PropertyIdentifier::NOTIFICATION_CLASS {
195            if let PropertyValue::Unsigned(v) = value {
196                self.notification_class = common::u64_to_u32(v)?;
197                return Ok(());
198            }
199            return Err(common::invalid_data_type_error());
200        }
201        if property == PropertyIdentifier::EVENT_ENABLE {
202            if let PropertyValue::BitString { data, .. } = &value {
203                if let Some(&byte) = data.first() {
204                    self.event_enable = byte >> 5;
205                    return Ok(());
206                }
207                return Err(common::invalid_data_type_error());
208            }
209            return Err(common::invalid_data_type_error());
210        }
211        if property == PropertyIdentifier::EVENT_STATE {
212            if let PropertyValue::Enumerated(v) = value {
213                self.event_state = v;
214                return Ok(());
215            }
216            return Err(common::invalid_data_type_error());
217        }
218        if property == PropertyIdentifier::EVENT_PARAMETERS {
219            if let PropertyValue::OctetString(bytes) = value {
220                self.event_parameters = bytes;
221                return Ok(());
222            }
223            return Err(common::invalid_data_type_error());
224        }
225        if let Some(result) =
226            common::write_out_of_service(&mut self.out_of_service, property, &value)
227        {
228            return result;
229        }
230        if let Some(result) = common::write_description(&mut self.description, property, &value) {
231            return result;
232        }
233        Err(common::write_access_denied_error())
234    }
235
236    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
237        static PROPS: &[PropertyIdentifier] = &[
238            PropertyIdentifier::OBJECT_IDENTIFIER,
239            PropertyIdentifier::OBJECT_NAME,
240            PropertyIdentifier::DESCRIPTION,
241            PropertyIdentifier::OBJECT_TYPE,
242            PropertyIdentifier::EVENT_TYPE,
243            PropertyIdentifier::NOTIFY_TYPE,
244            PropertyIdentifier::EVENT_PARAMETERS,
245            PropertyIdentifier::OBJECT_PROPERTY_REFERENCE,
246            PropertyIdentifier::EVENT_STATE,
247            PropertyIdentifier::EVENT_ENABLE,
248            PropertyIdentifier::ACKED_TRANSITIONS,
249            PropertyIdentifier::NOTIFICATION_CLASS,
250            PropertyIdentifier::FAULT_PARAMETERS,
251            PropertyIdentifier::STATUS_FLAGS,
252            PropertyIdentifier::OUT_OF_SERVICE,
253            PropertyIdentifier::RELIABILITY,
254        ];
255        Cow::Borrowed(PROPS)
256    }
257}
258
259// ---------------------------------------------------------------------------
260// AlertEnrollmentObject (type 52)
261// ---------------------------------------------------------------------------
262
263/// BACnet AlertEnrollment object (type 52).
264///
265/// Provides alert-based event enrollment. The PRESENT_VALUE is an enumerated
266/// AlertState. Supports EVENT_DETECTION_ENABLE, EVENT_ENABLE (3-bit),
267/// and NOTIFICATION_CLASS.
268pub struct AlertEnrollmentObject {
269    oid: ObjectIdentifier,
270    name: String,
271    description: String,
272    status_flags: StatusFlags,
273    /// Event_State: 0 = NORMAL.
274    event_state: u32,
275    out_of_service: bool,
276    reliability: u32,
277    /// Present value — AlertState enumeration.
278    pub present_value: u32,
279    /// Whether event detection is enabled.
280    pub event_detection_enable: bool,
281    /// Event enable bits: 3-bit (TO_OFFNORMAL, TO_FAULT, TO_NORMAL).
282    pub event_enable: u8,
283    /// Notification class number.
284    pub notification_class: u32,
285}
286
287impl AlertEnrollmentObject {
288    /// Create a new AlertEnrollment object.
289    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
290        let oid = ObjectIdentifier::new(ObjectType::ALERT_ENROLLMENT, instance)?;
291        Ok(Self {
292            oid,
293            name: name.into(),
294            description: String::new(),
295            status_flags: StatusFlags::empty(),
296            event_state: 0, // NORMAL
297            out_of_service: false,
298            reliability: 0,
299            present_value: 0,
300            event_detection_enable: true,
301            event_enable: 0b111,
302            notification_class: 0,
303        })
304    }
305}
306
307impl BACnetObject for AlertEnrollmentObject {
308    fn object_identifier(&self) -> ObjectIdentifier {
309        self.oid
310    }
311
312    fn object_name(&self) -> &str {
313        &self.name
314    }
315
316    fn read_property(
317        &self,
318        property: PropertyIdentifier,
319        array_index: Option<u32>,
320    ) -> Result<PropertyValue, Error> {
321        if let Some(result) = read_common_properties!(self, property, array_index) {
322            return result;
323        }
324        match property {
325            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
326                ObjectType::ALERT_ENROLLMENT.to_raw(),
327            )),
328            p if p == PropertyIdentifier::PRESENT_VALUE => {
329                Ok(PropertyValue::Enumerated(self.present_value))
330            }
331            p if p == PropertyIdentifier::EVENT_DETECTION_ENABLE => {
332                Ok(PropertyValue::Boolean(self.event_detection_enable))
333            }
334            p if p == PropertyIdentifier::EVENT_ENABLE => Ok(PropertyValue::BitString {
335                unused_bits: 5,
336                data: vec![self.event_enable << 5],
337            }),
338            p if p == PropertyIdentifier::NOTIFICATION_CLASS => {
339                Ok(PropertyValue::Unsigned(self.notification_class as u64))
340            }
341            p if p == PropertyIdentifier::EVENT_STATE => {
342                Ok(PropertyValue::Enumerated(self.event_state))
343            }
344            _ => Err(common::unknown_property_error()),
345        }
346    }
347
348    fn write_property(
349        &mut self,
350        property: PropertyIdentifier,
351        _array_index: Option<u32>,
352        value: PropertyValue,
353        _priority: Option<u8>,
354    ) -> Result<(), Error> {
355        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
356            if let PropertyValue::Boolean(v) = value {
357                self.event_detection_enable = v;
358                return Ok(());
359            }
360            return Err(common::invalid_data_type_error());
361        }
362        if property == PropertyIdentifier::EVENT_ENABLE {
363            if let PropertyValue::BitString { data, .. } = &value {
364                if let Some(&byte) = data.first() {
365                    self.event_enable = byte >> 5;
366                    return Ok(());
367                }
368                return Err(common::invalid_data_type_error());
369            }
370            return Err(common::invalid_data_type_error());
371        }
372        if property == PropertyIdentifier::NOTIFICATION_CLASS {
373            if let PropertyValue::Unsigned(v) = value {
374                self.notification_class = common::u64_to_u32(v)?;
375                return Ok(());
376            }
377            return Err(common::invalid_data_type_error());
378        }
379        if let Some(result) =
380            common::write_out_of_service(&mut self.out_of_service, property, &value)
381        {
382            return result;
383        }
384        if let Some(result) = common::write_description(&mut self.description, property, &value) {
385            return result;
386        }
387        Err(common::write_access_denied_error())
388    }
389
390    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
391        static PROPS: &[PropertyIdentifier] = &[
392            PropertyIdentifier::OBJECT_IDENTIFIER,
393            PropertyIdentifier::OBJECT_NAME,
394            PropertyIdentifier::DESCRIPTION,
395            PropertyIdentifier::OBJECT_TYPE,
396            PropertyIdentifier::PRESENT_VALUE,
397            PropertyIdentifier::EVENT_DETECTION_ENABLE,
398            PropertyIdentifier::EVENT_ENABLE,
399            PropertyIdentifier::NOTIFICATION_CLASS,
400            PropertyIdentifier::STATUS_FLAGS,
401            PropertyIdentifier::OUT_OF_SERVICE,
402            PropertyIdentifier::RELIABILITY,
403        ];
404        Cow::Borrowed(PROPS)
405    }
406}
407
408#[cfg(test)]
409mod tests;