use super::*;
use crate::access_control::{AccessDoorObject, AccessZoneObject};
use crate::analog::{AnalogInputObject, AnalogOutputObject, AnalogValueObject};
use crate::event_log::EventLogObject;
use crate::trend::{TrendLogMultipleObject, TrendLogObject};
use PropertyIdentifier as P;
const COMMON_REQUIRED: [P; 6] = [
P::NOTIFICATION_CLASS,
P::EVENT_ENABLE,
P::ACKED_TRANSITIONS,
P::NOTIFY_TYPE,
P::EVENT_TIME_STAMPS,
P::EVENT_DETECTION_ENABLE,
];
const COMMON_PERMITTED: [P; 4] = [
P::EVENT_MESSAGE_TEXTS,
P::EVENT_MESSAGE_TEXTS_CONFIG,
P::EVENT_ALGORITHM_INHIBIT_REF,
P::EVENT_ALGORITHM_INHIBIT,
];
struct Case {
object: Box<dyn BACnetObject>,
required: &'static [P],
permitted: &'static [P],
}
const OUT_OF_RANGE: &[P] = &[
P::TIME_DELAY,
P::HIGH_LIMIT,
P::LOW_LIMIT,
P::DEADBAND,
P::LIMIT_ENABLE,
];
const BUFFER_READY: &[P] = &[
P::NOTIFICATION_THRESHOLD,
P::RECORDS_SINCE_NOTIFICATION,
P::LAST_NOTIFY_RECORD,
];
fn cases() -> Vec<Case> {
let case = |object: Box<dyn BACnetObject>, required, permitted| Case {
object,
required,
permitted,
};
let delay_normal: &'static [P] = &[P::TIME_DELAY_NORMAL];
vec![
case(
Box::new(AnalogInputObject::new(1, "AI", 62).unwrap()),
OUT_OF_RANGE,
delay_normal,
),
case(
Box::new(AnalogOutputObject::new(1, "AO", 62).unwrap()),
OUT_OF_RANGE,
delay_normal,
),
case(
Box::new(AnalogValueObject::new(1, "AV", 62).unwrap()),
OUT_OF_RANGE,
delay_normal,
),
case(
Box::new(BinaryInputObject::new(1, "BI").unwrap()),
&[P::TIME_DELAY, P::ALARM_VALUE],
delay_normal,
),
case(
Box::new(BinaryOutputObject::new(1, "BO").unwrap()),
&[P::TIME_DELAY, P::FEEDBACK_VALUE],
delay_normal,
),
case(
Box::new(BinaryValueObject::new(1, "BV").unwrap()),
&[P::TIME_DELAY, P::ALARM_VALUE],
delay_normal,
),
case(
Box::new(MultiStateInputObject::new(1, "MSI", 3).unwrap()),
&[P::TIME_DELAY, P::ALARM_VALUES],
delay_normal,
),
case(
Box::new(MultiStateOutputObject::new(1, "MSO", 3).unwrap()),
&[P::TIME_DELAY, P::FEEDBACK_VALUE],
delay_normal,
),
case(
Box::new(MultiStateValueObject::new(1, "MSV", 3).unwrap()),
&[P::TIME_DELAY, P::ALARM_VALUES],
delay_normal,
),
case(
Box::new(AccessDoorObject::new(1, "DOOR").unwrap()),
&[P::TIME_DELAY, P::ALARM_VALUES, P::DOOR_ALARM_STATE],
delay_normal,
),
case(
Box::new(AccessZoneObject::new(1, "ZONE").unwrap()),
&[
P::TIME_DELAY,
P::ALARM_VALUES,
P::OCCUPANCY_COUNT,
P::OCCUPANCY_COUNT_ENABLE,
P::ADJUST_VALUE,
],
delay_normal,
),
case(
Box::new(TrendLogObject::new(1, "TL", 3).unwrap()),
BUFFER_READY,
&[],
),
case(
Box::new(EventLogObject::new(1, "EL", 3).unwrap()),
BUFFER_READY,
&[],
),
case(
Box::new(TrendLogMultipleObject::new(1, "TLM", 3).unwrap()),
BUFFER_READY,
&[],
),
]
}
#[test]
fn property_metadata_intrinsic_rows_are_required_or_only_permitted_per_table() {
for Case {
object,
required,
permitted,
} in cases()
{
let kind = object.object_identifier().object_type();
let metadata = object.property_metadata();
let rpm_required = object.required_properties();
let required: Vec<P> = COMMON_REQUIRED.iter().chain(required).copied().collect();
let permitted: Vec<P> = COMMON_PERMITTED.iter().chain(permitted).copied().collect();
for row in metadata.iter() {
let p = row.property_identifier;
let condition = row.presence_condition;
if required.contains(&p) {
assert_eq!(
condition,
Some(PropertyPresenceCondition::IntrinsicReportingRequired),
"{kind:?} {p:?}"
);
assert_eq!(
row.conformance,
PropertyConformance::Optional,
"{kind:?} {p:?}"
);
assert!(row.is_required(), "{kind:?} {p:?}");
assert!(rpm_required.contains(&p), "{kind:?} {p:?}");
} else if permitted.contains(&p) {
assert_eq!(
condition,
Some(PropertyPresenceCondition::IntrinsicReportingOptional),
"{kind:?} {p:?}"
);
assert!(!row.is_required(), "{kind:?} {p:?}");
assert!(!rpm_required.contains(&p), "{kind:?} {p:?}");
} else {
assert!(
!matches!(
condition,
Some(
PropertyPresenceCondition::IntrinsicReportingRequired
| PropertyPresenceCondition::IntrinsicReportingOptional
)
),
"{kind:?} {p:?} has an intrinsic condition no fixture names"
);
}
}
for p in required.iter().chain(&permitted) {
assert!(
metadata.iter().any(|row| row.property_identifier == *p),
"{kind:?} serves no {p:?}"
);
}
}
}