1use 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
14pub 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, }
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 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 pub fn records(&self) -> &VecDeque<BACnetLogRecord> {
73 &self.buffer
74 }
75
76 pub fn clear(&mut self) {
78 self.buffer.clear();
79 }
80
81 pub fn set_description(&mut self, desc: impl Into<String>) {
83 self.description = desc.into();
84 }
85
86 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 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 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
334pub 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, 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 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 pub fn add_property_reference(&mut self, reference: BACnetDeviceObjectPropertyReference) {
398 self.log_device_object_property.push(reference);
399 }
400
401 pub fn records(&self) -> &VecDeque<BACnetLogRecord> {
403 &self.buffer
404 }
405
406 pub fn clear(&mut self) {
408 self.buffer.clear();
409 }
410
411 pub fn set_description(&mut self, desc: impl Into<String>) {
413 self.description = desc.into();
414 }
415
416 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 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 {
642 use super::*;
643 use bacnet_types::primitives::{Date, Time};
644
645 fn make_record(hour: u8, value: f32) -> BACnetLogRecord {
646 BACnetLogRecord {
647 date: Date {
648 year: 124,
649 month: 3,
650 day: 15,
651 day_of_week: 5,
652 },
653 time: Time {
654 hour,
655 minute: 0,
656 second: 0,
657 hundredths: 0,
658 },
659 log_datum: LogDatum::RealValue(value),
660 status_flags: None,
661 }
662 }
663
664 #[test]
665 fn trendlog_add_records() {
666 let mut tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
667 tl.add_record(make_record(10, 72.5));
668 tl.add_record(make_record(11, 73.0));
669 assert_eq!(tl.records().len(), 2);
670 let val = tl
671 .read_property(PropertyIdentifier::RECORD_COUNT, None)
672 .unwrap();
673 assert_eq!(val, PropertyValue::Unsigned(2));
674 let val = tl
675 .read_property(PropertyIdentifier::TOTAL_RECORD_COUNT, None)
676 .unwrap();
677 assert_eq!(val, PropertyValue::Unsigned(2));
678 }
679
680 #[test]
681 fn trendlog_ring_buffer_wraps() {
682 let mut tl = TrendLogObject::new(1, "TL-1", 3).unwrap();
683 for i in 0..5u8 {
684 tl.add_record(BACnetLogRecord {
685 date: Date {
686 year: 124,
687 month: 3,
688 day: 15,
689 day_of_week: 5,
690 },
691 time: Time {
692 hour: i,
693 minute: 0,
694 second: 0,
695 hundredths: 0,
696 },
697 log_datum: LogDatum::UnsignedValue(i as u64),
698 status_flags: None,
699 });
700 }
701 assert_eq!(tl.records().len(), 3);
702 assert_eq!(tl.records()[0].time.hour, 2);
704 let val = tl
705 .read_property(PropertyIdentifier::TOTAL_RECORD_COUNT, None)
706 .unwrap();
707 assert_eq!(val, PropertyValue::Unsigned(5));
708 }
709
710 #[test]
711 fn trendlog_stop_when_full() {
712 let mut tl = TrendLogObject::new(1, "TL-1", 2).unwrap();
713 tl.write_property(
714 PropertyIdentifier::STOP_WHEN_FULL,
715 None,
716 PropertyValue::Boolean(true),
717 None,
718 )
719 .unwrap();
720 for i in 0..5u8 {
721 tl.add_record(make_record(i, i as f32));
722 }
723 assert_eq!(tl.records().len(), 2);
724 assert_eq!(tl.total_record_count, 2); }
726
727 #[test]
728 fn trendlog_disable_logging() {
729 let mut tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
730 tl.write_property(
731 PropertyIdentifier::LOG_ENABLE,
732 None,
733 PropertyValue::Boolean(false),
734 None,
735 )
736 .unwrap();
737 tl.add_record(make_record(10, 72.5));
738 assert_eq!(tl.records().len(), 0);
739 }
740
741 #[test]
742 fn trendlog_clear_buffer() {
743 let mut tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
744 tl.add_record(make_record(10, 72.5));
745 assert_eq!(tl.records().len(), 1);
746 tl.write_property(
747 PropertyIdentifier::RECORD_COUNT,
748 None,
749 PropertyValue::Unsigned(0),
750 None,
751 )
752 .unwrap();
753 assert_eq!(tl.records().len(), 0);
754 }
755
756 #[test]
757 fn trendlog_read_object_type() {
758 let tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
759 let val = tl
760 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
761 .unwrap();
762 assert_eq!(
763 val,
764 PropertyValue::Enumerated(ObjectType::TREND_LOG.to_raw())
765 );
766 }
767
768 #[test]
769 fn trendlog_description_read_write() {
770 let mut tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
771 assert_eq!(
773 tl.read_property(PropertyIdentifier::DESCRIPTION, None)
774 .unwrap(),
775 PropertyValue::CharacterString(String::new())
776 );
777 tl.write_property(
778 PropertyIdentifier::DESCRIPTION,
779 None,
780 PropertyValue::CharacterString("Zone temperature trend".into()),
781 None,
782 )
783 .unwrap();
784 assert_eq!(
785 tl.read_property(PropertyIdentifier::DESCRIPTION, None)
786 .unwrap(),
787 PropertyValue::CharacterString("Zone temperature trend".into())
788 );
789 }
790
791 #[test]
792 fn trendlog_set_description_convenience() {
793 let mut tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
794 tl.set_description("Outdoor air temperature log");
795 assert_eq!(
796 tl.read_property(PropertyIdentifier::DESCRIPTION, None)
797 .unwrap(),
798 PropertyValue::CharacterString("Outdoor air temperature log".into())
799 );
800 }
801
802 #[test]
803 fn trendlog_description_in_property_list() {
804 let tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
805 assert!(tl
806 .property_list()
807 .contains(&PropertyIdentifier::DESCRIPTION));
808 }
809
810 #[test]
811 fn trendlog_read_log_buffer() {
812 let mut tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
813 tl.add_record(make_record(10, 72.5));
814 tl.add_record(make_record(11, 73.0));
815 let val = tl
816 .read_property(PropertyIdentifier::LOG_BUFFER, None)
817 .unwrap();
818 if let PropertyValue::List(records) = val {
819 assert_eq!(records.len(), 2);
820 if let PropertyValue::List(fields) = &records[0] {
822 assert_eq!(fields.len(), 3);
823 assert_eq!(fields[0], PropertyValue::Date(make_record(10, 72.5).date));
824 assert_eq!(fields[1], PropertyValue::Time(make_record(10, 72.5).time));
825 assert_eq!(fields[2], PropertyValue::Real(72.5));
826 } else {
827 panic!("Expected List for log record");
828 }
829 if let PropertyValue::List(fields) = &records[1] {
831 assert_eq!(fields[2], PropertyValue::Real(73.0));
832 } else {
833 panic!("Expected List for log record");
834 }
835 } else {
836 panic!("Expected List for LOG_BUFFER");
837 }
838 }
839
840 #[test]
841 fn trendlog_log_buffer_empty() {
842 let tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
843 let val = tl
844 .read_property(PropertyIdentifier::LOG_BUFFER, None)
845 .unwrap();
846 assert_eq!(val, PropertyValue::List(vec![]));
847 }
848
849 #[test]
850 fn trendlog_log_buffer_overflow_stop_when_full() {
851 let mut tl = TrendLogObject::new(1, "TL-1", 3).unwrap();
852 tl.write_property(
853 PropertyIdentifier::STOP_WHEN_FULL,
854 None,
855 PropertyValue::Boolean(true),
856 None,
857 )
858 .unwrap();
859 for i in 0..5u8 {
860 tl.add_record(make_record(i, i as f32 * 10.0));
861 }
862 let val = tl
864 .read_property(PropertyIdentifier::LOG_BUFFER, None)
865 .unwrap();
866 if let PropertyValue::List(records) = val {
867 assert_eq!(records.len(), 3);
868 if let PropertyValue::List(fields) = &records[0] {
869 assert_eq!(fields[2], PropertyValue::Real(0.0));
870 } else {
871 panic!("Expected List");
872 }
873 if let PropertyValue::List(fields) = &records[2] {
874 assert_eq!(fields[2], PropertyValue::Real(20.0));
875 } else {
876 panic!("Expected List");
877 }
878 } else {
879 panic!("Expected List for LOG_BUFFER");
880 }
881 }
882
883 #[test]
884 fn trendlog_read_logging_type() {
885 let tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
886 let val = tl
887 .read_property(PropertyIdentifier::LOGGING_TYPE, None)
888 .unwrap();
889 assert_eq!(val, PropertyValue::Enumerated(0));
891 }
892
893 #[test]
894 fn trendlog_set_logging_type() {
895 let mut tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
896 tl.set_logging_type(1); let val = tl
898 .read_property(PropertyIdentifier::LOGGING_TYPE, None)
899 .unwrap();
900 assert_eq!(val, PropertyValue::Enumerated(1));
901 }
902
903 #[test]
904 fn trendlog_log_buffer_in_property_list() {
905 let tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
906 let props = tl.property_list();
907 assert!(props.contains(&PropertyIdentifier::LOG_BUFFER));
908 assert!(props.contains(&PropertyIdentifier::LOGGING_TYPE));
909 assert!(props.contains(&PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY));
910 }
911
912 #[test]
913 fn trendlog_log_device_object_property_null_by_default() {
914 let tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
915 let val = tl
916 .read_property(PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY, None)
917 .unwrap();
918 assert_eq!(val, PropertyValue::Null);
919 }
920
921 #[test]
922 fn trendlog_log_buffer_various_datum_types() {
923 use bacnet_types::constructed::LogDatum;
924 let mut tl = TrendLogObject::new(1, "TL-1", 100).unwrap();
925
926 let date = Date {
927 year: 124,
928 month: 3,
929 day: 15,
930 day_of_week: 5,
931 };
932 let time = Time {
933 hour: 8,
934 minute: 0,
935 second: 0,
936 hundredths: 0,
937 };
938
939 tl.add_record(BACnetLogRecord {
940 date,
941 time,
942 log_datum: LogDatum::BooleanValue(true),
943 status_flags: None,
944 });
945 tl.add_record(BACnetLogRecord {
946 date,
947 time,
948 log_datum: LogDatum::EnumValue(42),
949 status_flags: Some(0b0100),
950 });
951 tl.add_record(BACnetLogRecord {
952 date,
953 time,
954 log_datum: LogDatum::NullValue,
955 status_flags: None,
956 });
957
958 let val = tl
959 .read_property(PropertyIdentifier::LOG_BUFFER, None)
960 .unwrap();
961 if let PropertyValue::List(records) = val {
962 assert_eq!(records.len(), 3);
963 if let PropertyValue::List(fields) = &records[0] {
964 assert_eq!(fields[2], PropertyValue::Boolean(true));
965 } else {
966 panic!("Expected List");
967 }
968 if let PropertyValue::List(fields) = &records[1] {
969 assert_eq!(fields[2], PropertyValue::Enumerated(42));
970 } else {
971 panic!("Expected List");
972 }
973 if let PropertyValue::List(fields) = &records[2] {
974 assert_eq!(fields[2], PropertyValue::Null);
975 } else {
976 panic!("Expected List");
977 }
978 } else {
979 panic!("Expected List for LOG_BUFFER");
980 }
981 }
982
983 #[test]
988 fn trendlog_multiple_create() {
989 let tlm = TrendLogMultipleObject::new(1, "TLM-1", 200).unwrap();
990 assert_eq!(
991 tlm.read_property(PropertyIdentifier::OBJECT_NAME, None)
992 .unwrap(),
993 PropertyValue::CharacterString("TLM-1".into())
994 );
995 assert_eq!(
996 tlm.read_property(PropertyIdentifier::OBJECT_TYPE, None)
997 .unwrap(),
998 PropertyValue::Enumerated(ObjectType::TREND_LOG_MULTIPLE.to_raw())
999 );
1000 assert_eq!(
1001 tlm.read_property(PropertyIdentifier::BUFFER_SIZE, None)
1002 .unwrap(),
1003 PropertyValue::Unsigned(200)
1004 );
1005 }
1006
1007 #[test]
1008 fn trendlog_multiple_add_records() {
1009 let mut tlm = TrendLogMultipleObject::new(1, "TLM-1", 100).unwrap();
1010 tlm.add_record(make_record(10, 72.5));
1011 tlm.add_record(make_record(11, 73.0));
1012 assert_eq!(tlm.records().len(), 2);
1013 assert_eq!(
1014 tlm.read_property(PropertyIdentifier::RECORD_COUNT, None)
1015 .unwrap(),
1016 PropertyValue::Unsigned(2)
1017 );
1018 assert_eq!(
1019 tlm.read_property(PropertyIdentifier::TOTAL_RECORD_COUNT, None)
1020 .unwrap(),
1021 PropertyValue::Unsigned(2)
1022 );
1023 }
1024
1025 #[test]
1026 fn trendlog_multiple_ring_buffer() {
1027 let mut tlm = TrendLogMultipleObject::new(1, "TLM-1", 3).unwrap();
1028 for i in 0..5u8 {
1029 tlm.add_record(BACnetLogRecord {
1030 date: Date {
1031 year: 124,
1032 month: 3,
1033 day: 15,
1034 day_of_week: 5,
1035 },
1036 time: Time {
1037 hour: i,
1038 minute: 0,
1039 second: 0,
1040 hundredths: 0,
1041 },
1042 log_datum: LogDatum::UnsignedValue(i as u64),
1043 status_flags: None,
1044 });
1045 }
1046 assert_eq!(tlm.records().len(), 3);
1047 assert_eq!(tlm.records()[0].time.hour, 2);
1048 assert_eq!(
1049 tlm.read_property(PropertyIdentifier::TOTAL_RECORD_COUNT, None)
1050 .unwrap(),
1051 PropertyValue::Unsigned(5)
1052 );
1053 }
1054
1055 #[test]
1056 fn trendlog_multiple_read_log_buffer() {
1057 let mut tlm = TrendLogMultipleObject::new(1, "TLM-1", 100).unwrap();
1058 tlm.add_record(make_record(10, 72.5));
1059 let val = tlm
1060 .read_property(PropertyIdentifier::LOG_BUFFER, None)
1061 .unwrap();
1062 if let PropertyValue::List(records) = val {
1063 assert_eq!(records.len(), 1);
1064 if let PropertyValue::List(fields) = &records[0] {
1065 assert_eq!(fields[2], PropertyValue::Real(72.5));
1066 } else {
1067 panic!("Expected List for log record");
1068 }
1069 } else {
1070 panic!("Expected List for LOG_BUFFER");
1071 }
1072 }
1073
1074 #[test]
1075 fn trendlog_multiple_property_list() {
1076 let tlm = TrendLogMultipleObject::new(1, "TLM-1", 100).unwrap();
1077 let props = tlm.property_list();
1078 assert!(props.contains(&PropertyIdentifier::LOG_BUFFER));
1079 assert!(props.contains(&PropertyIdentifier::LOGGING_TYPE));
1080 assert!(props.contains(&PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY));
1081 assert!(props.contains(&PropertyIdentifier::OUT_OF_SERVICE));
1082 assert!(props.contains(&PropertyIdentifier::RELIABILITY));
1083 }
1084
1085 #[test]
1086 fn trendlog_multiple_add_property_references() {
1087 let mut tlm = TrendLogMultipleObject::new(1, "TLM-1", 100).unwrap();
1088
1089 let oid1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
1090 let oid2 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 2).unwrap();
1091 let pv_raw = PropertyIdentifier::PRESENT_VALUE.to_raw();
1092
1093 tlm.add_property_reference(BACnetDeviceObjectPropertyReference {
1094 object_identifier: oid1,
1095 property_identifier: pv_raw,
1096 property_array_index: None,
1097 device_identifier: None,
1098 });
1099 tlm.add_property_reference(BACnetDeviceObjectPropertyReference {
1100 object_identifier: oid2,
1101 property_identifier: pv_raw,
1102 property_array_index: Some(3),
1103 device_identifier: None,
1104 });
1105
1106 let val = tlm
1107 .read_property(PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY, None)
1108 .unwrap();
1109 if let PropertyValue::List(refs) = val {
1110 assert_eq!(refs.len(), 2);
1111 if let PropertyValue::List(fields) = &refs[0] {
1113 assert_eq!(fields[0], PropertyValue::ObjectIdentifier(oid1));
1114 assert_eq!(fields[1], PropertyValue::Unsigned(pv_raw as u64));
1115 assert_eq!(fields[2], PropertyValue::Null);
1116 assert_eq!(fields[3], PropertyValue::Null);
1117 } else {
1118 panic!("Expected List for property reference");
1119 }
1120 if let PropertyValue::List(fields) = &refs[1] {
1122 assert_eq!(fields[0], PropertyValue::ObjectIdentifier(oid2));
1123 assert_eq!(fields[1], PropertyValue::Unsigned(pv_raw as u64));
1124 assert_eq!(fields[2], PropertyValue::Unsigned(3));
1125 assert_eq!(fields[3], PropertyValue::Null);
1126 } else {
1127 panic!("Expected List for property reference");
1128 }
1129 } else {
1130 panic!("Expected List for LOG_DEVICE_OBJECT_PROPERTY");
1131 }
1132 }
1133
1134 #[test]
1135 fn trendlog_multiple_empty_property_references() {
1136 let tlm = TrendLogMultipleObject::new(1, "TLM-1", 100).unwrap();
1137 let val = tlm
1138 .read_property(PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY, None)
1139 .unwrap();
1140 assert_eq!(val, PropertyValue::List(vec![]));
1141 }
1142
1143 #[test]
1144 fn trendlog_multiple_write_log_enable() {
1145 let mut tlm = TrendLogMultipleObject::new(1, "TLM-1", 100).unwrap();
1146 tlm.write_property(
1147 PropertyIdentifier::LOG_ENABLE,
1148 None,
1149 PropertyValue::Boolean(false),
1150 None,
1151 )
1152 .unwrap();
1153 assert_eq!(
1154 tlm.read_property(PropertyIdentifier::LOG_ENABLE, None)
1155 .unwrap(),
1156 PropertyValue::Boolean(false)
1157 );
1158 tlm.add_record(make_record(10, 72.5));
1160 assert_eq!(tlm.records().len(), 0);
1161 }
1162}