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