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::constructed::BACnetLogRecord;
8use bacnet_types::enums::{ObjectType, PropertyIdentifier};
9use bacnet_types::error::Error;
10use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
11
12use crate::clock::ClockReader;
13use crate::common::{self, read_common_properties};
14use crate::log_buffer::{LogRecordBuffer, LogRecordIdentity, LogRecordProfile};
15use crate::log_lifecycle::{LogLifecycle, LogLifecycleSnapshot};
16use crate::traits::{BACnetObject, WritePropertyRollback};
17
18/// BACnet EventLog object.
19///
20/// Ring buffer of timestamped event log records. The application calls
21/// `add_record()` to log event data. Resident records retain the legacy shared
22/// Rust `BACnetLogRecord` shape; Event Log projection remains family-specific.
23pub struct EventLogObject {
24    oid: ObjectIdentifier,
25    name: String,
26    description: String,
27    log_enable: bool,
28    log_interval: u32,
29    stop_when_full: bool,
30    buffer_size: u32,
31    log_buffer: LogRecordBuffer,
32    status_flags: StatusFlags,
33    event_state: u32,
34    out_of_service: bool,
35    reliability: u32,
36    clock: Option<Arc<dyn ClockReader>>,
37}
38
39impl EventLogObject {
40    /// Create a new EventLog object.
41    pub fn new(instance: u32, name: impl Into<String>, buffer_size: u32) -> Result<Self, Error> {
42        let oid = ObjectIdentifier::new(ObjectType::EVENT_LOG, instance)?;
43        Ok(Self {
44            oid,
45            name: name.into(),
46            description: String::new(),
47            log_enable: true,
48            log_interval: 0,
49            stop_when_full: false,
50            buffer_size,
51            log_buffer: LogRecordBuffer::new(buffer_size),
52            status_flags: StatusFlags::empty(),
53            event_state: 0,
54            out_of_service: false,
55            reliability: 0,
56            clock: None,
57        })
58    }
59
60    /// Add a BACnetLogRecord to the event log buffer.
61    pub fn add_record(&mut self, record: BACnetLogRecord) {
62        let _ = self.try_add_record_internal(record);
63    }
64
65    /// Get the current buffer contents.
66    pub fn records(&self) -> &VecDeque<BACnetLogRecord> {
67        self.log_buffer.records()
68    }
69
70    /// Clear the buffer.
71    pub fn clear(&mut self) {
72        self.log_buffer.clear();
73    }
74
75    /// Set the description string.
76    pub fn set_description(&mut self, desc: impl Into<String>) {
77        self.description = desc.into();
78    }
79
80    fn try_add_record_internal(&mut self, record: BACnetLogRecord) -> Result<(), Error> {
81        self.lifecycle().try_add_ordinary(record).map(|_| ())
82    }
83
84    fn lifecycle(&mut self) -> LogLifecycle<'_> {
85        LogLifecycle::new(
86            &mut self.log_buffer,
87            &mut self.log_enable,
88            &mut self.stop_when_full,
89            self.clock.as_ref(),
90        )
91    }
92}
93
94impl BACnetObject for EventLogObject {
95    fn object_identifier(&self) -> ObjectIdentifier {
96        self.oid
97    }
98
99    fn object_name(&self) -> &str {
100        &self.name
101    }
102
103    fn read_property(
104        &self,
105        property: PropertyIdentifier,
106        array_index: Option<u32>,
107    ) -> Result<PropertyValue, Error> {
108        if let Some(result) = read_common_properties!(self, property, array_index) {
109            return result;
110        }
111        match property {
112            p if p == PropertyIdentifier::OBJECT_TYPE => {
113                Ok(PropertyValue::Enumerated(ObjectType::EVENT_LOG.to_raw()))
114            }
115            p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
116            p if p == PropertyIdentifier::LOG_INTERVAL => {
117                Ok(PropertyValue::Unsigned(self.log_interval as u64))
118            }
119            p if p == PropertyIdentifier::STOP_WHEN_FULL => {
120                Ok(PropertyValue::Boolean(self.stop_when_full))
121            }
122            p if p == PropertyIdentifier::BUFFER_SIZE => {
123                Ok(PropertyValue::Unsigned(self.buffer_size as u64))
124            }
125            p if p == PropertyIdentifier::LOG_BUFFER => {
126                Ok(self.log_buffer.project(LogRecordProfile::Event))
127            }
128            p if p == PropertyIdentifier::RECORD_COUNT => {
129                Ok(PropertyValue::Unsigned(self.records().len() as u64))
130            }
131            p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => Ok(PropertyValue::Unsigned(
132                self.log_buffer.total_record_count() as u64,
133            )),
134            p if p == PropertyIdentifier::EVENT_STATE => {
135                Ok(PropertyValue::Enumerated(self.event_state))
136            }
137            _ => Err(common::unknown_property_error()),
138        }
139    }
140
141    fn write_property(
142        &mut self,
143        property: PropertyIdentifier,
144        _array_index: Option<u32>,
145        value: PropertyValue,
146        _priority: Option<u8>,
147    ) -> Result<(), Error> {
148        if property == PropertyIdentifier::LOG_ENABLE {
149            if let PropertyValue::Boolean(v) = value {
150                return self.lifecycle().write_enable(v);
151            }
152            return Err(common::invalid_data_type_error());
153        }
154        if property == PropertyIdentifier::LOG_INTERVAL {
155            if let PropertyValue::Unsigned(v) = value {
156                self.log_interval = common::u64_to_u32(v)?;
157                return Ok(());
158            }
159            return Err(common::invalid_data_type_error());
160        }
161        if property == PropertyIdentifier::STOP_WHEN_FULL {
162            if let PropertyValue::Boolean(v) = value {
163                return self.lifecycle().write_stop_when_full(v);
164            }
165            return Err(common::invalid_data_type_error());
166        }
167        if property == PropertyIdentifier::RECORD_COUNT {
168            if let PropertyValue::Unsigned(0) = value {
169                return self.lifecycle().purge();
170            }
171            return Err(common::invalid_data_type_error());
172        }
173        if let Some(result) =
174            common::write_out_of_service(&mut self.out_of_service, property, &value)
175        {
176            return result;
177        }
178        if let Some(result) = common::write_description(&mut self.description, property, &value) {
179            return result;
180        }
181        Err(common::write_access_denied_error())
182    }
183
184    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
185        static PROPS: &[PropertyIdentifier] = &[
186            PropertyIdentifier::OBJECT_IDENTIFIER,
187            PropertyIdentifier::OBJECT_NAME,
188            PropertyIdentifier::DESCRIPTION,
189            PropertyIdentifier::OBJECT_TYPE,
190            PropertyIdentifier::LOG_ENABLE,
191            PropertyIdentifier::LOG_INTERVAL,
192            PropertyIdentifier::STOP_WHEN_FULL,
193            PropertyIdentifier::BUFFER_SIZE,
194            PropertyIdentifier::LOG_BUFFER,
195            PropertyIdentifier::RECORD_COUNT,
196            PropertyIdentifier::TOTAL_RECORD_COUNT,
197            PropertyIdentifier::STATUS_FLAGS,
198            PropertyIdentifier::EVENT_STATE,
199            PropertyIdentifier::OUT_OF_SERVICE,
200            PropertyIdentifier::RELIABILITY,
201        ];
202        Cow::Borrowed(PROPS)
203    }
204
205    fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
206        self.clock = clock;
207    }
208
209    fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
210        matches!(
211            property,
212            PropertyIdentifier::LOG_ENABLE
213                | PropertyIdentifier::LOG_INTERVAL
214                | PropertyIdentifier::STOP_WHEN_FULL
215                | PropertyIdentifier::RECORD_COUNT
216                | PropertyIdentifier::OUT_OF_SERVICE
217                | PropertyIdentifier::DESCRIPTION
218        )
219    }
220
221    fn capture_write_property_rollback(
222        &mut self,
223        property: PropertyIdentifier,
224        value: &PropertyValue,
225    ) -> Option<WritePropertyRollback> {
226        ((property == PropertyIdentifier::RECORD_COUNT
227            && matches!(value, PropertyValue::Unsigned(0)))
228            || (matches!(
229                property,
230                PropertyIdentifier::LOG_ENABLE | PropertyIdentifier::STOP_WHEN_FULL
231            ) && matches!(value, PropertyValue::Boolean(_))))
232        .then(|| {
233            WritePropertyRollback::new(LogLifecycleSnapshot::capture(
234                &self.log_buffer,
235                self.log_enable,
236                self.stop_when_full,
237            ))
238        })
239    }
240
241    fn restore_write_property_rollback(
242        &mut self,
243        rollback: WritePropertyRollback,
244    ) -> Result<(), Error> {
245        rollback.downcast::<LogLifecycleSnapshot>()?.restore(
246            &mut self.log_buffer,
247            &mut self.log_enable,
248            &mut self.stop_when_full,
249        );
250        Ok(())
251    }
252
253    fn log_record_identities_internal(&self) -> Option<Vec<LogRecordIdentity>> {
254        Some(self.log_buffer.identities())
255    }
256}
257
258#[cfg(test)]
259mod tests;