1use 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
18pub 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, 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 pub fn add_record(&mut self, record: BACnetLogRecord) {
62 let _ = self.try_add_record_internal(record);
63 }
64
65 pub fn records(&self) -> &VecDeque<BACnetLogRecord> {
67 self.log_buffer.records()
68 }
69
70 pub fn clear(&mut self) {
72 self.log_buffer.clear();
73 }
74
75 pub fn set_description(&mut self, desc: impl Into<String>) {
77 self.description = desc.into();
78 }
79
80 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 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 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
362pub 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, 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 pub fn add_record(&mut self, record: BACnetLogRecord) {
411 let _ = self.try_add_record_internal(record);
412 }
413
414 pub fn add_property_reference(&mut self, reference: BACnetDeviceObjectPropertyReference) {
416 self.log_device_object_property.push(reference);
417 }
418
419 pub fn records(&self) -> &VecDeque<BACnetLogRecord> {
421 self.log_buffer.records()
422 }
423
424 pub fn clear(&mut self) {
426 self.log_buffer.clear();
427 }
428
429 pub fn set_description(&mut self, desc: impl Into<String>) {
431 self.description = desc.into();
432 }
433
434 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;