Skip to main content

bacnet_objects/
event_log.rs

1//! EventLog (type 25) object per ASHRAE 135-2020 Clause 12.27.
2
3use std::borrow::Cow;
4use std::collections::VecDeque;
5use std::sync::Arc;
6
7use bacnet_types::bitstring::EventTransitionBits;
8use bacnet_types::constructed::BACnetEventLogRecord;
9use bacnet_types::enums::{NotifyType, ObjectType, PropertyIdentifier, Reliability};
10use bacnet_types::error::Error;
11use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
12
13use crate::clock::ClockReader;
14use crate::common::{self, read_common_properties};
15use crate::log_buffer::{
16    log_buffer_read_denied, LogBufferRecords, LogRecordBuffer, LogRecordIdentity,
17};
18use crate::log_lifecycle::LogLifecycle;
19use crate::log_reporting::{impl_buffer_ready_reporting, BufferReadyReporting};
20use crate::log_window::LogWindow;
21use crate::traits::BACnetObject;
22
23mod metadata;
24
25/// BACnet EventLog object.
26///
27/// Ring buffer of timestamped event log records. A server running the
28/// database logs the event notifications the device builds into its Event
29/// Logs ([`ObjectDatabase::log_event_notification`]), and those it receives
30/// into the logs that opt in with
31/// [`set_log_received_notifications`](Self::set_log_received_notifications);
32/// the application calls `add_record()` for anything else, such as a clock
33/// change. The log adds its own status records. Records are kept only while
34/// Enable is TRUE and the local time lies between Start_Time and Stop_Time;
35/// the database's trend poller looks at that window on every pass, so an
36/// opening or closing is recorded even when no record arrives.
37///
38/// The log reports intrinsically with the BUFFER_READY algorithm (Clause
39/// 13.3.7): once Notification_Threshold is set, the server tells the
40/// recipients of its Notification Class each time that many more records
41/// have been collected. No log takes a record for such a report.
42///
43/// [`ObjectDatabase::log_event_notification`]: crate::database::ObjectDatabase::log_event_notification
44pub struct EventLogObject {
45    oid: ObjectIdentifier,
46    name: String,
47    description: String,
48    log_enable: bool,
49    stop_when_full: bool,
50    buffer_size: u32,
51    log_buffer: LogRecordBuffer<BACnetEventLogRecord>,
52    status_flags: StatusFlags,
53    reliability: Reliability,
54    window: LogWindow,
55    reporting: BufferReadyReporting,
56    log_received_notifications: bool,
57    clock: Option<Arc<dyn ClockReader>>,
58}
59
60impl EventLogObject {
61    /// Create a new EventLog object.
62    pub fn new(instance: u32, name: impl Into<String>, buffer_size: u32) -> Result<Self, Error> {
63        let oid = ObjectIdentifier::new(ObjectType::EVENT_LOG, instance)?;
64        Ok(Self {
65            oid,
66            name: name.into(),
67            description: String::new(),
68            log_enable: true,
69            stop_when_full: false,
70            buffer_size,
71            log_buffer: LogRecordBuffer::new(buffer_size),
72            status_flags: StatusFlags::empty(),
73            reliability: Reliability::NO_FAULT_DETECTED,
74            window: LogWindow::default(),
75            reporting: BufferReadyReporting::default(),
76            log_received_notifications: false,
77            clock: None,
78        })
79    }
80
81    /// Add a record to the event log buffer.
82    ///
83    /// The record is kept as given; ReadRange serves its encoding, which for
84    /// an ACK_NOTIFICATION leaves out ack-required, from-state and event
85    /// values (see `EventLogDatum::Notification`).
86    ///
87    /// Success does not guarantee a resident ordinary record: disabled logging
88    /// and a record outside the Start_Time / Stop_Time window are ignored,
89    /// zero-capacity logging may only count, and a stop-before-full
90    /// transition records status instead. Missing/invalid status clocks fail
91    /// atomically with DEVICE / OPERATIONAL_PROBLEM.
92    pub fn add_record(&mut self, record: BACnetEventLogRecord) -> Result<(), Error> {
93        self.lifecycle().try_add_ordinary(record).map(|_| ())
94    }
95
96    /// Get the current buffer contents.
97    pub fn records(&self) -> &VecDeque<BACnetEventLogRecord> {
98        self.log_buffer.records()
99    }
100
101    /// Total_Record_Count, as
102    /// [`TrendLogObject::total_record_count`](crate::trend::TrendLogObject::total_record_count)
103    /// serves it.
104    pub fn total_record_count(&self) -> u32 {
105        self.log_buffer.total_record_count()
106    }
107
108    /// Restore the log buffer, or seed Total_Record_Count with no records,
109    /// before the log is added to an `ObjectDatabase`, under the rules of
110    /// [`TrendLogObject::restore_log_buffer`](crate::trend::TrendLogObject::restore_log_buffer)
111    /// (#1537). After a restart, follow it with
112    /// [`record_interruption`](Self::record_interruption), which counts
113    /// toward Total_Record_Count.
114    pub fn restore_log_buffer(
115        &mut self,
116        total_record_count: u32,
117        records: impl IntoIterator<Item = BACnetEventLogRecord>,
118    ) -> Result<(), Error> {
119        self.lifecycle()
120            .restore(total_record_count, records.into_iter().collect())
121    }
122
123    /// Append a LOG_INTERRUPTED status record, as
124    /// [`TrendLogObject::record_interruption`](crate::trend::TrendLogObject::record_interruption)
125    /// does, after restoring the log on a restart (#1537).
126    pub fn record_interruption(&mut self, date: Date, time: Time) -> Result<(), Error> {
127        self.lifecycle().record_interruption((date, time))
128    }
129
130    /// Clear the buffer.
131    pub fn clear(&mut self) {
132        self.log_buffer.clear();
133    }
134
135    /// Set the description string.
136    pub fn set_description(&mut self, desc: impl Into<String>) {
137        self.description = desc.into();
138    }
139
140    /// Set Start_Time, the local date and time from which records are kept,
141    /// as local configuration: nothing is recorded for the change itself,
142    /// but the log notes at once where the window stands, so a client's
143    /// write that then opens or shuts it is recorded. Every field
144    /// unspecified leaves the start open. Any other value has to name an
145    /// actual date and time, or it is PROPERTY / VALUE_OUT_OF_RANGE: the
146    /// weekday may stay unspecified, and unspecified seconds or hundredths
147    /// count as zero.
148    pub fn set_start_time(&mut self, date: Date, time: Time) -> Result<(), Error> {
149        self.lifecycle()
150            .configure_window(PropertyIdentifier::START_TIME, (date, time))
151    }
152
153    /// Set Stop_Time, the local date and time from which records are no
154    /// longer kept, under the same rules as
155    /// [`set_start_time`](Self::set_start_time).
156    pub fn set_stop_time(&mut self, date: Date, time: Time) -> Result<(), Error> {
157        self.lifecycle()
158            .configure_window(PropertyIdentifier::STOP_TIME, (date, time))
159    }
160
161    /// Whether the log takes the event notifications the device receives,
162    /// as well as those it builds. Off unless set.
163    pub fn logs_received_notifications(&self) -> bool {
164        self.log_received_notifications
165    }
166
167    /// Have the log take the Confirmed and UnconfirmedEventNotifications the
168    /// device receives, unicast or broadcast, each kept as it decoded, or
169    /// stop it doing so. Clause 12.27 leaves the choice to the device, and
170    /// it's off by default. A running server holds what it logs to a small
171    /// number of records per source each second; see
172    /// [`ObjectDatabase::log_received_event_notification`](crate::database::ObjectDatabase::log_received_event_notification).
173    pub fn set_log_received_notifications(&mut self, log: bool) {
174        self.log_received_notifications = log;
175    }
176
177    /// Set Notification_Threshold, the number of records that makes a
178    /// BUFFER_READY report; zero, the default, makes none.
179    pub fn set_notification_threshold(&mut self, threshold: u32) {
180        self.reporting.set_notification_threshold(threshold);
181    }
182
183    /// Set Notification_Class: the number of the Notification Class whose
184    /// recipients get the log's reports (0 by default).
185    pub fn set_notification_class(&mut self, class: u32) {
186        self.reporting.set_notification_class(class);
187    }
188
189    /// Set Event_Enable; a report goes out only while its TO_NORMAL flag is
190    /// set, as it is by default.
191    pub fn set_event_enable(&mut self, enable: EventTransitionBits) {
192        self.reporting.set_event_enable(enable);
193    }
194
195    /// Set Notify_Type, sent with each report (EVENT by default).
196    pub fn set_notify_type(&mut self, notify_type: NotifyType) {
197        self.reporting.set_notify_type(notify_type);
198    }
199
200    fn lifecycle(&mut self) -> LogLifecycle<'_, BACnetEventLogRecord> {
201        LogLifecycle::new(
202            &mut self.log_buffer,
203            &mut self.log_enable,
204            &mut self.stop_when_full,
205            self.clock.as_ref(),
206            &mut self.window,
207            &mut self.reporting,
208        )
209    }
210}
211
212impl BACnetObject for EventLogObject {
213    fn object_identifier(&self) -> ObjectIdentifier {
214        self.oid
215    }
216
217    fn object_name(&self) -> &str {
218        &self.name
219    }
220
221    fn read_property(
222        &self,
223        property: PropertyIdentifier,
224        array_index: Option<u32>,
225    ) -> Result<PropertyValue, Error> {
226        // Table 12-31 has neither Out_Of_Service nor Log_Interval (#1064), and
227        // Clause 12.27 holds the OUT_OF_SERVICE flag FALSE.
228        if let Some(result) =
229            read_common_properties!(self, property, array_index, no_out_of_service)
230        {
231            return result;
232        }
233        if let Some(value) = self.window.read(property) {
234            return Ok(value);
235        }
236        let total = self.log_buffer.total_record_count();
237        if let Some(result) = self.reporting.read(property, array_index, total) {
238            return result;
239        }
240        match property {
241            p if p == PropertyIdentifier::OBJECT_TYPE => {
242                Ok(PropertyValue::Enumerated(ObjectType::EVENT_LOG.to_raw()))
243            }
244            p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
245            p if p == PropertyIdentifier::STOP_WHEN_FULL => {
246                Ok(PropertyValue::Boolean(self.stop_when_full))
247            }
248            p if p == PropertyIdentifier::BUFFER_SIZE => {
249                Ok(PropertyValue::Unsigned(self.buffer_size as u64))
250            }
251            // ReadRange pages it through `log_buffer_internal`.
252            p if p == PropertyIdentifier::LOG_BUFFER => Err(log_buffer_read_denied()),
253            p if p == PropertyIdentifier::RECORD_COUNT => {
254                Ok(PropertyValue::Unsigned(self.records().len() as u64))
255            }
256            p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => {
257                Ok(PropertyValue::Unsigned(u64::from(total)))
258            }
259            _ => Err(common::unknown_property_error()),
260        }
261    }
262
263    fn write_property(
264        &mut self,
265        property: PropertyIdentifier,
266        array_index: Option<u32>,
267        value: PropertyValue,
268        _priority: Option<u8>,
269    ) -> Result<(), Error> {
270        if property == PropertyIdentifier::LOG_ENABLE {
271            if let PropertyValue::Boolean(v) = value {
272                return self.lifecycle().write_enable(v);
273            }
274            return Err(common::invalid_data_type_error());
275        }
276        if let Some(result) = self.window.write(property, &value) {
277            // A change that opens or closes the window is recorded at once.
278            result?;
279            self.lifecycle().refresh_window();
280            return Ok(());
281        }
282        if property == PropertyIdentifier::STOP_WHEN_FULL {
283            if let PropertyValue::Boolean(v) = value {
284                return self.lifecycle().write_stop_when_full(v);
285            }
286            return Err(common::invalid_data_type_error());
287        }
288        if property == PropertyIdentifier::RECORD_COUNT {
289            if let PropertyValue::Unsigned(0) = value {
290                return self.lifecycle().purge();
291            }
292            return Err(common::invalid_data_type_error());
293        }
294        let total = self.log_buffer.total_record_count();
295        if let Some(result) = self.reporting.write(property, array_index, &value, total) {
296            return result;
297        }
298        if let Some(result) = common::write_description(&mut self.description, property, &value) {
299            return result;
300        }
301        Err(crate::common::unhandled_write_error(
302            self.property_metadata().as_ref(),
303            property,
304            array_index,
305        ))
306    }
307
308    fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
309        metadata::for_object(self)
310    }
311
312    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
313        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
314    }
315
316    fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
317        self.clock = clock;
318    }
319
320    fn log_record_identities_internal(&self) -> Option<Vec<LogRecordIdentity>> {
321        Some(self.log_buffer.identities())
322    }
323
324    fn log_buffer_internal(&self) -> Option<&dyn LogBufferRecords> {
325        Some(&self.log_buffer)
326    }
327
328    fn add_event_log_record(&mut self, record: BACnetEventLogRecord) -> Result<(), Error> {
329        self.add_record(record)
330    }
331
332    fn refresh_log_window_internal(&mut self) -> bool {
333        self.lifecycle().refresh_window()
334    }
335
336    fn logs_received_event_notifications_internal(&self) -> bool {
337        self.log_received_notifications
338    }
339
340    impl_buffer_ready_reporting!(reporting, log_buffer);
341}
342
343#[cfg(test)]
344mod tests;