Skip to main content

bacnet_objects/
event_enrollment.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 {
410    use super::*;
411
412    #[test]
413    fn create_event_enrollment() {
414        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
415        assert_eq!(
416            ee.object_identifier().object_type(),
417            ObjectType::EVENT_ENROLLMENT
418        );
419        assert_eq!(ee.object_identifier().instance_number(), 1);
420        assert_eq!(ee.object_name(), "EE-1");
421    }
422
423    #[test]
424    fn read_object_type() {
425        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
426        let val = ee
427            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
428            .unwrap();
429        assert_eq!(
430            val,
431            PropertyValue::Enumerated(ObjectType::EVENT_ENROLLMENT.to_raw())
432        );
433    }
434
435    #[test]
436    fn read_event_type() {
437        let ee = EventEnrollmentObject::new(1, "EE-1", 3).unwrap();
438        let val = ee
439            .read_property(PropertyIdentifier::EVENT_TYPE, None)
440            .unwrap();
441        assert_eq!(val, PropertyValue::Enumerated(3));
442    }
443
444    #[test]
445    fn read_event_enable() {
446        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
447        let val = ee
448            .read_property(PropertyIdentifier::EVENT_ENABLE, None)
449            .unwrap();
450        // Default event_enable = 0b111, shifted left 5 = 0b1110_0000
451        assert_eq!(
452            val,
453            PropertyValue::BitString {
454                unused_bits: 5,
455                data: vec![0b1110_0000],
456            }
457        );
458    }
459
460    #[test]
461    fn read_notification_class() {
462        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
463        let val = ee
464            .read_property(PropertyIdentifier::NOTIFICATION_CLASS, None)
465            .unwrap();
466        assert_eq!(val, PropertyValue::Unsigned(0));
467    }
468
469    #[test]
470    fn write_notify_type() {
471        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
472        ee.write_property(
473            PropertyIdentifier::NOTIFY_TYPE,
474            None,
475            PropertyValue::Enumerated(1),
476            None,
477        )
478        .unwrap();
479        let val = ee
480            .read_property(PropertyIdentifier::NOTIFY_TYPE, None)
481            .unwrap();
482        assert_eq!(val, PropertyValue::Enumerated(1));
483    }
484
485    #[test]
486    fn write_notify_type_wrong_type() {
487        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
488        let result = ee.write_property(
489            PropertyIdentifier::NOTIFY_TYPE,
490            None,
491            PropertyValue::Real(1.0),
492            None,
493        );
494        assert!(result.is_err());
495    }
496
497    #[test]
498    fn read_acked_transitions() {
499        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
500        let val = ee
501            .read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
502            .unwrap();
503        // Default acked_transitions = 0b111, shifted left 5 = 0b1110_0000
504        assert_eq!(
505            val,
506            PropertyValue::BitString {
507                unused_bits: 5,
508                data: vec![0b1110_0000],
509            }
510        );
511    }
512
513    #[test]
514    fn read_object_property_reference_none() {
515        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
516        let val = ee
517            .read_property(PropertyIdentifier::OBJECT_PROPERTY_REFERENCE, None)
518            .unwrap();
519        assert_eq!(val, PropertyValue::Null);
520    }
521
522    #[test]
523    fn read_object_property_reference_some() {
524        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
525        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 5).unwrap();
526        ee.set_object_property_reference(Some(BACnetDeviceObjectPropertyReference {
527            object_identifier: ai_oid,
528            property_identifier: PropertyIdentifier::PRESENT_VALUE.to_raw(),
529            property_array_index: None,
530            device_identifier: None,
531        }));
532        let val = ee
533            .read_property(PropertyIdentifier::OBJECT_PROPERTY_REFERENCE, None)
534            .unwrap();
535        if let PropertyValue::List(fields) = val {
536            assert_eq!(fields.len(), 4);
537            assert_eq!(fields[0], PropertyValue::ObjectIdentifier(ai_oid));
538            assert_eq!(
539                fields[1],
540                PropertyValue::Unsigned(PropertyIdentifier::PRESENT_VALUE.to_raw() as u64)
541            );
542            assert_eq!(fields[2], PropertyValue::Null); // no array index
543            assert_eq!(fields[3], PropertyValue::Null); // no device
544        } else {
545            panic!("Expected List");
546        }
547    }
548
549    #[test]
550    fn write_notification_class() {
551        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
552        ee.write_property(
553            PropertyIdentifier::NOTIFICATION_CLASS,
554            None,
555            PropertyValue::Unsigned(42),
556            None,
557        )
558        .unwrap();
559        let val = ee
560            .read_property(PropertyIdentifier::NOTIFICATION_CLASS, None)
561            .unwrap();
562        assert_eq!(val, PropertyValue::Unsigned(42));
563    }
564
565    #[test]
566    fn write_event_enable() {
567        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
568        // Write only TO_OFFNORMAL enabled (bit 0 = 0b100 = 0x80 when shifted)
569        ee.write_property(
570            PropertyIdentifier::EVENT_ENABLE,
571            None,
572            PropertyValue::BitString {
573                unused_bits: 5,
574                data: vec![0b1000_0000],
575            },
576            None,
577        )
578        .unwrap();
579        let val = ee
580            .read_property(PropertyIdentifier::EVENT_ENABLE, None)
581            .unwrap();
582        assert_eq!(
583            val,
584            PropertyValue::BitString {
585                unused_bits: 5,
586                data: vec![0b1000_0000],
587            }
588        );
589    }
590
591    #[test]
592    fn property_list_complete() {
593        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
594        let props = ee.property_list();
595        assert!(props.contains(&PropertyIdentifier::EVENT_TYPE));
596        assert!(props.contains(&PropertyIdentifier::NOTIFY_TYPE));
597        assert!(props.contains(&PropertyIdentifier::EVENT_PARAMETERS));
598        assert!(props.contains(&PropertyIdentifier::OBJECT_PROPERTY_REFERENCE));
599        assert!(props.contains(&PropertyIdentifier::EVENT_STATE));
600        assert!(props.contains(&PropertyIdentifier::EVENT_ENABLE));
601        assert!(props.contains(&PropertyIdentifier::ACKED_TRANSITIONS));
602        assert!(props.contains(&PropertyIdentifier::NOTIFICATION_CLASS));
603    }
604
605    #[test]
606    fn write_event_parameters() {
607        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
608        let params = vec![0x01, 0x02, 0x03];
609        ee.write_property(
610            PropertyIdentifier::EVENT_PARAMETERS,
611            None,
612            PropertyValue::OctetString(params.clone()),
613            None,
614        )
615        .unwrap();
616        let val = ee
617            .read_property(PropertyIdentifier::EVENT_PARAMETERS, None)
618            .unwrap();
619        assert_eq!(val, PropertyValue::OctetString(params));
620    }
621
622    #[test]
623    fn read_event_state_default() {
624        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
625        let val = ee
626            .read_property(PropertyIdentifier::EVENT_STATE, None)
627            .unwrap();
628        assert_eq!(val, PropertyValue::Enumerated(0)); // normal
629    }
630
631    #[test]
632    fn write_unknown_property_denied() {
633        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
634        let result = ee.write_property(
635            PropertyIdentifier::PRESENT_VALUE,
636            None,
637            PropertyValue::Real(1.0),
638            None,
639        );
640        assert!(result.is_err());
641    }
642
643    // -----------------------------------------------------------------------
644    // FaultParameters tests
645    // -----------------------------------------------------------------------
646
647    #[test]
648    fn fault_parameters_default_none() {
649        let ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
650        let val = ee
651            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
652            .unwrap();
653        assert_eq!(val, PropertyValue::Null);
654    }
655
656    #[test]
657    fn fault_parameters_none_variant() {
658        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
659        ee.set_fault_parameters(Some(FaultParameters::FaultNone));
660        let val = ee
661            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
662            .unwrap();
663        assert_eq!(val, PropertyValue::Unsigned(0));
664    }
665
666    #[test]
667    fn fault_parameters_character_string() {
668        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
669        ee.set_fault_parameters(Some(FaultParameters::FaultCharacterString {
670            fault_values: vec!["alarm".to_string(), "critical".to_string()],
671        }));
672        let val = ee
673            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
674            .unwrap();
675        assert_eq!(val, PropertyValue::Unsigned(1));
676    }
677
678    #[test]
679    fn fault_parameters_extended() {
680        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
681        ee.set_fault_parameters(Some(FaultParameters::FaultExtended {
682            vendor_id: 42,
683            extended_fault_type: 7,
684            parameters: vec![0x01, 0x02],
685        }));
686        let val = ee
687            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
688            .unwrap();
689        assert_eq!(val, PropertyValue::Unsigned(2));
690    }
691
692    #[test]
693    fn fault_parameters_life_safety() {
694        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
695        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
696        ee.set_fault_parameters(Some(FaultParameters::FaultLifeSafety {
697            fault_values: vec![1, 2, 3],
698            mode_for_reference: BACnetDeviceObjectPropertyReference {
699                object_identifier: ai_oid,
700                property_identifier: PropertyIdentifier::PRESENT_VALUE.to_raw(),
701                property_array_index: None,
702                device_identifier: None,
703            },
704        }));
705        let val = ee
706            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
707            .unwrap();
708        assert_eq!(val, PropertyValue::Unsigned(3));
709    }
710
711    #[test]
712    fn fault_parameters_state() {
713        use bacnet_types::constructed::BACnetPropertyStates;
714        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
715        ee.set_fault_parameters(Some(FaultParameters::FaultState {
716            fault_values: vec![BACnetPropertyStates::BooleanValue(true)],
717        }));
718        let val = ee
719            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
720            .unwrap();
721        assert_eq!(val, PropertyValue::Unsigned(4));
722    }
723
724    #[test]
725    fn fault_parameters_status_flags() {
726        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
727        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
728        ee.set_fault_parameters(Some(FaultParameters::FaultStatusFlags {
729            reference: BACnetDeviceObjectPropertyReference {
730                object_identifier: ai_oid,
731                property_identifier: PropertyIdentifier::STATUS_FLAGS.to_raw(),
732                property_array_index: None,
733                device_identifier: None,
734            },
735        }));
736        let val = ee
737            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
738            .unwrap();
739        assert_eq!(val, PropertyValue::Unsigned(5));
740    }
741
742    #[test]
743    fn fault_parameters_out_of_range() {
744        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
745        ee.set_fault_parameters(Some(FaultParameters::FaultOutOfRange {
746            min_normal: 0.0,
747            max_normal: 100.0,
748        }));
749        let val = ee
750            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
751            .unwrap();
752        assert_eq!(val, PropertyValue::Unsigned(6));
753    }
754
755    #[test]
756    fn fault_parameters_listed() {
757        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
758        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
759        ee.set_fault_parameters(Some(FaultParameters::FaultListed {
760            reference: BACnetDeviceObjectPropertyReference {
761                object_identifier: ai_oid,
762                property_identifier: PropertyIdentifier::PRESENT_VALUE.to_raw(),
763                property_array_index: None,
764                device_identifier: None,
765            },
766        }));
767        let val = ee
768            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
769            .unwrap();
770        assert_eq!(val, PropertyValue::Unsigned(7));
771    }
772
773    #[test]
774    fn fault_parameters_in_property_list() {
775        let ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
776        let props = ee.property_list();
777        assert!(props.contains(&PropertyIdentifier::FAULT_PARAMETERS));
778    }
779
780    #[test]
781    fn fault_parameters_clear() {
782        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
783        ee.set_fault_parameters(Some(FaultParameters::FaultNone));
784        let val = ee
785            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
786            .unwrap();
787        assert_eq!(val, PropertyValue::Unsigned(0));
788
789        // Clear back to None
790        ee.set_fault_parameters(None);
791        let val = ee
792            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
793            .unwrap();
794        assert_eq!(val, PropertyValue::Null);
795    }
796
797    // -----------------------------------------------------------------------
798    // AlertEnrollmentObject tests
799    // -----------------------------------------------------------------------
800
801    #[test]
802    fn alert_enrollment_create() {
803        let ae = AlertEnrollmentObject::new(1, "AE-1").unwrap();
804        assert_eq!(
805            ae.object_identifier().object_type(),
806            ObjectType::ALERT_ENROLLMENT
807        );
808        assert_eq!(ae.object_identifier().instance_number(), 1);
809        assert_eq!(ae.object_name(), "AE-1");
810    }
811
812    #[test]
813    fn alert_enrollment_object_type() {
814        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
815        let val = ae
816            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
817            .unwrap();
818        assert_eq!(
819            val,
820            PropertyValue::Enumerated(ObjectType::ALERT_ENROLLMENT.to_raw())
821        );
822    }
823
824    #[test]
825    fn alert_enrollment_present_value_default() {
826        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
827        let val = ae
828            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
829            .unwrap();
830        assert_eq!(val, PropertyValue::Enumerated(0));
831    }
832
833    #[test]
834    fn alert_enrollment_event_detection_enable() {
835        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
836        let val = ae
837            .read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
838            .unwrap();
839        assert_eq!(val, PropertyValue::Boolean(true));
840    }
841
842    #[test]
843    fn alert_enrollment_write_event_detection_enable() {
844        let mut ae = AlertEnrollmentObject::new(1, "AE").unwrap();
845        ae.write_property(
846            PropertyIdentifier::EVENT_DETECTION_ENABLE,
847            None,
848            PropertyValue::Boolean(false),
849            None,
850        )
851        .unwrap();
852        let val = ae
853            .read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
854            .unwrap();
855        assert_eq!(val, PropertyValue::Boolean(false));
856    }
857
858    #[test]
859    fn alert_enrollment_event_enable() {
860        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
861        let val = ae
862            .read_property(PropertyIdentifier::EVENT_ENABLE, None)
863            .unwrap();
864        // Default event_enable = 0b111, shifted left 5 = 0b1110_0000
865        assert_eq!(
866            val,
867            PropertyValue::BitString {
868                unused_bits: 5,
869                data: vec![0b1110_0000],
870            }
871        );
872    }
873
874    #[test]
875    fn alert_enrollment_write_event_enable() {
876        let mut ae = AlertEnrollmentObject::new(1, "AE").unwrap();
877        ae.write_property(
878            PropertyIdentifier::EVENT_ENABLE,
879            None,
880            PropertyValue::BitString {
881                unused_bits: 5,
882                data: vec![0b1000_0000],
883            },
884            None,
885        )
886        .unwrap();
887        let val = ae
888            .read_property(PropertyIdentifier::EVENT_ENABLE, None)
889            .unwrap();
890        assert_eq!(
891            val,
892            PropertyValue::BitString {
893                unused_bits: 5,
894                data: vec![0b1000_0000],
895            }
896        );
897    }
898
899    #[test]
900    fn alert_enrollment_notification_class() {
901        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
902        let val = ae
903            .read_property(PropertyIdentifier::NOTIFICATION_CLASS, None)
904            .unwrap();
905        assert_eq!(val, PropertyValue::Unsigned(0));
906    }
907
908    #[test]
909    fn alert_enrollment_write_notification_class() {
910        let mut ae = AlertEnrollmentObject::new(1, "AE").unwrap();
911        ae.write_property(
912            PropertyIdentifier::NOTIFICATION_CLASS,
913            None,
914            PropertyValue::Unsigned(42),
915            None,
916        )
917        .unwrap();
918        let val = ae
919            .read_property(PropertyIdentifier::NOTIFICATION_CLASS, None)
920            .unwrap();
921        assert_eq!(val, PropertyValue::Unsigned(42));
922    }
923
924    #[test]
925    fn alert_enrollment_property_list() {
926        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
927        let props = ae.property_list();
928        assert!(props.contains(&PropertyIdentifier::PRESENT_VALUE));
929        assert!(props.contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
930        assert!(props.contains(&PropertyIdentifier::EVENT_ENABLE));
931        assert!(props.contains(&PropertyIdentifier::NOTIFICATION_CLASS));
932        assert!(props.contains(&PropertyIdentifier::STATUS_FLAGS));
933    }
934}