use super::super::*;
use bacnet_types::bitstring::EventTransitionBits;
#[test]
fn time_delay_normal_defaults_to_event_parameters_time_delay() {
let mut ee = EventEnrollmentObject::new(1, "EE-1", EventType::CHANGE_OF_BITSTRING).unwrap();
assert_eq!(
ee.read_property(PropertyIdentifier::TIME_DELAY_NORMAL, None)
.unwrap(),
PropertyValue::Unsigned(0)
);
ee.set_event_parameters(BACnetEventParameter::OutOfRange {
time_delay: 12,
low_limit: 10.0,
high_limit: 90.0,
deadband: 1.0,
});
assert_eq!(
ee.read_property(PropertyIdentifier::TIME_DELAY_NORMAL, None)
.unwrap(),
PropertyValue::Unsigned(12),
"unwritten Time_Delay_Normal reads as the pTimeDelay fallback"
);
}
#[test]
fn time_delay_normal_write_round_trips_and_is_writable() {
let mut ee = EventEnrollmentObject::new(1, "EE-1", EventType::CHANGE_OF_BITSTRING).unwrap();
assert!(ee.is_writable_property(PropertyIdentifier::TIME_DELAY_NORMAL));
ee.write_property(
PropertyIdentifier::TIME_DELAY_NORMAL,
None,
PropertyValue::Unsigned(45),
None,
)
.unwrap();
assert_eq!(
ee.read_property(PropertyIdentifier::TIME_DELAY_NORMAL, None)
.unwrap(),
PropertyValue::Unsigned(45),
"a configured value reads back verbatim, no fallback"
);
ee.set_time_delay_normal(None);
assert_eq!(
ee.read_property(PropertyIdentifier::TIME_DELAY_NORMAL, None)
.unwrap(),
PropertyValue::Unsigned(0),
"absent again: back to the (empty-params) fallback"
);
}
#[test]
fn time_delay_normal_in_property_list() {
let ee = EventEnrollmentObject::new(1, "EE-1", EventType::CHANGE_OF_BITSTRING).unwrap();
assert!(
ee.property_list()
.contains(&PropertyIdentifier::TIME_DELAY_NORMAL),
"Table 12-14 lists the property (O): it must be advertised"
);
}
#[test]
fn time_delay_normal_write_validation() {
let mut ee = EventEnrollmentObject::new(1, "EE-1", EventType::CHANGE_OF_BITSTRING).unwrap();
ee.write_property(
PropertyIdentifier::TIME_DELAY_NORMAL,
None,
PropertyValue::Unsigned(9),
None,
)
.unwrap();
match ee
.write_property(
PropertyIdentifier::TIME_DELAY_NORMAL,
None,
PropertyValue::Real(1.5),
None,
)
.unwrap_err()
{
bacnet_types::error::Error::Protocol { class, code } => {
assert_eq!(
(class, code),
(
bacnet_types::enums::ErrorClass::PROPERTY.to_raw() as u32,
bacnet_types::enums::ErrorCode::INVALID_DATA_TYPE.to_raw() as u32
),
"Unsigned-typed property fed a Real: PROPERTY/INVALID_DATA_TYPE"
);
}
other => panic!("expected PROPERTY/INVALID_DATA_TYPE, got {other:?}"),
}
match ee
.write_property(
PropertyIdentifier::TIME_DELAY_NORMAL,
None,
PropertyValue::Unsigned(u64::MAX),
None,
)
.unwrap_err()
{
bacnet_types::error::Error::Protocol { class, code } => {
assert_eq!(
(class, code),
(
bacnet_types::enums::ErrorClass::PROPERTY.to_raw() as u32,
bacnet_types::enums::ErrorCode::VALUE_OUT_OF_RANGE.to_raw() as u32
),
"unrepresentable Unsigned: PROPERTY/VALUE_OUT_OF_RANGE"
);
}
other => panic!("expected PROPERTY/VALUE_OUT_OF_RANGE, got {other:?}"),
}
assert_eq!(
ee.read_property(PropertyIdentifier::TIME_DELAY_NORMAL, None)
.unwrap(),
PropertyValue::Unsigned(9),
"refused writes must not disturb the stored value"
);
}
#[test]
fn enrollment_eval_state_round_trip() {
let mut ee = EventEnrollmentObject::new(1, "EE-1", EventType::CHANGE_OF_BITSTRING).unwrap();
assert_eq!(
ee.enrollment_eval_state_internal(),
Some(EventEnrollmentEvalState::default()),
"construction starts empty"
);
let source = (
ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
PropertyIdentifier::PRESENT_VALUE,
Some(2),
);
let state = EventEnrollmentEvalState {
pending: Some(EventEnrollmentPending {
state: EventState::HIGH_LIMIT,
remaining: 4,
condition: 0,
params_fingerprint: 0xDEAD_BEEF,
}),
cov_baseline: Some(PropertyValue::Real(2.5)),
last_offnormal_value: Some(7),
};
ee.set_enrollment_eval_state_internal(state.clone())
.unwrap();
ee.set_enrollment_eval_source_internal(Some(source))
.unwrap();
assert_eq!(ee.enrollment_eval_state_internal(), Some(state));
assert_eq!(ee.enrollment_eval_source_internal(), Some(Some(source)));
ee.set_enrollment_eval_state_internal(EventEnrollmentEvalState::default())
.unwrap();
ee.set_enrollment_eval_source_internal(None).unwrap();
assert_eq!(
ee.enrollment_eval_state_internal(),
Some(EventEnrollmentEvalState::default())
);
assert_eq!(ee.enrollment_eval_source_internal(), Some(None));
}
#[test]
fn configuration_setters_cancel_pending_countdowns() {
let mut ee = EventEnrollmentObject::new(1, "EE-1", EventType::CHANGE_OF_BITSTRING).unwrap();
let pending = EventEnrollmentPending {
state: EventState::OFFNORMAL,
remaining: 2,
condition: 1,
params_fingerprint: 2,
};
let seed = |ee: &mut EventEnrollmentObject| {
ee.set_enrollment_eval_state_internal(EventEnrollmentEvalState {
pending: Some(pending.clone()),
..Default::default()
})
.unwrap();
};
seed(&mut ee);
ee.set_event_parameters(BACnetEventParameter::OutOfRange {
time_delay: 2,
low_limit: 0.0,
high_limit: 1.0,
deadband: 0.0,
});
assert!(ee
.enrollment_eval_state_internal()
.unwrap()
.pending
.is_none());
seed(&mut ee);
ee.set_time_delay_normal(Some(3));
assert!(ee
.enrollment_eval_state_internal()
.unwrap()
.pending
.is_none());
seed(&mut ee);
ee.set_object_property_reference(None).unwrap();
assert!(ee
.enrollment_eval_state_internal()
.unwrap()
.pending
.is_none());
seed(&mut ee);
let before = ee
.read_property(PropertyIdentifier::EVENT_PARAMETERS, None)
.unwrap();
assert!(ee
.write_property(
PropertyIdentifier::EVENT_PARAMETERS,
None,
PropertyValue::Unsigned(1),
None
)
.is_err());
assert_eq!(
ee.read_property(PropertyIdentifier::EVENT_PARAMETERS, None)
.unwrap(),
before
);
assert_eq!(
ee.enrollment_eval_state_internal().unwrap().pending,
Some(pending)
);
}
#[test]
fn disabling_detection_clears_eval_state_and_refuses_writes() {
let mut ee = EventEnrollmentObject::new(1, "EE-1", EventType::CHANGE_OF_BITSTRING).unwrap();
let source = (
ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
PropertyIdentifier::PRESENT_VALUE,
Some(1),
);
ee.set_enrollment_eval_source_internal(Some(source))
.unwrap();
ee.set_enrollment_eval_state_internal(EventEnrollmentEvalState {
pending: Some(EventEnrollmentPending {
state: EventState::OFFNORMAL,
remaining: 1,
condition: 3,
params_fingerprint: 1,
}),
cov_baseline: Some(PropertyValue::Real(1.0)),
last_offnormal_value: Some(3),
})
.unwrap();
ee.write_property(
PropertyIdentifier::EVENT_DETECTION_ENABLE,
None,
PropertyValue::Boolean(false),
None,
)
.unwrap();
assert_eq!(
ee.enrollment_eval_state_internal(),
Some(EventEnrollmentEvalState::default()),
"the disable reset clears the evaluation state"
);
assert_eq!(ee.enrollment_eval_source_internal(), Some(None));
assert!(ee
.set_enrollment_eval_state_internal(EventEnrollmentEvalState {
pending: Some(EventEnrollmentPending {
state: EventState::OFFNORMAL,
remaining: 1,
condition: 0,
params_fingerprint: 0,
}),
..EventEnrollmentEvalState::default()
})
.is_err());
assert!(ee
.set_enrollment_eval_source_internal(Some(source))
.is_err());
assert!(ee
.set_acked_transitions_internal(EventTransitionBits::TO_OFFNORMAL, false)
.is_err());
ee.write_property(
PropertyIdentifier::EVENT_DETECTION_ENABLE,
None,
PropertyValue::Boolean(true),
None,
)
.unwrap();
ee.set_enrollment_eval_state_internal(EventEnrollmentEvalState {
last_offnormal_value: Some(3),
..EventEnrollmentEvalState::default()
})
.unwrap();
assert_eq!(
ee.enrollment_eval_state_internal()
.unwrap()
.last_offnormal_value,
Some(3)
);
}
#[test]
fn acked_transitions_internal_set_and_clear() {
let mut ee = EventEnrollmentObject::new(1, "EE-1", EventType::CHANGE_OF_BITSTRING).unwrap();
let read = |ee: &EventEnrollmentObject| match ee
.read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
.unwrap()
{
PropertyValue::BitString { data, .. } => EventTransitionBits::from_bacnet(&data),
other => panic!("BitString expected, got {other:?}"),
};
assert_eq!(read(&ee), EventTransitionBits::all());
ee.set_acked_transitions_internal(EventTransitionBits::TO_OFFNORMAL, false)
.unwrap();
assert_eq!(
read(&ee),
EventTransitionBits::TO_FAULT | EventTransitionBits::TO_NORMAL,
"TO_OFFNORMAL cleared (ack owed)"
);
ee.set_acked_transitions_internal(EventTransitionBits::TO_NORMAL, false)
.unwrap();
assert_eq!(
read(&ee),
EventTransitionBits::TO_FAULT,
"TO_NORMAL cleared"
);
ee.set_acked_transitions_internal(EventTransitionBits::TO_OFFNORMAL, true)
.unwrap();
assert_eq!(
read(&ee),
EventTransitionBits::TO_OFFNORMAL | EventTransitionBits::TO_FAULT,
"TO_OFFNORMAL re-set (acknowledged)"
);
}
#[test]
fn eval_state_trait_defaults_reject() {
let mut object = crate::averaging::AveragingObject::new(1, "AVG-1").unwrap();
assert!(object.enrollment_eval_state_internal().is_none());
assert!(object
.set_enrollment_eval_state_internal(EventEnrollmentEvalState::default())
.is_err());
assert!(object
.set_acked_transitions_internal(EventTransitionBits::TO_OFFNORMAL, false)
.is_err());
}