use std::time::Duration;
use bacnet_types::{
enums::{ErrorClass, ErrorCode, PropertyIdentifier},
error::Error,
primitives::PropertyValue,
};
use super::{BinaryLightingOutputObject, OFF, ON};
use crate::traits::BACnetObject;
fn read(object: &BinaryLightingOutputObject, property: PropertyIdentifier) -> PropertyValue {
object.read_property(property, None).unwrap()
}
fn slot(object: &BinaryLightingOutputObject, priority: u8) -> PropertyValue {
object
.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(priority as u32))
.unwrap()
}
fn write_present(object: &mut BinaryLightingOutputObject, value: u32, priority: u8) {
object
.write_property(
PropertyIdentifier::PRESENT_VALUE,
None,
PropertyValue::Enumerated(value),
Some(priority),
)
.unwrap();
}
fn command(object: &mut BinaryLightingOutputObject, priority: u8, value: PropertyValue) {
object
.write_property(
PropertyIdentifier::PRESENT_VALUE,
None,
value,
Some(priority),
)
.unwrap();
}
fn armed(operation: u32) -> BinaryLightingOutputObject {
let mut object = BinaryLightingOutputObject::new(1, "direct ordering").unwrap();
object
.write_property(
PropertyIdentifier::BLINK_WARN_ENABLE,
None,
PropertyValue::Boolean(true),
None,
)
.unwrap();
object
.write_property(
PropertyIdentifier::EGRESS_TIME,
None,
PropertyValue::Unsigned(5),
None,
)
.unwrap();
write_present(&mut object, ON, 8);
write_present(&mut object, operation, 8);
object
}
fn assert_property_error(error: Error, expected: ErrorCode) {
match error {
Error::Protocol { class, code } => {
assert_eq!(class, ErrorClass::PROPERTY.to_raw() as u32);
assert_eq!(code, expected.to_raw() as u32);
}
other => panic!("expected PROPERTY/{expected:?}, got {other:?}"),
}
}
#[test]
fn same_priority_commands_win_after_both_operation_kinds() {
for operation in [3, 4] {
for incoming in [
PropertyValue::Enumerated(OFF),
PropertyValue::Enumerated(ON),
PropertyValue::Null,
] {
let mut object = armed(operation);
command(&mut object, 8, incoming.clone());
assert_eq!(slot(&object, 8), incoming);
assert_eq!(
read(&object, PropertyIdentifier::EGRESS_ACTIVE),
PropertyValue::Boolean(false)
);
assert!(!object.advance_time_internal(Duration::from_secs(10)));
}
}
}
#[test]
fn higher_priority_command_completes_old_slot_then_installs_incoming() {
for (operation, completed_slot) in [
(3, PropertyValue::Enumerated(OFF)),
(4, PropertyValue::Null),
] {
let mut object = armed(operation);
command(&mut object, 4, PropertyValue::Enumerated(ON));
assert_eq!(slot(&object, 8), completed_slot);
assert_eq!(slot(&object, 4), PropertyValue::Enumerated(ON));
assert_eq!(
read(&object, PropertyIdentifier::EGRESS_ACTIVE),
PropertyValue::Boolean(false)
);
}
}
#[test]
fn lower_priority_command_preserves_each_operation_and_remaining_time() {
for (operation, completed_slot) in [
(3, PropertyValue::Enumerated(OFF)),
(4, PropertyValue::Null),
] {
let mut object = armed(operation);
assert!(!object.advance_time_internal(Duration::from_millis(1_500)));
command(&mut object, 10, PropertyValue::Enumerated(ON));
assert_eq!(slot(&object, 8), PropertyValue::Enumerated(ON));
assert_eq!(slot(&object, 10), PropertyValue::Enumerated(ON));
assert_eq!(
read(&object, PropertyIdentifier::EGRESS_ACTIVE),
PropertyValue::Boolean(true)
);
assert!(!object.advance_time_internal(Duration::from_millis(3_499)));
assert!(object.advance_time_internal(Duration::from_millis(1)));
assert_eq!(slot(&object, 8), completed_slot);
}
}
#[test]
fn read_only_array_writes_preserve_each_operation_and_remaining_time() {
for operation in [3, 4] {
let mut object = armed(operation);
assert!(!object.advance_time_internal(Duration::from_millis(1_500)));
let blink_count = object.lighting_blink_count_internal();
let before = read(&object, PropertyIdentifier::PRIORITY_ARRAY);
for index in [
None,
Some(0),
Some(1),
Some(4),
Some(8),
Some(10),
Some(16),
Some(17),
] {
for value in [
PropertyValue::Enumerated(OFF),
PropertyValue::Enumerated(ON),
PropertyValue::Null,
] {
assert_property_error(
object
.write_property(PropertyIdentifier::PRIORITY_ARRAY, index, value, None)
.unwrap_err(),
ErrorCode::WRITE_ACCESS_DENIED,
);
assert_eq!(read(&object, PropertyIdentifier::PRIORITY_ARRAY), before);
assert_eq!(
read(&object, PropertyIdentifier::PRESENT_VALUE),
PropertyValue::Enumerated(ON)
);
}
}
assert_property_error(
object
.write_property(
PropertyIdentifier::PRIORITY_ARRAY,
Some(4),
PropertyValue::Enumerated(3),
None,
)
.unwrap_err(),
ErrorCode::WRITE_ACCESS_DENIED,
);
assert_property_error(
object
.write_property(
PropertyIdentifier::PRIORITY_ARRAY,
Some(4),
PropertyValue::Unsigned(ON as u64),
None,
)
.unwrap_err(),
ErrorCode::WRITE_ACCESS_DENIED,
);
assert_property_error(
object
.write_property(
PropertyIdentifier::PRIORITY_ARRAY,
Some(17),
PropertyValue::Null,
None,
)
.unwrap_err(),
ErrorCode::WRITE_ACCESS_DENIED,
);
assert_eq!(slot(&object, 8), PropertyValue::Enumerated(ON));
assert_eq!(slot(&object, 4), PropertyValue::Null);
assert_eq!(object.lighting_blink_count_internal(), blink_count);
assert_eq!(
read(&object, PropertyIdentifier::EGRESS_ACTIVE),
PropertyValue::Boolean(true)
);
assert!(!object.advance_time_internal(Duration::from_millis(3_499)));
assert!(object.advance_time_internal(Duration::from_millis(1)));
}
}