Skip to main content

bacnet_objects/
event_log.rs

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