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