use super::*;
use crate::clock::ClockFrame;
use bacnet_types::constructed::LogDatum;
use bacnet_types::primitives::{Date, Time};
fn read(log: &TrendLogObject, property: PropertyIdentifier) -> PropertyValue {
log.read_property(property, None).unwrap()
}
fn write(
log: &mut TrendLogObject,
property: PropertyIdentifier,
value: PropertyValue,
) -> Result<(), Error> {
log.write_property(property, None, value, None)
}
fn assert_refused(result: Result<(), Error>, class: ErrorClass, code: ErrorCode) {
assert!(
matches!(result, Err(Error::Protocol { class: c, code: e })
if c == class.to_raw() as u32 && e == code.to_raw() as u32),
"expected {class:?} / {code:?}, got {result:?}"
);
}
fn interval(log: &TrendLogObject) -> PropertyValue {
read(log, PropertyIdentifier::LOG_INTERVAL)
}
fn record() -> BACnetLogRecord {
BACnetLogRecord {
date: Date {
year: 126,
month: 10,
day: 4,
day_of_week: 7,
},
time: Time {
hour: 9,
minute: 0,
second: 0,
hundredths: 0,
},
log_datum: LogDatum::RealValue(1.5),
status_flags: None,
}
}
#[test]
fn logging_type_takes_polled_or_triggered_and_refuses_cov_for_now() {
let mut log = TrendLogObject::new(1, "TL-1", 8).unwrap();
log.set_log_interval(500).unwrap();
for raw in [LoggingType::COV.to_raw(), 3, 255] {
assert_refused(
write(
&mut log,
PropertyIdentifier::LOGGING_TYPE,
PropertyValue::Enumerated(raw),
),
ErrorClass::PROPERTY,
ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
);
assert_refused(
log.set_logging_type(LoggingType::from_raw(raw)),
ErrorClass::PROPERTY,
ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
);
}
assert_refused(
write(
&mut log,
PropertyIdentifier::LOGGING_TYPE,
PropertyValue::Unsigned(2),
),
ErrorClass::PROPERTY,
ErrorCode::INVALID_DATA_TYPE,
);
assert_eq!(
read(&log, PropertyIdentifier::LOGGING_TYPE),
PropertyValue::Enumerated(0)
);
assert_eq!(interval(&log), PropertyValue::Unsigned(500));
write(
&mut log,
PropertyIdentifier::LOGGING_TYPE,
PropertyValue::Enumerated(2),
)
.unwrap();
assert_eq!(interval(&log), PropertyValue::Unsigned(0));
assert!(!log.is_writable_property(PropertyIdentifier::LOG_INTERVAL));
for value in [PropertyValue::Unsigned(500), PropertyValue::Null] {
assert_refused(
write(&mut log, PropertyIdentifier::LOG_INTERVAL, value),
ErrorClass::PROPERTY,
ErrorCode::WRITE_ACCESS_DENIED,
);
}
assert_refused(
log.set_log_interval(500),
ErrorClass::PROPERTY,
ErrorCode::WRITE_ACCESS_DENIED,
);
log.set_logging_type(LoggingType::POLLED).unwrap();
assert!(log.is_writable_property(PropertyIdentifier::LOG_INTERVAL));
assert_eq!(
interval(&log),
PropertyValue::Unsigned(DEFAULT_LOG_INTERVAL.into())
);
log.set_log_interval(250).unwrap();
log.set_logging_type(LoggingType::POLLED).unwrap();
assert_eq!(interval(&log), PropertyValue::Unsigned(250));
}
#[test]
fn a_polled_interval_written_to_zero_is_a_refused_switch_to_cov() {
let mut log = TrendLogObject::new(1, "TL-1", 8).unwrap();
write(
&mut log,
PropertyIdentifier::LOG_INTERVAL,
PropertyValue::Unsigned(0),
)
.unwrap();
log.set_log_interval(0).unwrap();
write(
&mut log,
PropertyIdentifier::LOG_INTERVAL,
PropertyValue::Unsigned(300),
)
.unwrap();
assert_refused(
write(
&mut log,
PropertyIdentifier::LOG_INTERVAL,
PropertyValue::Unsigned(0),
),
ErrorClass::PROPERTY,
ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
);
assert_refused(
log.set_log_interval(0),
ErrorClass::PROPERTY,
ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
);
assert_eq!(interval(&log), PropertyValue::Unsigned(300));
assert_eq!(
read(&log, PropertyIdentifier::LOGGING_TYPE),
PropertyValue::Enumerated(0)
);
log.set_log_interval(1).unwrap();
assert_eq!(interval(&log), PropertyValue::Unsigned(1));
for (value, code) in [
(
PropertyValue::Unsigned(u64::from(u32::MAX) + 1),
ErrorCode::VALUE_OUT_OF_RANGE,
),
(PropertyValue::Boolean(true), ErrorCode::INVALID_DATA_TYPE),
] {
assert_refused(
write(&mut log, PropertyIdentifier::LOG_INTERVAL, value),
ErrorClass::PROPERTY,
code,
);
}
assert_eq!(interval(&log), PropertyValue::Unsigned(1));
let mut multiple = TrendLogMultipleObject::new(1, "TLM-1", 8).unwrap();
multiple.set_log_interval(300).unwrap();
multiple.set_log_interval(0).unwrap();
assert_eq!(
multiple
.read_property(PropertyIdentifier::LOG_INTERVAL, None)
.unwrap(),
PropertyValue::Unsigned(0)
);
}
#[test]
fn trigger_asks_a_triggered_log_for_one_record() {
let mut log = TrendLogObject::new(1, "TL-1", 8).unwrap();
assert_refused(
write(
&mut log,
PropertyIdentifier::TRIGGER,
PropertyValue::Boolean(true),
),
ErrorClass::PROPERTY,
ErrorCode::NOT_CONFIGURED_FOR_TRIGGERED_LOGGING,
);
assert_refused(
log.trigger(),
ErrorClass::PROPERTY,
ErrorCode::NOT_CONFIGURED_FOR_TRIGGERED_LOGGING,
);
write(
&mut log,
PropertyIdentifier::TRIGGER,
PropertyValue::Boolean(false),
)
.unwrap();
log.set_logging_type(LoggingType::TRIGGERED).unwrap();
write(
&mut log,
PropertyIdentifier::TRIGGER,
PropertyValue::Boolean(true),
)
.unwrap();
write(
&mut log,
PropertyIdentifier::TRIGGER,
PropertyValue::Boolean(false),
)
.unwrap();
assert_eq!(
read(&log, PropertyIdentifier::TRIGGER),
PropertyValue::Boolean(true)
);
assert_refused(
write(
&mut log,
PropertyIdentifier::TRIGGER,
PropertyValue::Unsigned(1),
),
ErrorClass::PROPERTY,
ErrorCode::INVALID_DATA_TYPE,
);
log.add_record(record()).unwrap();
assert_eq!(
read(&log, PropertyIdentifier::TRIGGER),
PropertyValue::Boolean(false)
);
assert_eq!(log.records().len(), 1);
log.trigger().unwrap();
log.set_logging_type(LoggingType::POLLED).unwrap();
assert_eq!(
read(&log, PropertyIdentifier::TRIGGER),
PropertyValue::Boolean(false)
);
}
#[test]
fn a_record_the_log_ignores_still_serves_the_trigger() {
struct Nine;
impl ClockReader for Nine {
fn read_clock(&self) -> Option<ClockFrame> {
Some(ClockFrame {
local_date: record().date,
local_time: record().time,
utc_offset: 0,
daylight_savings_status: false,
})
}
}
let mut log = TrendLogObject::new(1, "TL-1", 8).unwrap();
log.bind_clock_internal(Some(Arc::new(Nine)));
log.set_logging_type(LoggingType::TRIGGERED).unwrap();
let trigger = |log: &TrendLogObject| read(log, PropertyIdentifier::TRIGGER);
write(
&mut log,
PropertyIdentifier::LOG_ENABLE,
PropertyValue::Boolean(false),
)
.unwrap();
log.trigger().unwrap();
log.add_record(record()).unwrap();
assert_eq!(trigger(&log), PropertyValue::Boolean(false));
assert!(log
.records()
.iter()
.all(|r| matches!(r.log_datum, LogDatum::LogStatus(_))));
write(
&mut log,
PropertyIdentifier::LOG_ENABLE,
PropertyValue::Boolean(true),
)
.unwrap();
let (date, time) = (record().date, record().time);
log.set_start_time(date, Time { hour: 10, ..time }).unwrap();
log.trigger().unwrap();
log.add_record(record()).unwrap();
assert_eq!(trigger(&log), PropertyValue::Boolean(false));
assert!(log
.records()
.iter()
.all(|r| matches!(r.log_datum, LogDatum::LogStatus(_))));
}
#[test]
fn alignment_rows_read_back_what_is_written() {
let mut log = TrendLogObject::new(1, "TL-1", 8).unwrap();
assert_eq!(
read(&log, PropertyIdentifier::ALIGN_INTERVALS),
PropertyValue::Boolean(false)
);
assert_eq!(
read(&log, PropertyIdentifier::INTERVAL_OFFSET),
PropertyValue::Unsigned(0)
);
write(
&mut log,
PropertyIdentifier::ALIGN_INTERVALS,
PropertyValue::Boolean(true),
)
.unwrap();
write(
&mut log,
PropertyIdentifier::INTERVAL_OFFSET,
PropertyValue::Unsigned(31),
)
.unwrap();
assert_eq!(
read(&log, PropertyIdentifier::ALIGN_INTERVALS),
PropertyValue::Boolean(true)
);
assert_eq!(
read(&log, PropertyIdentifier::INTERVAL_OFFSET),
PropertyValue::Unsigned(31)
);
log.set_align_intervals(false);
log.set_interval_offset(7);
assert_eq!(
read(&log, PropertyIdentifier::ALIGN_INTERVALS),
PropertyValue::Boolean(false)
);
assert_eq!(
read(&log, PropertyIdentifier::INTERVAL_OFFSET),
PropertyValue::Unsigned(7)
);
for (property, value) in [
(
PropertyIdentifier::ALIGN_INTERVALS,
PropertyValue::Unsigned(1),
),
(
PropertyIdentifier::INTERVAL_OFFSET,
PropertyValue::Boolean(true),
),
] {
assert_refused(
write(&mut log, property, value),
ErrorClass::PROPERTY,
ErrorCode::INVALID_DATA_TYPE,
);
}
}