Skip to main content

bacnet_objects/trend/
mod.rs

1//! TrendLog (type 20) and TrendLogMultiple (type 27) objects per ASHRAE 135-2020.
2
3use std::borrow::Cow;
4use std::collections::VecDeque;
5
6use bacnet_types::constructed::{BACnetDeviceObjectPropertyReference, BACnetLogRecord, LogDatum};
7use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
10
11use crate::common::{self, read_property_list_property};
12use crate::traits::BACnetObject;
13
14/// BACnet TrendLog object.
15///
16/// Ring buffer of timestamped property values. The application calls
17/// `add_record()` to log values at `log_interval` intervals.
18pub struct TrendLogObject {
19    oid: ObjectIdentifier,
20    name: String,
21    description: String,
22    log_enable: bool,
23    log_interval: u32,
24    stop_when_full: bool,
25    buffer_size: u32,
26    buffer: VecDeque<BACnetLogRecord>,
27    total_record_count: u64,
28    out_of_service: bool,
29    reliability: u32,
30    status_flags: StatusFlags,
31    log_device_object_property: Option<BACnetDeviceObjectPropertyReference>,
32    logging_type: u32, // 0=polled, 1=cov, 2=triggered
33}
34
35impl TrendLogObject {
36    pub fn new(instance: u32, name: impl Into<String>, buffer_size: u32) -> Result<Self, Error> {
37        let oid = ObjectIdentifier::new(ObjectType::TREND_LOG, instance)?;
38        Ok(Self {
39            oid,
40            name: name.into(),
41            description: String::new(),
42            log_enable: true,
43            log_interval: 0,
44            stop_when_full: false,
45            buffer_size,
46            buffer: VecDeque::new(),
47            total_record_count: 0,
48            out_of_service: false,
49            reliability: 0,
50            status_flags: StatusFlags::empty(),
51            log_device_object_property: None,
52            logging_type: 0,
53        })
54    }
55
56    /// Add a BACnetLogRecord to the trend 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    /// Set the log device object property reference.
87    pub fn set_log_device_object_property(
88        &mut self,
89        reference: Option<BACnetDeviceObjectPropertyReference>,
90    ) {
91        self.log_device_object_property = reference;
92    }
93
94    /// Set the logging type (0=polled, 1=cov, 2=triggered).
95    pub fn set_logging_type(&mut self, logging_type: u32) {
96        self.logging_type = logging_type;
97    }
98}
99
100impl BACnetObject for TrendLogObject {
101    fn object_identifier(&self) -> ObjectIdentifier {
102        self.oid
103    }
104
105    fn object_name(&self) -> &str {
106        &self.name
107    }
108
109    fn read_property(
110        &self,
111        property: PropertyIdentifier,
112        array_index: Option<u32>,
113    ) -> Result<PropertyValue, Error> {
114        match property {
115            p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
116                Ok(PropertyValue::ObjectIdentifier(self.oid))
117            }
118            p if p == PropertyIdentifier::OBJECT_NAME => {
119                Ok(PropertyValue::CharacterString(self.name.clone()))
120            }
121            p if p == PropertyIdentifier::DESCRIPTION => {
122                Ok(PropertyValue::CharacterString(self.description.clone()))
123            }
124            p if p == PropertyIdentifier::OBJECT_TYPE => {
125                Ok(PropertyValue::Enumerated(ObjectType::TREND_LOG.to_raw()))
126            }
127            p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
128            p if p == PropertyIdentifier::LOG_INTERVAL => {
129                Ok(PropertyValue::Unsigned(self.log_interval as u64))
130            }
131            p if p == PropertyIdentifier::STOP_WHEN_FULL => {
132                Ok(PropertyValue::Boolean(self.stop_when_full))
133            }
134            p if p == PropertyIdentifier::BUFFER_SIZE => {
135                Ok(PropertyValue::Unsigned(self.buffer_size as u64))
136            }
137            p if p == PropertyIdentifier::RECORD_COUNT => {
138                Ok(PropertyValue::Unsigned(self.buffer.len() as u64))
139            }
140            p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => {
141                Ok(PropertyValue::Unsigned(self.total_record_count))
142            }
143            p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
144                unused_bits: 4,
145                data: vec![self.status_flags.bits() << 4],
146            }),
147            p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
148            p if p == PropertyIdentifier::RELIABILITY => {
149                Ok(PropertyValue::Enumerated(self.reliability))
150            }
151            p if p == PropertyIdentifier::OUT_OF_SERVICE => {
152                Ok(PropertyValue::Boolean(self.out_of_service))
153            }
154            p if p == PropertyIdentifier::LOG_BUFFER => {
155                let records = self
156                    .buffer
157                    .iter()
158                    .map(|record| {
159                        let datum_value = match &record.log_datum {
160                            LogDatum::LogStatus(v) => PropertyValue::Unsigned(*v as u64),
161                            LogDatum::BooleanValue(v) => PropertyValue::Boolean(*v),
162                            LogDatum::RealValue(v) => PropertyValue::Real(*v),
163                            LogDatum::EnumValue(v) => PropertyValue::Enumerated(*v),
164                            LogDatum::UnsignedValue(v) => PropertyValue::Unsigned(*v),
165                            LogDatum::SignedValue(v) => PropertyValue::Signed(*v as i32),
166                            LogDatum::BitstringValue { unused_bits, data } => {
167                                PropertyValue::BitString {
168                                    unused_bits: *unused_bits,
169                                    data: data.clone(),
170                                }
171                            }
172                            LogDatum::NullValue => PropertyValue::Null,
173                            LogDatum::Failure {
174                                error_class,
175                                error_code,
176                            } => PropertyValue::List(vec![
177                                PropertyValue::Unsigned(*error_class as u64),
178                                PropertyValue::Unsigned(*error_code as u64),
179                            ]),
180                            LogDatum::TimeChange(v) => PropertyValue::Real(*v),
181                            LogDatum::AnyValue(bytes) => PropertyValue::OctetString(bytes.clone()),
182                        };
183                        PropertyValue::List(vec![
184                            PropertyValue::Date(record.date),
185                            PropertyValue::Time(record.time),
186                            datum_value,
187                        ])
188                    })
189                    .collect();
190                Ok(PropertyValue::List(records))
191            }
192            p if p == PropertyIdentifier::LOGGING_TYPE => {
193                Ok(PropertyValue::Enumerated(self.logging_type))
194            }
195            p if p == PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY => {
196                match &self.log_device_object_property {
197                    None => Ok(PropertyValue::Null),
198                    Some(r) => Ok(PropertyValue::List(vec![
199                        PropertyValue::ObjectIdentifier(r.object_identifier),
200                        PropertyValue::Unsigned(r.property_identifier as u64),
201                        match r.property_array_index {
202                            Some(idx) => PropertyValue::Unsigned(idx as u64),
203                            None => PropertyValue::Null,
204                        },
205                        match r.device_identifier {
206                            Some(dev) => PropertyValue::ObjectIdentifier(dev),
207                            None => PropertyValue::Null,
208                        },
209                    ])),
210                }
211            }
212            p if p == PropertyIdentifier::PROPERTY_LIST => {
213                read_property_list_property(&self.property_list(), array_index)
214            }
215            _ => Err(Error::Protocol {
216                class: ErrorClass::PROPERTY.to_raw() as u32,
217                code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
218            }),
219        }
220    }
221
222    fn write_property(
223        &mut self,
224        property: PropertyIdentifier,
225        _array_index: Option<u32>,
226        value: PropertyValue,
227        _priority: Option<u8>,
228    ) -> Result<(), Error> {
229        if property == PropertyIdentifier::LOG_ENABLE {
230            if let PropertyValue::Boolean(v) = value {
231                self.log_enable = v;
232                return Ok(());
233            }
234            return Err(Error::Protocol {
235                class: ErrorClass::PROPERTY.to_raw() as u32,
236                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
237            });
238        }
239        if property == PropertyIdentifier::LOG_INTERVAL {
240            if let PropertyValue::Unsigned(v) = value {
241                self.log_interval = common::u64_to_u32(v)?;
242                return Ok(());
243            }
244            return Err(Error::Protocol {
245                class: ErrorClass::PROPERTY.to_raw() as u32,
246                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
247            });
248        }
249        if property == PropertyIdentifier::STOP_WHEN_FULL {
250            if let PropertyValue::Boolean(v) = value {
251                self.stop_when_full = v;
252                return Ok(());
253            }
254            return Err(Error::Protocol {
255                class: ErrorClass::PROPERTY.to_raw() as u32,
256                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
257            });
258        }
259        if property == PropertyIdentifier::RECORD_COUNT {
260            // Writing 0 clears the buffer
261            if let PropertyValue::Unsigned(0) = value {
262                self.buffer.clear();
263                return Ok(());
264            }
265            return Err(Error::Protocol {
266                class: ErrorClass::PROPERTY.to_raw() as u32,
267                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
268            });
269        }
270        if property == PropertyIdentifier::RELIABILITY {
271            if let PropertyValue::Enumerated(v) = value {
272                self.reliability = v;
273                return Ok(());
274            }
275            return Err(Error::Protocol {
276                class: ErrorClass::PROPERTY.to_raw() as u32,
277                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
278            });
279        }
280        if property == PropertyIdentifier::OUT_OF_SERVICE {
281            if let PropertyValue::Boolean(v) = value {
282                self.out_of_service = v;
283                return Ok(());
284            }
285            return Err(Error::Protocol {
286                class: ErrorClass::PROPERTY.to_raw() as u32,
287                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
288            });
289        }
290        if property == PropertyIdentifier::DESCRIPTION {
291            if let PropertyValue::CharacterString(s) = value {
292                self.description = s;
293                return Ok(());
294            }
295            return Err(Error::Protocol {
296                class: ErrorClass::PROPERTY.to_raw() as u32,
297                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
298            });
299        }
300        Err(Error::Protocol {
301            class: ErrorClass::PROPERTY.to_raw() as u32,
302            code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
303        })
304    }
305
306    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
307        static PROPS: &[PropertyIdentifier] = &[
308            PropertyIdentifier::OBJECT_IDENTIFIER,
309            PropertyIdentifier::OBJECT_NAME,
310            PropertyIdentifier::DESCRIPTION,
311            PropertyIdentifier::OBJECT_TYPE,
312            PropertyIdentifier::LOG_ENABLE,
313            PropertyIdentifier::LOG_INTERVAL,
314            PropertyIdentifier::STOP_WHEN_FULL,
315            PropertyIdentifier::BUFFER_SIZE,
316            PropertyIdentifier::LOG_BUFFER,
317            PropertyIdentifier::RECORD_COUNT,
318            PropertyIdentifier::TOTAL_RECORD_COUNT,
319            PropertyIdentifier::STATUS_FLAGS,
320            PropertyIdentifier::EVENT_STATE,
321            PropertyIdentifier::RELIABILITY,
322            PropertyIdentifier::OUT_OF_SERVICE,
323            PropertyIdentifier::LOGGING_TYPE,
324            PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY,
325        ];
326        Cow::Borrowed(PROPS)
327    }
328
329    fn add_trend_record(&mut self, record: BACnetLogRecord) {
330        self.add_record(record);
331    }
332}
333
334// ---------------------------------------------------------------------------
335// TrendLogMultiple (type 27)
336// ---------------------------------------------------------------------------
337
338/// BACnet TrendLogMultiple object (type 27).
339///
340/// Multi-channel trending. Logs values from multiple properties simultaneously.
341/// Unlike TrendLog which monitors a single property, TrendLogMultiple monitors
342/// a list of device-object-property references per record.
343pub struct TrendLogMultipleObject {
344    oid: ObjectIdentifier,
345    name: String,
346    description: String,
347    log_enable: bool,
348    log_interval: u32,
349    stop_when_full: bool,
350    buffer_size: u32,
351    buffer: VecDeque<BACnetLogRecord>,
352    total_record_count: u64,
353    status_flags: StatusFlags,
354    log_device_object_property: Vec<BACnetDeviceObjectPropertyReference>,
355    logging_type: u32, // 0=polled, 1=cov, 2=triggered
356    out_of_service: bool,
357    reliability: u32,
358}
359
360impl TrendLogMultipleObject {
361    pub fn new(instance: u32, name: impl Into<String>, buffer_size: u32) -> Result<Self, Error> {
362        let oid = ObjectIdentifier::new(ObjectType::TREND_LOG_MULTIPLE, instance)?;
363        Ok(Self {
364            oid,
365            name: name.into(),
366            description: String::new(),
367            log_enable: true,
368            log_interval: 0,
369            stop_when_full: false,
370            buffer_size,
371            buffer: VecDeque::new(),
372            total_record_count: 0,
373            status_flags: StatusFlags::empty(),
374            log_device_object_property: Vec::new(),
375            logging_type: 0,
376            out_of_service: false,
377            reliability: 0,
378        })
379    }
380
381    /// Add a BACnetLogRecord to the trend log buffer.
382    pub fn add_record(&mut self, record: BACnetLogRecord) {
383        if !self.log_enable {
384            return;
385        }
386        if self.buffer.len() >= self.buffer_size as usize {
387            if self.stop_when_full {
388                return;
389            }
390            self.buffer.pop_front();
391        }
392        self.buffer.push_back(record);
393        self.total_record_count += 1;
394    }
395
396    /// Add a property reference to the monitored list.
397    pub fn add_property_reference(&mut self, reference: BACnetDeviceObjectPropertyReference) {
398        self.log_device_object_property.push(reference);
399    }
400
401    /// Get the current buffer contents.
402    pub fn records(&self) -> &VecDeque<BACnetLogRecord> {
403        &self.buffer
404    }
405
406    /// Clear the buffer.
407    pub fn clear(&mut self) {
408        self.buffer.clear();
409    }
410
411    /// Set the description string.
412    pub fn set_description(&mut self, desc: impl Into<String>) {
413        self.description = desc.into();
414    }
415
416    /// Set the logging type (0=polled, 1=cov, 2=triggered).
417    pub fn set_logging_type(&mut self, logging_type: u32) {
418        self.logging_type = logging_type;
419    }
420}
421
422impl BACnetObject for TrendLogMultipleObject {
423    fn object_identifier(&self) -> ObjectIdentifier {
424        self.oid
425    }
426
427    fn object_name(&self) -> &str {
428        &self.name
429    }
430
431    fn read_property(
432        &self,
433        property: PropertyIdentifier,
434        array_index: Option<u32>,
435    ) -> Result<PropertyValue, Error> {
436        match property {
437            p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
438                Ok(PropertyValue::ObjectIdentifier(self.oid))
439            }
440            p if p == PropertyIdentifier::OBJECT_NAME => {
441                Ok(PropertyValue::CharacterString(self.name.clone()))
442            }
443            p if p == PropertyIdentifier::DESCRIPTION => {
444                Ok(PropertyValue::CharacterString(self.description.clone()))
445            }
446            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
447                ObjectType::TREND_LOG_MULTIPLE.to_raw(),
448            )),
449            p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
450            p if p == PropertyIdentifier::LOG_INTERVAL => {
451                Ok(PropertyValue::Unsigned(self.log_interval as u64))
452            }
453            p if p == PropertyIdentifier::STOP_WHEN_FULL => {
454                Ok(PropertyValue::Boolean(self.stop_when_full))
455            }
456            p if p == PropertyIdentifier::BUFFER_SIZE => {
457                Ok(PropertyValue::Unsigned(self.buffer_size as u64))
458            }
459            p if p == PropertyIdentifier::RECORD_COUNT => {
460                Ok(PropertyValue::Unsigned(self.buffer.len() as u64))
461            }
462            p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => {
463                Ok(PropertyValue::Unsigned(self.total_record_count))
464            }
465            p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
466                unused_bits: 4,
467                data: vec![self.status_flags.bits() << 4],
468            }),
469            p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
470            p if p == PropertyIdentifier::OUT_OF_SERVICE => {
471                Ok(PropertyValue::Boolean(self.out_of_service))
472            }
473            p if p == PropertyIdentifier::RELIABILITY => {
474                Ok(PropertyValue::Enumerated(self.reliability))
475            }
476            p if p == PropertyIdentifier::LOG_BUFFER => {
477                let records = self
478                    .buffer
479                    .iter()
480                    .map(|record| {
481                        let datum_value = match &record.log_datum {
482                            LogDatum::LogStatus(v) => PropertyValue::Unsigned(*v as u64),
483                            LogDatum::BooleanValue(v) => PropertyValue::Boolean(*v),
484                            LogDatum::RealValue(v) => PropertyValue::Real(*v),
485                            LogDatum::EnumValue(v) => PropertyValue::Enumerated(*v),
486                            LogDatum::UnsignedValue(v) => PropertyValue::Unsigned(*v),
487                            LogDatum::SignedValue(v) => PropertyValue::Signed(*v as i32),
488                            LogDatum::BitstringValue { unused_bits, data } => {
489                                PropertyValue::BitString {
490                                    unused_bits: *unused_bits,
491                                    data: data.clone(),
492                                }
493                            }
494                            LogDatum::NullValue => PropertyValue::Null,
495                            LogDatum::Failure {
496                                error_class,
497                                error_code,
498                            } => PropertyValue::List(vec![
499                                PropertyValue::Unsigned(*error_class as u64),
500                                PropertyValue::Unsigned(*error_code as u64),
501                            ]),
502                            LogDatum::TimeChange(v) => PropertyValue::Real(*v),
503                            LogDatum::AnyValue(bytes) => PropertyValue::OctetString(bytes.clone()),
504                        };
505                        PropertyValue::List(vec![
506                            PropertyValue::Date(record.date),
507                            PropertyValue::Time(record.time),
508                            datum_value,
509                        ])
510                    })
511                    .collect();
512                Ok(PropertyValue::List(records))
513            }
514            p if p == PropertyIdentifier::LOGGING_TYPE => {
515                Ok(PropertyValue::Enumerated(self.logging_type))
516            }
517            p if p == PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY => {
518                let refs: Vec<PropertyValue> = self
519                    .log_device_object_property
520                    .iter()
521                    .map(|r| {
522                        PropertyValue::List(vec![
523                            PropertyValue::ObjectIdentifier(r.object_identifier),
524                            PropertyValue::Unsigned(r.property_identifier as u64),
525                            match r.property_array_index {
526                                Some(idx) => PropertyValue::Unsigned(idx as u64),
527                                None => PropertyValue::Null,
528                            },
529                            match r.device_identifier {
530                                Some(dev) => PropertyValue::ObjectIdentifier(dev),
531                                None => PropertyValue::Null,
532                            },
533                        ])
534                    })
535                    .collect();
536                Ok(PropertyValue::List(refs))
537            }
538            p if p == PropertyIdentifier::PROPERTY_LIST => {
539                read_property_list_property(&self.property_list(), array_index)
540            }
541            _ => Err(Error::Protocol {
542                class: ErrorClass::PROPERTY.to_raw() as u32,
543                code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
544            }),
545        }
546    }
547
548    fn write_property(
549        &mut self,
550        property: PropertyIdentifier,
551        _array_index: Option<u32>,
552        value: PropertyValue,
553        _priority: Option<u8>,
554    ) -> Result<(), Error> {
555        if property == PropertyIdentifier::LOG_ENABLE {
556            if let PropertyValue::Boolean(v) = value {
557                self.log_enable = v;
558                return Ok(());
559            }
560            return Err(Error::Protocol {
561                class: ErrorClass::PROPERTY.to_raw() as u32,
562                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
563            });
564        }
565        if property == PropertyIdentifier::LOG_INTERVAL {
566            if let PropertyValue::Unsigned(v) = value {
567                self.log_interval = common::u64_to_u32(v)?;
568                return Ok(());
569            }
570            return Err(Error::Protocol {
571                class: ErrorClass::PROPERTY.to_raw() as u32,
572                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
573            });
574        }
575        if property == PropertyIdentifier::STOP_WHEN_FULL {
576            if let PropertyValue::Boolean(v) = value {
577                self.stop_when_full = v;
578                return Ok(());
579            }
580            return Err(Error::Protocol {
581                class: ErrorClass::PROPERTY.to_raw() as u32,
582                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
583            });
584        }
585        if property == PropertyIdentifier::RECORD_COUNT {
586            // Writing 0 clears the buffer
587            if let PropertyValue::Unsigned(0) = value {
588                self.buffer.clear();
589                return Ok(());
590            }
591            return Err(Error::Protocol {
592                class: ErrorClass::PROPERTY.to_raw() as u32,
593                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
594            });
595        }
596        if property == PropertyIdentifier::DESCRIPTION {
597            if let PropertyValue::CharacterString(s) = value {
598                self.description = s;
599                return Ok(());
600            }
601            return Err(Error::Protocol {
602                class: ErrorClass::PROPERTY.to_raw() as u32,
603                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
604            });
605        }
606        Err(Error::Protocol {
607            class: ErrorClass::PROPERTY.to_raw() as u32,
608            code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
609        })
610    }
611
612    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
613        static PROPS: &[PropertyIdentifier] = &[
614            PropertyIdentifier::OBJECT_IDENTIFIER,
615            PropertyIdentifier::OBJECT_NAME,
616            PropertyIdentifier::DESCRIPTION,
617            PropertyIdentifier::OBJECT_TYPE,
618            PropertyIdentifier::LOG_ENABLE,
619            PropertyIdentifier::LOG_INTERVAL,
620            PropertyIdentifier::STOP_WHEN_FULL,
621            PropertyIdentifier::BUFFER_SIZE,
622            PropertyIdentifier::LOG_BUFFER,
623            PropertyIdentifier::RECORD_COUNT,
624            PropertyIdentifier::TOTAL_RECORD_COUNT,
625            PropertyIdentifier::STATUS_FLAGS,
626            PropertyIdentifier::EVENT_STATE,
627            PropertyIdentifier::OUT_OF_SERVICE,
628            PropertyIdentifier::RELIABILITY,
629            PropertyIdentifier::LOGGING_TYPE,
630            PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY,
631        ];
632        Cow::Borrowed(PROPS)
633    }
634
635    fn add_trend_record(&mut self, record: BACnetLogRecord) {
636        self.add_record(record);
637    }
638}
639
640#[cfg(test)]
641mod tests;