Skip to main content

bacnet_objects/event_enrollment/
alert.rs

1use std::borrow::Cow;
2
3use bacnet_types::bitstring::EventTransitionBits;
4use bacnet_types::enums::{EventState, NotifyType, ObjectType, PropertyIdentifier};
5use bacnet_types::error::Error;
6use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
7
8use crate::common;
9use crate::event::history::EventHistory;
10use crate::property_metadata::PropertyMetadata;
11use crate::traits::BACnetObject;
12
13use super::metadata;
14
15/// BACnet AlertEnrollment object (type 52).
16///
17/// Provides the Alert Enrollment property surface from ASHRAE 135-2020 Table
18/// 12-61. `Present_Value` names whichever object most recently handed this
19/// enrollment an alert to distribute; recording that source does not itself
20/// evaluate or generate an event.
21pub struct AlertEnrollmentObject {
22    oid: ObjectIdentifier,
23    name: String,
24    description: String,
25    /// Event_State; NORMAL while event detection is disabled.
26    pub(super) event_state: EventState,
27    /// Source of the most recent alert handed to this enrollment.
28    pub present_value: ObjectIdentifier,
29    /// Whether event detection is enabled.
30    ///
31    /// Prefer [`Self::set_event_detection_enable`] so disabling also clears
32    /// stored event state. This field remains public for compatibility;
33    /// property reads and internal transition hooks still enforce the
34    /// disabled-state invariant after a direct assignment. Re-enable through
35    /// the setter as well: a direct FALSE-to-TRUE assignment cannot run the
36    /// reset and may expose state stored before the direct disable.
37    pub event_detection_enable: bool,
38    /// Acked_Transitions: a set flag means that transition was acknowledged.
39    pub(super) acked_transitions: EventTransitionBits,
40    pub(super) event_history: EventHistory,
41    /// Event_Enable: the transitions whose notifications are distributed.
42    pub event_enable: EventTransitionBits,
43    /// Notification class number.
44    pub notification_class: u32,
45    /// Notification category for generated notifications. The local default is
46    /// ALARM; ACK_NOTIFICATION is output-only acknowledgement-flow vocabulary.
47    notify_type: NotifyType,
48}
49
50impl AlertEnrollmentObject {
51    /// Create a new AlertEnrollment object whose Present_Value starts as
52    /// `initial_source`, the latest alert source.
53    pub fn new(
54        instance: u32,
55        name: impl Into<String>,
56        initial_source: ObjectIdentifier,
57    ) -> Result<Self, Error> {
58        let oid = ObjectIdentifier::new(ObjectType::ALERT_ENROLLMENT, instance)?;
59        Ok(Self {
60            oid,
61            name: name.into(),
62            description: String::new(),
63            event_state: EventState::NORMAL,
64            present_value: initial_source,
65            event_detection_enable: true,
66            acked_transitions: EventTransitionBits::all(),
67            event_history: EventHistory::default(),
68            event_enable: EventTransitionBits::all(),
69            notification_class: 0,
70            notify_type: NotifyType::ALARM,
71        })
72    }
73
74    /// Record `source` as the object behind the latest alert.
75    ///
76    /// This source-ownership hook updates only `Present_Value`; it does not
77    /// evaluate an alert, change event/acknowledgement history, or generate a
78    /// notification.
79    pub fn record_alert_source(&mut self, source: ObjectIdentifier) {
80        self.present_value = source;
81    }
82
83    /// Enable or disable event detection.
84    ///
85    /// Disabling applies the Clause 13.2.2.1 initial conditions immediately.
86    pub fn set_event_detection_enable(&mut self, enabled: bool) {
87        if !enabled || !self.event_detection_enable {
88            self.event_state = EventState::NORMAL;
89            self.acked_transitions = EventTransitionBits::all();
90            self.event_history.reset();
91        }
92        self.event_detection_enable = enabled;
93    }
94
95    /// Alert-local projection of the universal properties that Table 12-61
96    /// serves. In particular, the shared common-property macro is intentionally
97    /// not used because it would also expose Status_Flags, Out_Of_Service, and
98    /// Reliability, none of which is a Table 12-61 row.
99    fn read_alert_common_property(
100        &self,
101        property: PropertyIdentifier,
102        array_index: Option<u32>,
103    ) -> Option<Result<PropertyValue, Error>> {
104        match property {
105            PropertyIdentifier::OBJECT_IDENTIFIER => {
106                Some(Ok(PropertyValue::ObjectIdentifier(self.oid)))
107            }
108            PropertyIdentifier::OBJECT_NAME => {
109                Some(Ok(PropertyValue::CharacterString(self.name.clone())))
110            }
111            PropertyIdentifier::DESCRIPTION => {
112                Some(Ok(PropertyValue::CharacterString(self.description.clone())))
113            }
114            PropertyIdentifier::PROPERTY_LIST => {
115                let properties = self.property_list();
116                Some(common::read_property_list_property(
117                    &properties,
118                    array_index,
119                ))
120            }
121            _ => None,
122        }
123    }
124}
125
126impl BACnetObject for AlertEnrollmentObject {
127    fn object_identifier(&self) -> ObjectIdentifier {
128        self.oid
129    }
130
131    fn object_name(&self) -> &str {
132        &self.name
133    }
134
135    fn read_property(
136        &self,
137        property: PropertyIdentifier,
138        array_index: Option<u32>,
139    ) -> Result<PropertyValue, Error> {
140        if property == PropertyIdentifier::EVENT_TIME_STAMPS {
141            if !self.event_detection_enable {
142                return EventHistory::default()
143                    .read(property, array_index)
144                    .expect("EventHistory handles Event_Time_Stamps");
145            }
146            return self
147                .event_history
148                .read(property, array_index)
149                .expect("EventHistory handles Event_Time_Stamps");
150        }
151        if let Some(result) = self.read_alert_common_property(property, array_index) {
152            return result;
153        }
154        match property {
155            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
156                ObjectType::ALERT_ENROLLMENT.to_raw(),
157            )),
158            p if p == PropertyIdentifier::PRESENT_VALUE => {
159                Ok(PropertyValue::ObjectIdentifier(self.present_value))
160            }
161            p if p == PropertyIdentifier::EVENT_DETECTION_ENABLE => {
162                Ok(PropertyValue::Boolean(self.event_detection_enable))
163            }
164            p if p == PropertyIdentifier::EVENT_ENABLE => Ok(PropertyValue::BitString {
165                unused_bits: 5,
166                data: vec![self.event_enable.to_bacnet()],
167            }),
168            p if p == PropertyIdentifier::NOTIFICATION_CLASS => {
169                Ok(PropertyValue::Unsigned(self.notification_class as u64))
170            }
171            p if p == PropertyIdentifier::NOTIFY_TYPE => {
172                Ok(PropertyValue::Enumerated(self.notify_type.to_raw()))
173            }
174            p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(
175                if self.event_detection_enable {
176                    self.event_state
177                } else {
178                    EventState::NORMAL
179                }
180                .to_raw(),
181            )),
182            p if p == PropertyIdentifier::ACKED_TRANSITIONS => Ok(PropertyValue::BitString {
183                unused_bits: 5,
184                data: vec![if self.event_detection_enable {
185                    self.acked_transitions
186                } else {
187                    EventTransitionBits::all()
188                }
189                .to_bacnet()],
190            }),
191            _ => Err(common::unknown_property_error()),
192        }
193    }
194
195    fn write_property(
196        &mut self,
197        property: PropertyIdentifier,
198        _array_index: Option<u32>,
199        value: PropertyValue,
200        _priority: Option<u8>,
201    ) -> Result<(), Error> {
202        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
203            if let PropertyValue::Boolean(v) = value {
204                self.set_event_detection_enable(v);
205                return Ok(());
206            }
207            return Err(common::invalid_data_type_error());
208        }
209        if property == PropertyIdentifier::EVENT_ENABLE {
210            // BACnetEventTransitionBits is a 3-bit production (Clause 21):
211            // the written BitString must declare its canonical shape.
212            if let PropertyValue::BitString { unused_bits, data } = &value {
213                let byte = common::check_fixed_width_bit_string(*unused_bits, data, 3)?;
214                self.event_enable = EventTransitionBits::from_bacnet(&[byte]);
215                return Ok(());
216            }
217            return Err(common::invalid_data_type_error());
218        }
219        if property == PropertyIdentifier::NOTIFICATION_CLASS {
220            if let PropertyValue::Unsigned(v) = value {
221                self.notification_class = common::u64_to_u32(v)?;
222                return Ok(());
223            }
224            return Err(common::invalid_data_type_error());
225        }
226        if property == PropertyIdentifier::NOTIFY_TYPE {
227            if let PropertyValue::Enumerated(v) = value {
228                let notify_type = NotifyType::from_raw(v);
229                if notify_type != NotifyType::ALARM && notify_type != NotifyType::EVENT {
230                    return Err(common::value_out_of_range_error());
231                }
232                self.notify_type = notify_type;
233                return Ok(());
234            }
235            return Err(common::invalid_data_type_error());
236        }
237        if let Some(result) = common::write_description(&mut self.description, property, &value) {
238            return result;
239        }
240        Err(crate::common::unhandled_write_error(
241            self.property_metadata().as_ref(),
242            property,
243            _array_index,
244        ))
245    }
246
247    fn set_event_state_internal(&mut self, state: EventState) -> Result<(), Error> {
248        if !self.event_detection_enable && state != EventState::NORMAL {
249            return Err(common::write_access_denied_error());
250        }
251        self.event_state = state;
252        Ok(())
253    }
254
255    fn set_acked_transitions_internal(
256        &mut self,
257        transition_bit: EventTransitionBits,
258        acknowledged: bool,
259    ) -> Result<(), Error> {
260        if !self.event_detection_enable {
261            return Err(common::write_access_denied_error());
262        }
263        let transition_bit = transition_bit & EventTransitionBits::all();
264        if acknowledged {
265            self.acked_transitions.insert(transition_bit);
266        } else {
267            // Alert Enrollment never requires acknowledgment for TO_NORMAL
268            // (Clause 12.52.8), so that bit cannot enter the unacknowledged
269            // state even if a generic transition hook asks to clear it.
270            self.acked_transitions
271                .remove(transition_bit.difference(EventTransitionBits::TO_NORMAL));
272        }
273        self.acked_transitions
274            .insert(EventTransitionBits::TO_NORMAL);
275        Ok(())
276    }
277
278    fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
279        matches!(
280            property,
281            PropertyIdentifier::DESCRIPTION
282                | PropertyIdentifier::EVENT_DETECTION_ENABLE
283                | PropertyIdentifier::EVENT_ENABLE
284                | PropertyIdentifier::NOTIFICATION_CLASS
285                | PropertyIdentifier::NOTIFY_TYPE
286        )
287    }
288
289    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
290        Cow::Borrowed(metadata::ALERT_ENROLLMENT_PROPERTIES)
291    }
292
293    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
294        crate::property_metadata::property_list_from_metadata(metadata::ALERT_ENROLLMENT_PROPERTIES)
295    }
296}