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    out_of_service: bool,
274    reliability: u32,
275    /// Present value — AlertState enumeration.
276    pub present_value: u32,
277    /// Whether event detection is enabled.
278    pub event_detection_enable: bool,
279    /// Event enable bits: 3-bit (TO_OFFNORMAL, TO_FAULT, TO_NORMAL).
280    pub event_enable: u8,
281    /// Notification class number.
282    pub notification_class: u32,
283}
284
285impl AlertEnrollmentObject {
286    /// Create a new AlertEnrollment object.
287    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
288        let oid = ObjectIdentifier::new(ObjectType::ALERT_ENROLLMENT, instance)?;
289        Ok(Self {
290            oid,
291            name: name.into(),
292            description: String::new(),
293            status_flags: StatusFlags::empty(),
294            out_of_service: false,
295            reliability: 0,
296            present_value: 0,
297            event_detection_enable: true,
298            event_enable: 0b111,
299            notification_class: 0,
300        })
301    }
302}
303
304impl BACnetObject for AlertEnrollmentObject {
305    fn object_identifier(&self) -> ObjectIdentifier {
306        self.oid
307    }
308
309    fn object_name(&self) -> &str {
310        &self.name
311    }
312
313    fn read_property(
314        &self,
315        property: PropertyIdentifier,
316        array_index: Option<u32>,
317    ) -> Result<PropertyValue, Error> {
318        if let Some(result) = read_common_properties!(self, property, array_index) {
319            return result;
320        }
321        match property {
322            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
323                ObjectType::ALERT_ENROLLMENT.to_raw(),
324            )),
325            p if p == PropertyIdentifier::PRESENT_VALUE => {
326                Ok(PropertyValue::Enumerated(self.present_value))
327            }
328            p if p == PropertyIdentifier::EVENT_DETECTION_ENABLE => {
329                Ok(PropertyValue::Boolean(self.event_detection_enable))
330            }
331            p if p == PropertyIdentifier::EVENT_ENABLE => Ok(PropertyValue::BitString {
332                unused_bits: 5,
333                data: vec![self.event_enable << 5],
334            }),
335            p if p == PropertyIdentifier::NOTIFICATION_CLASS => {
336                Ok(PropertyValue::Unsigned(self.notification_class as u64))
337            }
338            _ => Err(common::unknown_property_error()),
339        }
340    }
341
342    fn write_property(
343        &mut self,
344        property: PropertyIdentifier,
345        _array_index: Option<u32>,
346        value: PropertyValue,
347        _priority: Option<u8>,
348    ) -> Result<(), Error> {
349        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
350            if let PropertyValue::Boolean(v) = value {
351                self.event_detection_enable = v;
352                return Ok(());
353            }
354            return Err(common::invalid_data_type_error());
355        }
356        if property == PropertyIdentifier::EVENT_ENABLE {
357            if let PropertyValue::BitString { data, .. } = &value {
358                if let Some(&byte) = data.first() {
359                    self.event_enable = byte >> 5;
360                    return Ok(());
361                }
362                return Err(common::invalid_data_type_error());
363            }
364            return Err(common::invalid_data_type_error());
365        }
366        if property == PropertyIdentifier::NOTIFICATION_CLASS {
367            if let PropertyValue::Unsigned(v) = value {
368                self.notification_class = common::u64_to_u32(v)?;
369                return Ok(());
370            }
371            return Err(common::invalid_data_type_error());
372        }
373        if let Some(result) =
374            common::write_out_of_service(&mut self.out_of_service, property, &value)
375        {
376            return result;
377        }
378        if let Some(result) = common::write_description(&mut self.description, property, &value) {
379            return result;
380        }
381        Err(common::write_access_denied_error())
382    }
383
384    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
385        static PROPS: &[PropertyIdentifier] = &[
386            PropertyIdentifier::OBJECT_IDENTIFIER,
387            PropertyIdentifier::OBJECT_NAME,
388            PropertyIdentifier::DESCRIPTION,
389            PropertyIdentifier::OBJECT_TYPE,
390            PropertyIdentifier::PRESENT_VALUE,
391            PropertyIdentifier::EVENT_DETECTION_ENABLE,
392            PropertyIdentifier::EVENT_ENABLE,
393            PropertyIdentifier::NOTIFICATION_CLASS,
394            PropertyIdentifier::STATUS_FLAGS,
395            PropertyIdentifier::OUT_OF_SERVICE,
396            PropertyIdentifier::RELIABILITY,
397        ];
398        Cow::Borrowed(PROPS)
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405
406    #[test]
407    fn create_event_enrollment() {
408        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
409        assert_eq!(
410            ee.object_identifier().object_type(),
411            ObjectType::EVENT_ENROLLMENT
412        );
413        assert_eq!(ee.object_identifier().instance_number(), 1);
414        assert_eq!(ee.object_name(), "EE-1");
415    }
416
417    #[test]
418    fn read_object_type() {
419        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
420        let val = ee
421            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
422            .unwrap();
423        assert_eq!(
424            val,
425            PropertyValue::Enumerated(ObjectType::EVENT_ENROLLMENT.to_raw())
426        );
427    }
428
429    #[test]
430    fn read_event_type() {
431        let ee = EventEnrollmentObject::new(1, "EE-1", 3).unwrap();
432        let val = ee
433            .read_property(PropertyIdentifier::EVENT_TYPE, None)
434            .unwrap();
435        assert_eq!(val, PropertyValue::Enumerated(3));
436    }
437
438    #[test]
439    fn read_event_enable() {
440        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
441        let val = ee
442            .read_property(PropertyIdentifier::EVENT_ENABLE, None)
443            .unwrap();
444        // Default event_enable = 0b111, shifted left 5 = 0b1110_0000
445        assert_eq!(
446            val,
447            PropertyValue::BitString {
448                unused_bits: 5,
449                data: vec![0b1110_0000],
450            }
451        );
452    }
453
454    #[test]
455    fn read_notification_class() {
456        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
457        let val = ee
458            .read_property(PropertyIdentifier::NOTIFICATION_CLASS, None)
459            .unwrap();
460        assert_eq!(val, PropertyValue::Unsigned(0));
461    }
462
463    #[test]
464    fn write_notify_type() {
465        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
466        ee.write_property(
467            PropertyIdentifier::NOTIFY_TYPE,
468            None,
469            PropertyValue::Enumerated(1),
470            None,
471        )
472        .unwrap();
473        let val = ee
474            .read_property(PropertyIdentifier::NOTIFY_TYPE, None)
475            .unwrap();
476        assert_eq!(val, PropertyValue::Enumerated(1));
477    }
478
479    #[test]
480    fn write_notify_type_wrong_type() {
481        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
482        let result = ee.write_property(
483            PropertyIdentifier::NOTIFY_TYPE,
484            None,
485            PropertyValue::Real(1.0),
486            None,
487        );
488        assert!(result.is_err());
489    }
490
491    #[test]
492    fn read_acked_transitions() {
493        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
494        let val = ee
495            .read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
496            .unwrap();
497        // Default acked_transitions = 0b111, shifted left 5 = 0b1110_0000
498        assert_eq!(
499            val,
500            PropertyValue::BitString {
501                unused_bits: 5,
502                data: vec![0b1110_0000],
503            }
504        );
505    }
506
507    #[test]
508    fn read_object_property_reference_none() {
509        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
510        let val = ee
511            .read_property(PropertyIdentifier::OBJECT_PROPERTY_REFERENCE, None)
512            .unwrap();
513        assert_eq!(val, PropertyValue::Null);
514    }
515
516    #[test]
517    fn read_object_property_reference_some() {
518        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
519        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 5).unwrap();
520        ee.set_object_property_reference(Some(BACnetDeviceObjectPropertyReference {
521            object_identifier: ai_oid,
522            property_identifier: PropertyIdentifier::PRESENT_VALUE.to_raw(),
523            property_array_index: None,
524            device_identifier: None,
525        }));
526        let val = ee
527            .read_property(PropertyIdentifier::OBJECT_PROPERTY_REFERENCE, None)
528            .unwrap();
529        if let PropertyValue::List(fields) = val {
530            assert_eq!(fields.len(), 4);
531            assert_eq!(fields[0], PropertyValue::ObjectIdentifier(ai_oid));
532            assert_eq!(
533                fields[1],
534                PropertyValue::Unsigned(PropertyIdentifier::PRESENT_VALUE.to_raw() as u64)
535            );
536            assert_eq!(fields[2], PropertyValue::Null); // no array index
537            assert_eq!(fields[3], PropertyValue::Null); // no device
538        } else {
539            panic!("Expected List");
540        }
541    }
542
543    #[test]
544    fn write_notification_class() {
545        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
546        ee.write_property(
547            PropertyIdentifier::NOTIFICATION_CLASS,
548            None,
549            PropertyValue::Unsigned(42),
550            None,
551        )
552        .unwrap();
553        let val = ee
554            .read_property(PropertyIdentifier::NOTIFICATION_CLASS, None)
555            .unwrap();
556        assert_eq!(val, PropertyValue::Unsigned(42));
557    }
558
559    #[test]
560    fn write_event_enable() {
561        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
562        // Write only TO_OFFNORMAL enabled (bit 0 = 0b100 = 0x80 when shifted)
563        ee.write_property(
564            PropertyIdentifier::EVENT_ENABLE,
565            None,
566            PropertyValue::BitString {
567                unused_bits: 5,
568                data: vec![0b1000_0000],
569            },
570            None,
571        )
572        .unwrap();
573        let val = ee
574            .read_property(PropertyIdentifier::EVENT_ENABLE, None)
575            .unwrap();
576        assert_eq!(
577            val,
578            PropertyValue::BitString {
579                unused_bits: 5,
580                data: vec![0b1000_0000],
581            }
582        );
583    }
584
585    #[test]
586    fn property_list_complete() {
587        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
588        let props = ee.property_list();
589        assert!(props.contains(&PropertyIdentifier::EVENT_TYPE));
590        assert!(props.contains(&PropertyIdentifier::NOTIFY_TYPE));
591        assert!(props.contains(&PropertyIdentifier::EVENT_PARAMETERS));
592        assert!(props.contains(&PropertyIdentifier::OBJECT_PROPERTY_REFERENCE));
593        assert!(props.contains(&PropertyIdentifier::EVENT_STATE));
594        assert!(props.contains(&PropertyIdentifier::EVENT_ENABLE));
595        assert!(props.contains(&PropertyIdentifier::ACKED_TRANSITIONS));
596        assert!(props.contains(&PropertyIdentifier::NOTIFICATION_CLASS));
597    }
598
599    #[test]
600    fn write_event_parameters() {
601        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
602        let params = vec![0x01, 0x02, 0x03];
603        ee.write_property(
604            PropertyIdentifier::EVENT_PARAMETERS,
605            None,
606            PropertyValue::OctetString(params.clone()),
607            None,
608        )
609        .unwrap();
610        let val = ee
611            .read_property(PropertyIdentifier::EVENT_PARAMETERS, None)
612            .unwrap();
613        assert_eq!(val, PropertyValue::OctetString(params));
614    }
615
616    #[test]
617    fn read_event_state_default() {
618        let ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
619        let val = ee
620            .read_property(PropertyIdentifier::EVENT_STATE, None)
621            .unwrap();
622        assert_eq!(val, PropertyValue::Enumerated(0)); // normal
623    }
624
625    #[test]
626    fn write_unknown_property_denied() {
627        let mut ee = EventEnrollmentObject::new(1, "EE-1", 0).unwrap();
628        let result = ee.write_property(
629            PropertyIdentifier::PRESENT_VALUE,
630            None,
631            PropertyValue::Real(1.0),
632            None,
633        );
634        assert!(result.is_err());
635    }
636
637    // -----------------------------------------------------------------------
638    // FaultParameters tests
639    // -----------------------------------------------------------------------
640
641    #[test]
642    fn fault_parameters_default_none() {
643        let ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
644        let val = ee
645            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
646            .unwrap();
647        assert_eq!(val, PropertyValue::Null);
648    }
649
650    #[test]
651    fn fault_parameters_none_variant() {
652        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
653        ee.set_fault_parameters(Some(FaultParameters::FaultNone));
654        let val = ee
655            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
656            .unwrap();
657        assert_eq!(val, PropertyValue::Unsigned(0));
658    }
659
660    #[test]
661    fn fault_parameters_character_string() {
662        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
663        ee.set_fault_parameters(Some(FaultParameters::FaultCharacterString {
664            fault_values: vec!["alarm".to_string(), "critical".to_string()],
665        }));
666        let val = ee
667            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
668            .unwrap();
669        assert_eq!(val, PropertyValue::Unsigned(1));
670    }
671
672    #[test]
673    fn fault_parameters_extended() {
674        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
675        ee.set_fault_parameters(Some(FaultParameters::FaultExtended {
676            vendor_id: 42,
677            extended_fault_type: 7,
678            parameters: vec![0x01, 0x02],
679        }));
680        let val = ee
681            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
682            .unwrap();
683        assert_eq!(val, PropertyValue::Unsigned(2));
684    }
685
686    #[test]
687    fn fault_parameters_life_safety() {
688        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
689        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
690        ee.set_fault_parameters(Some(FaultParameters::FaultLifeSafety {
691            fault_values: vec![1, 2, 3],
692            mode_for_reference: BACnetDeviceObjectPropertyReference {
693                object_identifier: ai_oid,
694                property_identifier: PropertyIdentifier::PRESENT_VALUE.to_raw(),
695                property_array_index: None,
696                device_identifier: None,
697            },
698        }));
699        let val = ee
700            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
701            .unwrap();
702        assert_eq!(val, PropertyValue::Unsigned(3));
703    }
704
705    #[test]
706    fn fault_parameters_state() {
707        use bacnet_types::constructed::BACnetPropertyStates;
708        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
709        ee.set_fault_parameters(Some(FaultParameters::FaultState {
710            fault_values: vec![BACnetPropertyStates::BooleanValue(true)],
711        }));
712        let val = ee
713            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
714            .unwrap();
715        assert_eq!(val, PropertyValue::Unsigned(4));
716    }
717
718    #[test]
719    fn fault_parameters_status_flags() {
720        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
721        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
722        ee.set_fault_parameters(Some(FaultParameters::FaultStatusFlags {
723            reference: BACnetDeviceObjectPropertyReference {
724                object_identifier: ai_oid,
725                property_identifier: PropertyIdentifier::STATUS_FLAGS.to_raw(),
726                property_array_index: None,
727                device_identifier: None,
728            },
729        }));
730        let val = ee
731            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
732            .unwrap();
733        assert_eq!(val, PropertyValue::Unsigned(5));
734    }
735
736    #[test]
737    fn fault_parameters_out_of_range() {
738        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
739        ee.set_fault_parameters(Some(FaultParameters::FaultOutOfRange {
740            min_normal: 0.0,
741            max_normal: 100.0,
742        }));
743        let val = ee
744            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
745            .unwrap();
746        assert_eq!(val, PropertyValue::Unsigned(6));
747    }
748
749    #[test]
750    fn fault_parameters_listed() {
751        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
752        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
753        ee.set_fault_parameters(Some(FaultParameters::FaultListed {
754            reference: BACnetDeviceObjectPropertyReference {
755                object_identifier: ai_oid,
756                property_identifier: PropertyIdentifier::PRESENT_VALUE.to_raw(),
757                property_array_index: None,
758                device_identifier: None,
759            },
760        }));
761        let val = ee
762            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
763            .unwrap();
764        assert_eq!(val, PropertyValue::Unsigned(7));
765    }
766
767    #[test]
768    fn fault_parameters_in_property_list() {
769        let ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
770        let props = ee.property_list();
771        assert!(props.contains(&PropertyIdentifier::FAULT_PARAMETERS));
772    }
773
774    #[test]
775    fn fault_parameters_clear() {
776        let mut ee = EventEnrollmentObject::new(1, "EE-FP", 0).unwrap();
777        ee.set_fault_parameters(Some(FaultParameters::FaultNone));
778        let val = ee
779            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
780            .unwrap();
781        assert_eq!(val, PropertyValue::Unsigned(0));
782
783        // Clear back to None
784        ee.set_fault_parameters(None);
785        let val = ee
786            .read_property(PropertyIdentifier::FAULT_PARAMETERS, None)
787            .unwrap();
788        assert_eq!(val, PropertyValue::Null);
789    }
790
791    // -----------------------------------------------------------------------
792    // AlertEnrollmentObject tests
793    // -----------------------------------------------------------------------
794
795    #[test]
796    fn alert_enrollment_create() {
797        let ae = AlertEnrollmentObject::new(1, "AE-1").unwrap();
798        assert_eq!(
799            ae.object_identifier().object_type(),
800            ObjectType::ALERT_ENROLLMENT
801        );
802        assert_eq!(ae.object_identifier().instance_number(), 1);
803        assert_eq!(ae.object_name(), "AE-1");
804    }
805
806    #[test]
807    fn alert_enrollment_object_type() {
808        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
809        let val = ae
810            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
811            .unwrap();
812        assert_eq!(
813            val,
814            PropertyValue::Enumerated(ObjectType::ALERT_ENROLLMENT.to_raw())
815        );
816    }
817
818    #[test]
819    fn alert_enrollment_present_value_default() {
820        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
821        let val = ae
822            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
823            .unwrap();
824        assert_eq!(val, PropertyValue::Enumerated(0));
825    }
826
827    #[test]
828    fn alert_enrollment_event_detection_enable() {
829        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
830        let val = ae
831            .read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
832            .unwrap();
833        assert_eq!(val, PropertyValue::Boolean(true));
834    }
835
836    #[test]
837    fn alert_enrollment_write_event_detection_enable() {
838        let mut ae = AlertEnrollmentObject::new(1, "AE").unwrap();
839        ae.write_property(
840            PropertyIdentifier::EVENT_DETECTION_ENABLE,
841            None,
842            PropertyValue::Boolean(false),
843            None,
844        )
845        .unwrap();
846        let val = ae
847            .read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
848            .unwrap();
849        assert_eq!(val, PropertyValue::Boolean(false));
850    }
851
852    #[test]
853    fn alert_enrollment_event_enable() {
854        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
855        let val = ae
856            .read_property(PropertyIdentifier::EVENT_ENABLE, None)
857            .unwrap();
858        // Default event_enable = 0b111, shifted left 5 = 0b1110_0000
859        assert_eq!(
860            val,
861            PropertyValue::BitString {
862                unused_bits: 5,
863                data: vec![0b1110_0000],
864            }
865        );
866    }
867
868    #[test]
869    fn alert_enrollment_write_event_enable() {
870        let mut ae = AlertEnrollmentObject::new(1, "AE").unwrap();
871        ae.write_property(
872            PropertyIdentifier::EVENT_ENABLE,
873            None,
874            PropertyValue::BitString {
875                unused_bits: 5,
876                data: vec![0b1000_0000],
877            },
878            None,
879        )
880        .unwrap();
881        let val = ae
882            .read_property(PropertyIdentifier::EVENT_ENABLE, None)
883            .unwrap();
884        assert_eq!(
885            val,
886            PropertyValue::BitString {
887                unused_bits: 5,
888                data: vec![0b1000_0000],
889            }
890        );
891    }
892
893    #[test]
894    fn alert_enrollment_notification_class() {
895        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
896        let val = ae
897            .read_property(PropertyIdentifier::NOTIFICATION_CLASS, None)
898            .unwrap();
899        assert_eq!(val, PropertyValue::Unsigned(0));
900    }
901
902    #[test]
903    fn alert_enrollment_write_notification_class() {
904        let mut ae = AlertEnrollmentObject::new(1, "AE").unwrap();
905        ae.write_property(
906            PropertyIdentifier::NOTIFICATION_CLASS,
907            None,
908            PropertyValue::Unsigned(42),
909            None,
910        )
911        .unwrap();
912        let val = ae
913            .read_property(PropertyIdentifier::NOTIFICATION_CLASS, None)
914            .unwrap();
915        assert_eq!(val, PropertyValue::Unsigned(42));
916    }
917
918    #[test]
919    fn alert_enrollment_property_list() {
920        let ae = AlertEnrollmentObject::new(1, "AE").unwrap();
921        let props = ae.property_list();
922        assert!(props.contains(&PropertyIdentifier::PRESENT_VALUE));
923        assert!(props.contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
924        assert!(props.contains(&PropertyIdentifier::EVENT_ENABLE));
925        assert!(props.contains(&PropertyIdentifier::NOTIFICATION_CLASS));
926        assert!(props.contains(&PropertyIdentifier::STATUS_FLAGS));
927    }
928}