use std::time::Duration;
use bacnet_types::enums::PropertyIdentifier as P;
use super::*;
use crate::event::commit_test_proposal;
const SECURED: DoorSecuredStatus = DoorSecuredStatus::SECURED;
const UNSECURED: DoorSecuredStatus = DoorSecuredStatus::UNSECURED;
const UNKNOWN: DoorSecuredStatus = DoorSecuredStatus::UNKNOWN;
fn door() -> AccessDoorObject {
AccessDoorObject::new(1, "DOOR-1").unwrap()
}
fn secured(door: &AccessDoorObject) -> DoorSecuredStatus {
match door.read_property(P::SECURED_STATUS, None).unwrap() {
PropertyValue::Enumerated(raw) => DoorSecuredStatus::from_raw(raw),
other => panic!("Secured_Status must be Enumerated, got {other:?}"),
}
}
fn command(door: &mut AccessDoorObject, value: Option<DoorValue>, priority: u8) {
let value = value.map_or(PropertyValue::Null, |v| {
PropertyValue::Enumerated(v.to_raw())
});
door.write_property(P::PRESENT_VALUE, None, value, Some(priority))
.unwrap();
}
fn simulate(door: &mut AccessDoorObject, property: P, raw: u32) {
door.write_property(property, None, PropertyValue::Enumerated(raw), None)
.unwrap();
}
fn set_out_of_service(door: &mut AccessDoorObject, out_of_service: bool) {
door.write_property(
P::OUT_OF_SERVICE,
None,
PropertyValue::Boolean(out_of_service),
None,
)
.unwrap();
}
fn status_flags(bits: StatusFlags) -> PropertyValue {
PropertyValue::BitString {
unused_bits: 4,
data: vec![bits.bits() << 4],
}
}
#[test]
fn access_door_secured_status_is_secured_with_every_input_met() {
let mut door = door();
assert_eq!(secured(&door), SECURED);
door.set_door_status(DoorStatus::UNUSED);
assert_eq!(secured(&door), SECURED);
door.set_lock_status(LockStatus::UNUSED);
assert_eq!(secured(&door), SECURED);
door.set_alarm_values([DoorAlarmState::FORCED_OPEN])
.unwrap();
door.set_door_alarm_state(DoorAlarmState::FORCED_OPEN)
.unwrap();
assert_eq!(secured(&door), SECURED);
}
#[test]
fn access_door_secured_status_follows_present_value() {
let mut door = door();
for value in [
DoorValue::UNLOCK,
DoorValue::PULSE_UNLOCK,
DoorValue::EXTENDED_PULSE_UNLOCK,
] {
command(&mut door, Some(value), 8);
assert_eq!(secured(&door), UNSECURED, "{value:?}");
command(&mut door, None, 8);
assert_eq!(secured(&door), SECURED, "{value:?} relinquished");
}
command(&mut door, Some(DoorValue::UNLOCK), 10);
command(&mut door, Some(DoorValue::LOCK), 5);
assert_eq!(secured(&door), SECURED);
command(&mut door, None, 5);
assert_eq!(secured(&door), UNSECURED);
command(&mut door, None, 10);
door.set_relinquish_default(DoorValue::UNLOCK).unwrap();
assert_eq!(secured(&door), UNSECURED);
door.set_relinquish_default(DoorValue::LOCK).unwrap();
assert_eq!(secured(&door), SECURED);
}
#[test]
fn access_door_secured_status_follows_door_and_lock_status() {
let mut door = door();
let open = DoorStatus::ALL_NAMED
.iter()
.map(|&(_, status)| status)
.filter(|&status| {
!matches!(
status,
DoorStatus::CLOSED
| DoorStatus::UNUSED
| DoorStatus::UNKNOWN
| DoorStatus::DOOR_FAULT
)
})
.chain([DoorStatus::from_raw(1_024)]);
for status in open {
door.set_door_status(status);
assert_eq!(secured(&door), UNSECURED, "{status:?}");
door.set_door_status(DoorStatus::CLOSED);
assert_eq!(secured(&door), SECURED, "{status:?} closed again");
}
door.set_lock_status(LockStatus::UNLOCKED);
assert_eq!(secured(&door), UNSECURED);
door.set_lock_status(LockStatus::LOCKED);
assert_eq!(secured(&door), SECURED);
}
fn report(door: &mut AccessDoorObject, state: DoorAlarmState) {
door.set_door_alarm_state(state).unwrap();
let outcome = door.evaluate_intrinsic_reporting().unwrap();
commit_test_proposal(door, outcome);
}
#[test]
fn access_door_secured_status_follows_the_in_alarm_flag() {
let mut door = door();
door.set_alarm_values([DoorAlarmState::FORCED_OPEN])
.unwrap();
door.set_fault_values([DoorAlarmState::DOOR_FAULT]).unwrap();
for (state, event_state, flags) in [
(
DoorAlarmState::FORCED_OPEN,
EventState::OFFNORMAL,
StatusFlags::IN_ALARM,
),
(
DoorAlarmState::DOOR_FAULT,
EventState::FAULT,
StatusFlags::IN_ALARM | StatusFlags::FAULT,
),
] {
report(&mut door, state);
assert_eq!(
door.read_property(P::EVENT_STATE, None).unwrap(),
PropertyValue::Enumerated(event_state.to_raw())
);
assert_eq!(
door.read_property(P::STATUS_FLAGS, None).unwrap(),
status_flags(flags),
"{state:?}"
);
assert_eq!(secured(&door), UNSECURED, "{state:?}");
report(&mut door, DoorAlarmState::NORMAL);
}
assert_eq!(
door.read_property(P::STATUS_FLAGS, None).unwrap(),
status_flags(StatusFlags::empty())
);
assert_eq!(secured(&door), SECURED);
}
#[test]
fn access_door_secured_status_fails_while_any_state_is_masked() {
let mut door = door();
door.set_masked_alarm_values([DoorAlarmState::TAMPER])
.unwrap();
assert_eq!(secured(&door), UNSECURED);
door.write_property(
P::MASKED_ALARM_VALUES,
None,
PropertyValue::List(vec![]),
None,
)
.unwrap();
assert_eq!(secured(&door), SECURED);
}
#[test]
fn access_door_secured_status_is_unknown_when_an_input_cannot_be_told() {
let mut door = door();
for status in [DoorStatus::UNKNOWN, DoorStatus::DOOR_FAULT] {
door.set_door_status(status);
assert_eq!(secured(&door), UNKNOWN, "{status:?}");
command(&mut door, Some(DoorValue::UNLOCK), 8);
assert_eq!(secured(&door), UNSECURED, "{status:?} unlocked");
command(&mut door, None, 8);
door.set_lock_status(LockStatus::UNLOCKED);
assert_eq!(secured(&door), UNSECURED, "{status:?} lock open");
door.set_lock_status(LockStatus::LOCKED);
door.set_door_status(DoorStatus::CLOSED);
assert_eq!(secured(&door), SECURED, "{status:?} closed again");
}
for status in [LockStatus::UNKNOWN, LockStatus::LOCK_FAULT] {
door.set_lock_status(status);
assert_eq!(secured(&door), UNKNOWN, "{status:?}");
door.set_door_status(DoorStatus::OPENED);
assert_eq!(secured(&door), UNSECURED, "{status:?} door open");
door.set_door_status(DoorStatus::CLOSED);
door.set_lock_status(LockStatus::LOCKED);
assert_eq!(secured(&door), SECURED, "{status:?} locked again");
}
door.set_door_status(DoorStatus::UNKNOWN);
door.set_lock_status(LockStatus::LOCK_FAULT);
assert_eq!(secured(&door), UNKNOWN);
}
#[test]
fn access_door_secured_status_follows_a_pulse_and_its_relock() {
let mut door = door();
door.set_door_pulse_time(30);
door.set_door_extended_pulse_time(50);
for (value, tenths) in [
(DoorValue::PULSE_UNLOCK, 30),
(DoorValue::EXTENDED_PULSE_UNLOCK, 50),
] {
command(&mut door, Some(value), 8);
assert_eq!(secured(&door), UNSECURED, "{value:?}");
assert!(!door.advance_time_internal(Duration::from_millis((tenths - 1) * 100)));
assert_eq!(secured(&door), UNSECURED, "{value:?} one tenth short");
assert!(door.advance_time_internal(Duration::from_millis(100)));
assert_eq!(secured(&door), SECURED, "{value:?} relocked");
let snapshot = door.cov_snapshot_internal().unwrap();
assert_eq!(
snapshot.read_property(P::SECURED_STATUS, None).unwrap(),
PropertyValue::Enumerated(SECURED.to_raw())
);
}
}
#[test]
fn access_door_secured_status_follows_a_simulation_and_the_return_to_service() {
let mut door = door();
set_out_of_service(&mut door, true);
assert_eq!(secured(&door), SECURED);
simulate(&mut door, P::DOOR_STATUS, DoorStatus::OPENED.to_raw());
assert_eq!(secured(&door), UNSECURED);
simulate(&mut door, P::DOOR_STATUS, DoorStatus::CLOSED.to_raw());
simulate(&mut door, P::LOCK_STATUS, LockStatus::UNLOCKED.to_raw());
assert_eq!(secured(&door), UNSECURED);
simulate(&mut door, P::LOCK_STATUS, LockStatus::UNKNOWN.to_raw());
assert_eq!(secured(&door), UNKNOWN);
simulate(&mut door, P::LOCK_STATUS, LockStatus::LOCKED.to_raw());
assert_eq!(secured(&door), SECURED);
door.set_door_status(DoorStatus::OPENED);
door.set_lock_status(LockStatus::UNLOCKED);
assert_eq!(secured(&door), SECURED);
set_out_of_service(&mut door, false);
assert_eq!(secured(&door), UNSECURED);
door.set_door_status(DoorStatus::CLOSED);
assert_eq!(secured(&door), UNSECURED);
door.set_lock_status(LockStatus::LOCKED);
assert_eq!(secured(&door), SECURED);
set_out_of_service(&mut door, true);
simulate(&mut door, P::DOOR_STATUS, DoorStatus::DOOR_FAULT.to_raw());
assert_eq!(secured(&door), UNKNOWN);
set_out_of_service(&mut door, false);
assert_eq!(secured(&door), SECURED);
}