use super::*;
use bacnet_types::constructed::{
BACnetCalendarEntry, BACnetDateRange, BACnetSpecialEvent, BACnetTimeValue, BACnetWeekNDay,
SpecialEventPeriod,
};
use bacnet_types::primitives::{Date, Time};
#[test]
fn schedule_read_present_value_default() {
let sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
let val = sched
.read_property(PropertyIdentifier::PRESENT_VALUE, None)
.unwrap();
assert_eq!(val, PropertyValue::Real(72.0));
}
#[test]
fn schedule_read_schedule_default() {
let sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
let val = sched
.read_property(PropertyIdentifier::SCHEDULE_DEFAULT, None)
.unwrap();
assert_eq!(val, PropertyValue::Real(72.0));
}
#[test]
fn schedule_write_schedule_default() {
let mut sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
sched
.write_property(
PropertyIdentifier::SCHEDULE_DEFAULT,
None,
PropertyValue::Real(68.0),
None,
)
.unwrap();
let val = sched
.read_property(PropertyIdentifier::SCHEDULE_DEFAULT, None)
.unwrap();
assert_eq!(val, PropertyValue::Real(68.0));
}
fn make_time(hour: u8, minute: u8) -> Time {
Time {
hour,
minute,
second: 0,
hundredths: 0,
}
}
fn make_tv(hour: u8, minute: u8, value: PropertyValue) -> BACnetTimeValue {
BACnetTimeValue {
time: make_time(hour, minute),
value,
}
}
fn app(bytes: &[u8]) -> PropertyValue {
PropertyValue::ApplicationData(bytes.to_vec())
}
const EMPTY_DAY: &[u8] = &[0x0E, 0x0F];
fn weekly(sched: &ScheduleObject, index: Option<u32>) -> Result<PropertyValue, Error> {
sched.read_property(PropertyIdentifier::WEEKLY_SCHEDULE, index)
}
#[test]
fn schedule_weekly_schedule_empty_by_default() {
let sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
assert_eq!(
weekly(&sched, None).unwrap(),
PropertyValue::List(vec![app(EMPTY_DAY); 7])
);
}
const MONDAY: &[u8] = &[
0x0E, 0xB4, 8, 0, 0, 0, 0x21, 1, 0xB4, 17, 0, 0, 0, 0x00, 0x0F,
];
fn monday_schedule() -> ScheduleObject {
let mut sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
sched
.set_weekly_schedule(
0,
vec![
make_tv(8, 0, PropertyValue::Unsigned(1)),
make_tv(17, 0, PropertyValue::Null),
],
)
.unwrap();
sched
}
#[test]
fn schedule_weekly_schedule_set_monday_read_no_index() {
let mut expected = vec![app(MONDAY)];
expected.extend(std::iter::repeat_n(app(EMPTY_DAY), 6));
assert_eq!(
weekly(&monday_schedule(), None).unwrap(),
PropertyValue::List(expected)
);
}
#[test]
fn schedule_weekly_schedule_index_0_returns_count() {
let sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
assert_eq!(weekly(&sched, Some(0)).unwrap(), PropertyValue::Unsigned(7));
}
#[test]
fn schedule_weekly_schedule_index_1_returns_monday() {
let sched = monday_schedule();
assert_eq!(weekly(&sched, Some(1)).unwrap(), app(MONDAY));
assert_eq!(weekly(&sched, Some(2)).unwrap(), app(EMPTY_DAY));
}
#[test]
fn schedule_weekly_schedule_index_7_returns_sunday() {
let mut sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
sched
.set_weekly_schedule(6, vec![make_tv(10, 0, PropertyValue::Boolean(false))])
.unwrap();
assert_eq!(
weekly(&sched, Some(7)).unwrap(),
app(&[0x0E, 0xB4, 10, 0, 0, 0, 0x10, 0x0F])
);
}
#[test]
fn schedule_weekly_schedule_invalid_index_8_returns_error() {
let sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
let result = weekly(&sched, Some(8));
if let Err(Error::Protocol { code, .. }) = result {
assert_eq!(code, ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32);
} else {
panic!("expected Protocol error, got {result:?}");
}
}
#[test]
fn schedule_weekly_schedule_refuses_a_day_index_past_sunday() {
let mut sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
assert!(matches!(
sched.set_weekly_schedule(7, vec![make_tv(8, 0, PropertyValue::Unsigned(1))]),
Err(Error::Protocol { code, .. }) if code == ErrorCode::VALUE_OUT_OF_RANGE.to_raw() as u32
));
assert_eq!(
weekly(&sched, None).unwrap(),
PropertyValue::List(vec![app(EMPTY_DAY); 7])
);
}
#[test]
fn schedule_effective_period_defaults_to_every_date() {
let sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
assert_eq!(
sched
.read_property(PropertyIdentifier::EFFECTIVE_PERIOD, None)
.unwrap(),
app(&[0xA4, 0xFF, 0xFF, 0xFF, 0xFF, 0xA4, 0xFF, 0xFF, 0xFF, 0xFF])
);
}
#[test]
fn schedule_effective_period_set_and_read() {
let mut sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
let period = BACnetDateRange {
start_date: Date {
year: 124,
month: 1,
day: 1,
day_of_week: 1,
},
end_date: Date {
year: 124,
month: 12,
day: 31,
day_of_week: 2,
},
};
sched.set_effective_period(period).unwrap();
assert_eq!(
sched
.read_property(PropertyIdentifier::EFFECTIVE_PERIOD, None)
.unwrap(),
app(&[0xA4, 124, 1, 1, 1, 0xA4, 124, 12, 31, 2])
);
}
fn every_weekday(day_of_week: u8) -> SpecialEventPeriod {
SpecialEventPeriod::CalendarEntry(BACnetCalendarEntry::WeekNDay(BACnetWeekNDay {
month: BACnetWeekNDay::ANY,
week_of_month: BACnetWeekNDay::ANY,
day_of_week,
}))
}
const SUNDAY_EVENT: &[u8] = &[
0x0E, 0x2B, 0xFF, 0xFF, 7, 0x0F, 0x2E, 0xB4, 0, 0, 0, 0, 0x00, 0x2F, 0x39, 16,
];
fn sunday_event() -> BACnetSpecialEvent {
BACnetSpecialEvent {
period: every_weekday(7),
list_of_time_values: vec![make_tv(0, 0, PropertyValue::Null)],
event_priority: 16,
}
}
fn exceptions(sched: &ScheduleObject, index: Option<u32>) -> Result<PropertyValue, Error> {
sched.read_property(PropertyIdentifier::EXCEPTION_SCHEDULE, index)
}
#[test]
fn schedule_exception_schedule_empty_by_default() {
let sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
assert_eq!(
exceptions(&sched, None).unwrap(),
PropertyValue::List(vec![])
);
assert_eq!(
exceptions(&sched, Some(0)).unwrap(),
PropertyValue::Unsigned(0)
);
}
#[test]
fn schedule_exception_schedule_count_via_index_zero() {
let mut sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
sched.add_exception(sunday_event()).unwrap();
assert_eq!(
exceptions(&sched, Some(0)).unwrap(),
PropertyValue::Unsigned(1)
);
}
#[test]
fn schedule_exception_schedule_reads_each_special_event_with_its_period() {
let mut sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
sched.add_exception(sunday_event()).unwrap();
sched
.add_exception(BACnetSpecialEvent {
period: every_weekday(1),
list_of_time_values: vec![],
event_priority: 14,
})
.unwrap();
sched
.add_exception(BACnetSpecialEvent {
period: SpecialEventPeriod::CalendarReference(
ObjectIdentifier::new(ObjectType::CALENDAR, 7).unwrap(),
),
list_of_time_values: vec![],
event_priority: 2,
})
.unwrap();
let monday: &[u8] = &[0x0E, 0x2B, 0xFF, 0xFF, 1, 0x0F, 0x2E, 0x2F, 0x39, 14];
let reference: &[u8] = &[0x1C, 0x01, 0x80, 0x00, 0x07, 0x2E, 0x2F, 0x39, 2];
assert_eq!(
exceptions(&sched, None).unwrap(),
PropertyValue::List(vec![app(SUNDAY_EVENT), app(monday), app(reference)])
);
assert_eq!(
exceptions(&sched, Some(0)).unwrap(),
PropertyValue::Unsigned(3)
);
assert_eq!(exceptions(&sched, Some(1)).unwrap(), app(SUNDAY_EVENT));
assert_eq!(exceptions(&sched, Some(3)).unwrap(), app(reference));
for index in [4, u32::MAX] {
assert!(matches!(
exceptions(&sched, Some(index)),
Err(Error::Protocol { code, .. })
if code == ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32
));
}
}
#[test]
fn schedule_opr_list_empty_by_default() {
let sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
let val = sched
.read_property(PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES, None)
.unwrap();
assert_eq!(val, PropertyValue::ApplicationData(vec![]));
}
#[test]
fn schedule_opr_list_add_and_read() {
let mut sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
let r = BACnetObjectPropertyReference::new(oid, PropertyIdentifier::PRESENT_VALUE.to_raw());
sched.add_object_property_reference(r.clone()).unwrap();
let val = sched
.read_property(PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES, None)
.unwrap();
assert_eq!(
val,
PropertyValue::ApplicationData(vec![0x0c, 0, 0, 0, 1, 0x19, 85])
);
}
#[test]
fn schedule_opr_list_multiple_references() {
let mut sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
let oid1 = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
let oid2 = ObjectIdentifier::new(ObjectType::BINARY_OUTPUT, 5).unwrap();
sched
.add_object_property_reference(BACnetObjectPropertyReference::new(
oid1,
PropertyIdentifier::PRESENT_VALUE.to_raw(),
))
.unwrap();
sched
.add_object_property_reference(BACnetObjectPropertyReference::new(
oid2,
PropertyIdentifier::PRESENT_VALUE.to_raw(),
))
.unwrap();
let val = sched
.read_property(PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES, None)
.unwrap();
assert_eq!(
val,
PropertyValue::ApplicationData(vec![
0x0c, 0, 0, 0, 1, 0x19, 85, 0x0c, 0x01, 0, 0, 5, 0x19, 85,
])
);
}
#[test]
fn schedule_property_list_contains_new_properties() {
let sched = ScheduleObject::new(1, "SCHED-1", PropertyValue::Real(72.0)).unwrap();
let props = sched.property_list();
assert!(props.contains(&PropertyIdentifier::WEEKLY_SCHEDULE));
assert!(props.contains(&PropertyIdentifier::EXCEPTION_SCHEDULE));
assert!(props.contains(&PropertyIdentifier::EFFECTIVE_PERIOD));
assert!(props.contains(&PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES));
}