use super::*;
use std::sync::Arc;
use bacnet_types::enums::LifeSafetyState;
fn read_enumerated(object: &dyn BACnetObject, property: PropertyIdentifier) -> u32 {
match object.read_property(property, None).unwrap() {
PropertyValue::Enumerated(value) => value,
other => panic!("expected enumerated value, got {other:?}"),
}
}
fn read_event_state(object: &dyn BACnetObject) -> u32 {
read_enumerated(object, PropertyIdentifier::EVENT_STATE)
}
fn assert_in_alarm_clear(object: &dyn BACnetObject) {
match object
.read_property(PropertyIdentifier::STATUS_FLAGS, None)
.unwrap()
{
PropertyValue::BitString { data, .. } => {
assert!(!data.is_empty(), "Status_Flags must carry flag octets");
assert_eq!(
data[0] & 0x80,
0,
"IN_ALARM must stay clear while Event_State is NORMAL"
);
}
other => panic!("expected Status_Flags bitstring, got {other:?}"),
}
}
#[test]
fn point_silence_unsilence_leaves_event_state_and_in_alarm_untouched() {
let cases = [
(
SilencedState::UNSILENCED,
LifeSafetyOperation::SILENCE,
SilencedState::ALL_SILENCED,
),
(
SilencedState::UNSILENCED,
LifeSafetyOperation::SILENCE_AUDIBLE,
SilencedState::AUDIBLE_SILENCED,
),
(
SilencedState::UNSILENCED,
LifeSafetyOperation::SILENCE_VISUAL,
SilencedState::VISIBLE_SILENCED,
),
(
SilencedState::ALL_SILENCED,
LifeSafetyOperation::UNSILENCE,
SilencedState::UNSILENCED,
),
(
SilencedState::ALL_SILENCED,
LifeSafetyOperation::UNSILENCE_AUDIBLE,
SilencedState::VISIBLE_SILENCED,
),
(
SilencedState::ALL_SILENCED,
LifeSafetyOperation::UNSILENCE_VISUAL,
SilencedState::AUDIBLE_SILENCED,
),
];
for (initial, operation, expected) in cases {
let mut point = LifeSafetyPointObject::new(1, "LSP-1").unwrap();
point.set_silenced(initial);
point.set_operation_expected(operation);
assert_eq!(read_event_state(&point), 0);
assert_in_alarm_clear(&point);
let outcome = point.apply_life_safety_operation(operation).unwrap();
assert_eq!(outcome.effect, LifeSafetyOperationEffect::Applied);
assert_eq!(
read_enumerated(&point, PropertyIdentifier::SILENCED),
expected.to_raw(),
"operation {} from state {}",
operation.to_raw(),
initial.to_raw()
);
assert_eq!(read_event_state(&point), 0);
assert_in_alarm_clear(&point);
assert!(
!outcome
.changed_properties
.contains(&PropertyIdentifier::EVENT_STATE),
"operation {} must not report EVENT_STATE",
operation.to_raw()
);
assert!(
!outcome
.changed_properties
.contains(&PropertyIdentifier::STATUS_FLAGS),
"operation {} must not report STATUS_FLAGS",
operation.to_raw()
);
}
}
#[test]
fn zone_silence_unsilence_leaves_event_state_and_in_alarm_untouched() {
let cases = [
(
SilencedState::UNSILENCED,
LifeSafetyOperation::SILENCE,
SilencedState::ALL_SILENCED,
),
(
SilencedState::UNSILENCED,
LifeSafetyOperation::SILENCE_AUDIBLE,
SilencedState::AUDIBLE_SILENCED,
),
(
SilencedState::UNSILENCED,
LifeSafetyOperation::SILENCE_VISUAL,
SilencedState::VISIBLE_SILENCED,
),
(
SilencedState::ALL_SILENCED,
LifeSafetyOperation::UNSILENCE,
SilencedState::UNSILENCED,
),
(
SilencedState::ALL_SILENCED,
LifeSafetyOperation::UNSILENCE_AUDIBLE,
SilencedState::VISIBLE_SILENCED,
),
(
SilencedState::ALL_SILENCED,
LifeSafetyOperation::UNSILENCE_VISUAL,
SilencedState::AUDIBLE_SILENCED,
),
];
for (initial, operation, expected) in cases {
let mut zone = LifeSafetyZoneObject::new(1, "LSZ-1").unwrap();
zone.set_silenced(initial);
zone.set_operation_expected(operation);
assert_eq!(read_event_state(&zone), 0);
assert_in_alarm_clear(&zone);
let outcome = zone.apply_life_safety_operation(operation).unwrap();
assert_eq!(outcome.effect, LifeSafetyOperationEffect::Applied);
assert_eq!(
read_enumerated(&zone, PropertyIdentifier::SILENCED),
expected.to_raw(),
"operation {} from state {}",
operation.to_raw(),
initial.to_raw()
);
assert_eq!(read_event_state(&zone), 0);
assert_in_alarm_clear(&zone);
assert!(
!outcome
.changed_properties
.contains(&PropertyIdentifier::EVENT_STATE),
"operation {} must not report EVENT_STATE",
operation.to_raw()
);
assert!(
!outcome
.changed_properties
.contains(&PropertyIdentifier::STATUS_FLAGS),
"operation {} must not report STATUS_FLAGS",
operation.to_raw()
);
assert!(
!outcome
.changed_properties
.contains(&PropertyIdentifier::TRACKING_VALUE),
"an unchanged TRACKING_VALUE is not reported"
);
}
}
#[test]
fn point_resets_leave_event_state_and_in_alarm_untouched() {
for operation in [
LifeSafetyOperation::RESET,
LifeSafetyOperation::RESET_ALARM,
LifeSafetyOperation::RESET_FAULT,
] {
let mut point = LifeSafetyPointObject::new(1, "point").unwrap();
point.set_present_value(LifeSafetyState::ALARM);
point.set_tracking_value(LifeSafetyState::FAULT);
point.set_silenced(SilencedState::ALL_SILENCED);
point.set_operation_expected(operation);
point.set_reset_executor(Arc::new(|_| {
Ok(LifeSafetyPointResetCommit {
present_value: Some(LifeSafetyState::QUIET),
tracking_value: Some(LifeSafetyState::QUIET),
silenced: Some(SilencedState::UNSILENCED),
})
}));
assert_eq!(read_event_state(&point), 0);
assert_in_alarm_clear(&point);
let outcome = point.apply_life_safety_operation(operation).unwrap();
assert_eq!(outcome.effect, LifeSafetyOperationEffect::Applied);
assert_eq!(
outcome.changed_properties,
vec![
PropertyIdentifier::PRESENT_VALUE,
PropertyIdentifier::TRACKING_VALUE,
PropertyIdentifier::SILENCED,
PropertyIdentifier::OPERATION_EXPECTED,
]
);
assert_eq!(read_event_state(&point), 0);
assert_in_alarm_clear(&point);
}
}
#[test]
fn zone_resets_leave_event_state_and_in_alarm_untouched() {
for operation in [
LifeSafetyOperation::RESET,
LifeSafetyOperation::RESET_ALARM,
LifeSafetyOperation::RESET_FAULT,
] {
let mut zone = LifeSafetyZoneObject::new(1, "zone").unwrap();
zone.set_present_value(LifeSafetyState::ALARM);
zone.set_silenced(SilencedState::ALL_SILENCED);
zone.set_operation_expected(operation);
zone.set_reset_executor(Arc::new(|_| {
Ok(LifeSafetyZoneResetCommit {
present_value: Some(LifeSafetyState::QUIET),
tracking_value: None,
silenced: Some(SilencedState::UNSILENCED),
})
}));
assert_eq!(read_event_state(&zone), 0);
assert_in_alarm_clear(&zone);
let outcome = zone.apply_life_safety_operation(operation).unwrap();
assert_eq!(outcome.effect, LifeSafetyOperationEffect::Applied);
assert_eq!(
outcome.changed_properties,
vec![
PropertyIdentifier::PRESENT_VALUE,
PropertyIdentifier::SILENCED,
PropertyIdentifier::OPERATION_EXPECTED,
]
);
assert_eq!(read_event_state(&zone), 0);
assert_in_alarm_clear(&zone);
assert!(
!outcome
.changed_properties
.contains(&PropertyIdentifier::TRACKING_VALUE),
"an unchanged TRACKING_VALUE is not reported"
);
}
}
#[test]
fn point_silence_and_unsilence_report_only_actual_silenced_and_expected_deltas() {
let mut point = LifeSafetyPointObject::new(1, "LSP-1").unwrap();
point.set_operation_expected(LifeSafetyOperation::SILENCE);
let outcome = point
.apply_life_safety_operation(LifeSafetyOperation::SILENCE)
.unwrap();
assert_eq!(outcome.effect, LifeSafetyOperationEffect::Applied);
assert_eq!(
outcome.changed_properties,
vec![
PropertyIdentifier::SILENCED,
PropertyIdentifier::OPERATION_EXPECTED,
]
);
point.set_operation_expected(LifeSafetyOperation::SILENCE);
let outcome = point
.apply_life_safety_operation(LifeSafetyOperation::SILENCE)
.unwrap();
assert_eq!(
outcome.changed_properties,
vec![PropertyIdentifier::OPERATION_EXPECTED]
);
point.set_operation_expected(LifeSafetyOperation::UNSILENCE);
let outcome = point
.apply_life_safety_operation(LifeSafetyOperation::UNSILENCE)
.unwrap();
assert_eq!(
outcome.changed_properties,
vec![
PropertyIdentifier::SILENCED,
PropertyIdentifier::OPERATION_EXPECTED,
]
);
}