Skip to main content

bacnet_objects/
event_log.rs

1//! EventLog (type 25) object per ASHRAE 135-2020 Clause 12.28.
2
3use std::borrow::Cow;
4use std::collections::VecDeque;
5
6use bacnet_types::constructed::{BACnetLogRecord, LogDatum};
7use bacnet_types::enums::{ObjectType, PropertyIdentifier};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
10
11use crate::common::{self, read_common_properties};
12use crate::traits::BACnetObject;
13
14/// BACnet EventLog object.
15///
16/// Ring buffer of timestamped event log records. The application calls
17/// `add_record()` to log event data. Uses the same `BACnetLogRecord`
18/// encoding as TrendLog.
19pub struct EventLogObject {
20    oid: ObjectIdentifier,
21    name: String,
22    description: String,
23    log_enable: bool,
24    log_interval: u32,
25    stop_when_full: bool,
26    buffer_size: u32,
27    buffer: VecDeque<BACnetLogRecord>,
28    total_record_count: u64,
29    status_flags: StatusFlags,
30    event_state: u32,
31    out_of_service: bool,
32    reliability: u32,
33}
34
35impl EventLogObject {
36    /// Create a new EventLog object.
37    pub fn new(instance: u32, name: impl Into<String>, buffer_size: u32) -> Result<Self, Error> {
38        let oid = ObjectIdentifier::new(ObjectType::EVENT_LOG, instance)?;
39        Ok(Self {
40            oid,
41            name: name.into(),
42            description: String::new(),
43            log_enable: true,
44            log_interval: 0,
45            stop_when_full: false,
46            buffer_size,
47            buffer: VecDeque::new(),
48            total_record_count: 0,
49            status_flags: StatusFlags::empty(),
50            event_state: 0,
51            out_of_service: false,
52            reliability: 0,
53        })
54    }
55
56    /// Add a BACnetLogRecord to the event log buffer.
57    pub fn add_record(&mut self, record: BACnetLogRecord) {
58        if !self.log_enable {
59            return;
60        }
61        if self.buffer.len() >= self.buffer_size as usize {
62            if self.stop_when_full {
63                return;
64            }
65            self.buffer.pop_front();
66        }
67        self.buffer.push_back(record);
68        self.total_record_count += 1;
69    }
70
71    /// Get the current buffer contents.
72    pub fn records(&self) -> &VecDeque<BACnetLogRecord> {
73        &self.buffer
74    }
75
76    /// Clear the buffer.
77    pub fn clear(&mut self) {
78        self.buffer.clear();
79    }
80
81    /// Set the description string.
82    pub fn set_description(&mut self, desc: impl Into<String>) {
83        self.description = desc.into();
84    }
85
86    fn encode_log_buffer(&self) -> PropertyValue {
87        let records = self
88            .buffer
89            .iter()
90            .map(|record| {
91                let datum_value = match &record.log_datum {
92                    LogDatum::LogStatus(v) => PropertyValue::Unsigned(*v as u64),
93                    LogDatum::BooleanValue(v) => PropertyValue::Boolean(*v),
94                    LogDatum::RealValue(v) => PropertyValue::Real(*v),
95                    LogDatum::EnumValue(v) => PropertyValue::Enumerated(*v),
96                    LogDatum::UnsignedValue(v) => PropertyValue::Unsigned(*v),
97                    LogDatum::SignedValue(v) => PropertyValue::Signed(*v as i32),
98                    LogDatum::BitstringValue { unused_bits, data } => PropertyValue::BitString {
99                        unused_bits: *unused_bits,
100                        data: data.clone(),
101                    },
102                    LogDatum::NullValue => PropertyValue::Null,
103                    LogDatum::Failure {
104                        error_class,
105                        error_code,
106                    } => PropertyValue::List(vec![
107                        PropertyValue::Unsigned(*error_class as u64),
108                        PropertyValue::Unsigned(*error_code as u64),
109                    ]),
110                    LogDatum::TimeChange(v) => PropertyValue::Real(*v),
111                    LogDatum::AnyValue(bytes) => PropertyValue::OctetString(bytes.clone()),
112                };
113                PropertyValue::List(vec![
114                    PropertyValue::Date(record.date),
115                    PropertyValue::Time(record.time),
116                    datum_value,
117                ])
118            })
119            .collect();
120        PropertyValue::List(records)
121    }
122}
123
124impl BACnetObject for EventLogObject {
125    fn object_identifier(&self) -> ObjectIdentifier {
126        self.oid
127    }
128
129    fn object_name(&self) -> &str {
130        &self.name
131    }
132
133    fn read_property(
134        &self,
135        property: PropertyIdentifier,
136        array_index: Option<u32>,
137    ) -> Result<PropertyValue, Error> {
138        if let Some(result) = read_common_properties!(self, property, array_index) {
139            return result;
140        }
141        match property {
142            p if p == PropertyIdentifier::OBJECT_TYPE => {
143                Ok(PropertyValue::Enumerated(ObjectType::EVENT_LOG.to_raw()))
144            }
145            p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
146            p if p == PropertyIdentifier::LOG_INTERVAL => {
147                Ok(PropertyValue::Unsigned(self.log_interval as u64))
148            }
149            p if p == PropertyIdentifier::STOP_WHEN_FULL => {
150                Ok(PropertyValue::Boolean(self.stop_when_full))
151            }
152            p if p == PropertyIdentifier::BUFFER_SIZE => {
153                Ok(PropertyValue::Unsigned(self.buffer_size as u64))
154            }
155            p if p == PropertyIdentifier::LOG_BUFFER => Ok(self.encode_log_buffer()),
156            p if p == PropertyIdentifier::RECORD_COUNT => {
157                Ok(PropertyValue::Unsigned(self.buffer.len() as u64))
158            }
159            p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => {
160                Ok(PropertyValue::Unsigned(self.total_record_count))
161            }
162            p if p == PropertyIdentifier::EVENT_STATE => {
163                Ok(PropertyValue::Enumerated(self.event_state))
164            }
165            _ => Err(common::unknown_property_error()),
166        }
167    }
168
169    fn write_property(
170        &mut self,
171        property: PropertyIdentifier,
172        _array_index: Option<u32>,
173        value: PropertyValue,
174        _priority: Option<u8>,
175    ) -> Result<(), Error> {
176        if property == PropertyIdentifier::LOG_ENABLE {
177            if let PropertyValue::Boolean(v) = value {
178                self.log_enable = v;
179                return Ok(());
180            }
181            return Err(common::invalid_data_type_error());
182        }
183        if property == PropertyIdentifier::LOG_INTERVAL {
184            if let PropertyValue::Unsigned(v) = value {
185                self.log_interval = common::u64_to_u32(v)?;
186                return Ok(());
187            }
188            return Err(common::invalid_data_type_error());
189        }
190        if property == PropertyIdentifier::STOP_WHEN_FULL {
191            if let PropertyValue::Boolean(v) = value {
192                self.stop_when_full = v;
193                return Ok(());
194            }
195            return Err(common::invalid_data_type_error());
196        }
197        if property == PropertyIdentifier::RECORD_COUNT {
198            // Writing 0 clears the buffer
199            if let PropertyValue::Unsigned(0) = value {
200                self.buffer.clear();
201                return Ok(());
202            }
203            return Err(common::invalid_data_type_error());
204        }
205        if let Some(result) =
206            common::write_out_of_service(&mut self.out_of_service, property, &value)
207        {
208            return result;
209        }
210        if let Some(result) = common::write_description(&mut self.description, property, &value) {
211            return result;
212        }
213        Err(common::write_access_denied_error())
214    }
215
216    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
217        static PROPS: &[PropertyIdentifier] = &[
218            PropertyIdentifier::OBJECT_IDENTIFIER,
219            PropertyIdentifier::OBJECT_NAME,
220            PropertyIdentifier::DESCRIPTION,
221            PropertyIdentifier::OBJECT_TYPE,
222            PropertyIdentifier::LOG_ENABLE,
223            PropertyIdentifier::LOG_INTERVAL,
224            PropertyIdentifier::STOP_WHEN_FULL,
225            PropertyIdentifier::BUFFER_SIZE,
226            PropertyIdentifier::LOG_BUFFER,
227            PropertyIdentifier::RECORD_COUNT,
228            PropertyIdentifier::TOTAL_RECORD_COUNT,
229            PropertyIdentifier::STATUS_FLAGS,
230            PropertyIdentifier::EVENT_STATE,
231            PropertyIdentifier::OUT_OF_SERVICE,
232            PropertyIdentifier::RELIABILITY,
233        ];
234        Cow::Borrowed(PROPS)
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use bacnet_types::constructed::LogDatum;
242    use bacnet_types::primitives::{Date, Time};
243
244    fn make_date() -> Date {
245        Date {
246            year: 124,
247            month: 3,
248            day: 15,
249            day_of_week: 5,
250        }
251    }
252
253    fn make_time(hour: u8) -> Time {
254        Time {
255            hour,
256            minute: 0,
257            second: 0,
258            hundredths: 0,
259        }
260    }
261
262    fn make_record(hour: u8, value: f32) -> BACnetLogRecord {
263        BACnetLogRecord {
264            date: make_date(),
265            time: make_time(hour),
266            log_datum: LogDatum::RealValue(value),
267            status_flags: None,
268        }
269    }
270
271    #[test]
272    fn create_event_log() {
273        let el = EventLogObject::new(1, "EL-1", 100).unwrap();
274        assert_eq!(el.object_identifier().object_type(), ObjectType::EVENT_LOG);
275        assert_eq!(el.object_identifier().instance_number(), 1);
276        assert_eq!(el.object_name(), "EL-1");
277    }
278
279    #[test]
280    fn read_object_type() {
281        let el = EventLogObject::new(1, "EL-1", 100).unwrap();
282        let val = el
283            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
284            .unwrap();
285        assert_eq!(
286            val,
287            PropertyValue::Enumerated(ObjectType::EVENT_LOG.to_raw())
288        );
289    }
290
291    #[test]
292    fn add_records_and_read_count() {
293        let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
294        el.add_record(make_record(10, 72.5));
295        el.add_record(make_record(11, 73.0));
296        assert_eq!(el.records().len(), 2);
297        let val = el
298            .read_property(PropertyIdentifier::RECORD_COUNT, None)
299            .unwrap();
300        assert_eq!(val, PropertyValue::Unsigned(2));
301        let val = el
302            .read_property(PropertyIdentifier::TOTAL_RECORD_COUNT, None)
303            .unwrap();
304        assert_eq!(val, PropertyValue::Unsigned(2));
305    }
306
307    #[test]
308    fn read_log_buffer() {
309        let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
310        el.add_record(make_record(10, 72.5));
311        el.add_record(make_record(11, 73.0));
312        let val = el
313            .read_property(PropertyIdentifier::LOG_BUFFER, None)
314            .unwrap();
315        if let PropertyValue::List(records) = val {
316            assert_eq!(records.len(), 2);
317            if let PropertyValue::List(fields) = &records[0] {
318                assert_eq!(fields.len(), 3);
319                assert_eq!(fields[2], PropertyValue::Real(72.5));
320            } else {
321                panic!("Expected List for log record");
322            }
323            if let PropertyValue::List(fields) = &records[1] {
324                assert_eq!(fields[2], PropertyValue::Real(73.0));
325            } else {
326                panic!("Expected List for log record");
327            }
328        } else {
329            panic!("Expected List for LOG_BUFFER");
330        }
331    }
332
333    #[test]
334    fn read_log_buffer_empty() {
335        let el = EventLogObject::new(1, "EL-1", 100).unwrap();
336        let val = el
337            .read_property(PropertyIdentifier::LOG_BUFFER, None)
338            .unwrap();
339        assert_eq!(val, PropertyValue::List(vec![]));
340    }
341
342    #[test]
343    fn ring_buffer_wraps() {
344        let mut el = EventLogObject::new(1, "EL-1", 3).unwrap();
345        for i in 0..5u8 {
346            el.add_record(BACnetLogRecord {
347                date: make_date(),
348                time: make_time(i),
349                log_datum: LogDatum::UnsignedValue(i as u64),
350                status_flags: None,
351            });
352        }
353        assert_eq!(el.records().len(), 3);
354        // Oldest records evicted; first remaining is hour=2
355        assert_eq!(el.records()[0].time.hour, 2);
356        let val = el
357            .read_property(PropertyIdentifier::TOTAL_RECORD_COUNT, None)
358            .unwrap();
359        assert_eq!(val, PropertyValue::Unsigned(5));
360    }
361
362    #[test]
363    fn stop_when_full() {
364        let mut el = EventLogObject::new(1, "EL-1", 2).unwrap();
365        el.write_property(
366            PropertyIdentifier::STOP_WHEN_FULL,
367            None,
368            PropertyValue::Boolean(true),
369            None,
370        )
371        .unwrap();
372        for i in 0..5u8 {
373            el.add_record(make_record(i, i as f32));
374        }
375        assert_eq!(el.records().len(), 2);
376        assert_eq!(el.total_record_count, 2); // Only 2 accepted
377    }
378
379    #[test]
380    fn disable_logging() {
381        let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
382        el.write_property(
383            PropertyIdentifier::LOG_ENABLE,
384            None,
385            PropertyValue::Boolean(false),
386            None,
387        )
388        .unwrap();
389        el.add_record(make_record(10, 72.5));
390        assert_eq!(el.records().len(), 0);
391    }
392
393    #[test]
394    fn clear_buffer_via_record_count() {
395        let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
396        el.add_record(make_record(10, 72.5));
397        assert_eq!(el.records().len(), 1);
398        el.write_property(
399            PropertyIdentifier::RECORD_COUNT,
400            None,
401            PropertyValue::Unsigned(0),
402            None,
403        )
404        .unwrap();
405        assert_eq!(el.records().len(), 0);
406    }
407
408    #[test]
409    fn read_event_state_default() {
410        let el = EventLogObject::new(1, "EL-1", 100).unwrap();
411        let val = el
412            .read_property(PropertyIdentifier::EVENT_STATE, None)
413            .unwrap();
414        assert_eq!(val, PropertyValue::Enumerated(0)); // normal
415    }
416
417    #[test]
418    fn property_list_complete() {
419        let el = EventLogObject::new(1, "EL-1", 100).unwrap();
420        let props = el.property_list();
421        assert!(props.contains(&PropertyIdentifier::LOG_ENABLE));
422        assert!(props.contains(&PropertyIdentifier::LOG_INTERVAL));
423        assert!(props.contains(&PropertyIdentifier::STOP_WHEN_FULL));
424        assert!(props.contains(&PropertyIdentifier::BUFFER_SIZE));
425        assert!(props.contains(&PropertyIdentifier::LOG_BUFFER));
426        assert!(props.contains(&PropertyIdentifier::RECORD_COUNT));
427        assert!(props.contains(&PropertyIdentifier::TOTAL_RECORD_COUNT));
428        assert!(props.contains(&PropertyIdentifier::STATUS_FLAGS));
429        assert!(props.contains(&PropertyIdentifier::EVENT_STATE));
430        assert!(props.contains(&PropertyIdentifier::OUT_OF_SERVICE));
431        assert!(props.contains(&PropertyIdentifier::RELIABILITY));
432    }
433
434    #[test]
435    fn write_log_interval() {
436        let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
437        el.write_property(
438            PropertyIdentifier::LOG_INTERVAL,
439            None,
440            PropertyValue::Unsigned(60),
441            None,
442        )
443        .unwrap();
444        let val = el
445            .read_property(PropertyIdentifier::LOG_INTERVAL, None)
446            .unwrap();
447        assert_eq!(val, PropertyValue::Unsigned(60));
448    }
449
450    #[test]
451    fn write_unknown_property_denied() {
452        let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
453        let result = el.write_property(
454            PropertyIdentifier::PRESENT_VALUE,
455            None,
456            PropertyValue::Real(1.0),
457            None,
458        );
459        assert!(result.is_err());
460    }
461
462    #[test]
463    fn log_buffer_various_datum_types() {
464        let mut el = EventLogObject::new(1, "EL-1", 100).unwrap();
465        let date = make_date();
466        let time = make_time(8);
467
468        el.add_record(BACnetLogRecord {
469            date,
470            time,
471            log_datum: LogDatum::BooleanValue(true),
472            status_flags: None,
473        });
474        el.add_record(BACnetLogRecord {
475            date,
476            time,
477            log_datum: LogDatum::EnumValue(42),
478            status_flags: Some(0b0100),
479        });
480        el.add_record(BACnetLogRecord {
481            date,
482            time,
483            log_datum: LogDatum::NullValue,
484            status_flags: None,
485        });
486
487        let val = el
488            .read_property(PropertyIdentifier::LOG_BUFFER, None)
489            .unwrap();
490        if let PropertyValue::List(records) = val {
491            assert_eq!(records.len(), 3);
492            if let PropertyValue::List(fields) = &records[0] {
493                assert_eq!(fields[2], PropertyValue::Boolean(true));
494            } else {
495                panic!("Expected List");
496            }
497            if let PropertyValue::List(fields) = &records[1] {
498                assert_eq!(fields[2], PropertyValue::Enumerated(42));
499            } else {
500                panic!("Expected List");
501            }
502            if let PropertyValue::List(fields) = &records[2] {
503                assert_eq!(fields[2], PropertyValue::Null);
504            } else {
505                panic!("Expected List");
506            }
507        } else {
508            panic!("Expected List for LOG_BUFFER");
509        }
510    }
511}