use super::lighting_required_rows::{assert_refused, db_with, read_wire, write_wire};
use super::property_metadata::assert_rpm_selector_bytes;
use super::*;
use PropertyIdentifier as P;
use bacnet_objects::value_types::{
BitStringValueObject, CharacterStringValueObject, DatePatternValueObject,
DateTimePatternValueObject, DateTimeValueObject, DateValueObject, IntegerValueObject,
LargeAnalogValueObject, OctetStringValueObject, PositiveIntegerValueObject,
TimePatternValueObject, TimeValueObject,
};
use bacnet_types::primitives::{Date, Time};
fn every_value_type() -> Vec<(Box<dyn BACnetObject>, PropertyValue)> {
let date = Date {
year: 126,
month: 10,
day: 2,
day_of_week: 5,
};
let time = Time {
hour: 6,
minute: 15,
second: 0,
hundredths: 0,
};
let datetime = PropertyValue::List(vec![PropertyValue::Date(date), PropertyValue::Time(time)]);
vec![
(
Box::new(IntegerValueObject::new(3, "IV-3").unwrap()),
PropertyValue::Signed(-40),
),
(
Box::new(PositiveIntegerValueObject::new(3, "PIV-3").unwrap()),
PropertyValue::Unsigned(40),
),
(
Box::new(LargeAnalogValueObject::new(3, "LAV-3").unwrap()),
PropertyValue::Double(40.25),
),
(
Box::new(CharacterStringValueObject::new(3, "CSV-3").unwrap()),
PropertyValue::CharacterString("occupied".into()),
),
(
Box::new(OctetStringValueObject::new(3, "OSV-3").unwrap()),
PropertyValue::OctetString(vec![0xBE, 0xEF]),
),
(
Box::new(BitStringValueObject::new(3, "BSV-3").unwrap()),
PropertyValue::BitString {
unused_bits: 6,
data: vec![0x40],
},
),
(
Box::new(DateValueObject::new(3, "DV-3").unwrap()),
PropertyValue::Date(date),
),
(
Box::new(TimeValueObject::new(3, "TV-3").unwrap()),
PropertyValue::Time(time),
),
(
Box::new(DateTimeValueObject::new(3, "DTV-3").unwrap()),
datetime.clone(),
),
(
Box::new(DatePatternValueObject::new(3, "DPV-3").unwrap()),
PropertyValue::Date(date),
),
(
Box::new(TimePatternValueObject::new(3, "TPV-3").unwrap()),
PropertyValue::Time(time),
),
(
Box::new(DateTimePatternValueObject::new(3, "DTPV-3").unwrap()),
datetime,
),
]
}
#[test]
fn rpm_non_numeric_value_selectors_carry_current_command_priority() {
let all = [
P::OBJECT_IDENTIFIER,
P::OBJECT_NAME,
P::DESCRIPTION,
P::OBJECT_TYPE,
P::PRESENT_VALUE,
P::STATUS_FLAGS,
P::OUT_OF_SERVICE,
P::RELIABILITY,
P::PRIORITY_ARRAY,
P::RELINQUISH_DEFAULT,
P::CURRENT_COMMAND_PRIORITY,
];
let required = [
P::OBJECT_IDENTIFIER,
P::OBJECT_NAME,
P::OBJECT_TYPE,
P::PRESENT_VALUE,
P::STATUS_FLAGS,
];
let optional = [
P::DESCRIPTION,
P::OUT_OF_SERVICE,
P::RELIABILITY,
P::PRIORITY_ARRAY,
P::RELINQUISH_DEFAULT,
P::CURRENT_COMMAND_PRIORITY,
];
for (object, value) in every_value_type().into_iter().skip(3) {
let (mut db, oid) = db_with(object);
for commanded in [false, true] {
if commanded {
write_wire(&mut db, oid, P::PRESENT_VALUE, value.clone(), Some(12)).unwrap();
}
for (selector, expected) in [
(P::ALL, all.as_slice()),
(P::REQUIRED, required.as_slice()),
(P::OPTIONAL, optional.as_slice()),
] {
assert_rpm_selector_bytes(&db, oid, selector, expected);
}
}
}
}
#[test]
fn current_command_priority_reads_over_the_wire_for_every_value_type() {
let ccp = P::CURRENT_COMMAND_PRIORITY;
for (object, value) in every_value_type() {
let (mut db, oid) = db_with(object);
assert_eq!(read_wire(&db, oid, ccp), [0x00], "{oid:?}");
write_wire(&mut db, oid, P::PRESENT_VALUE, value.clone(), Some(11)).unwrap();
assert_eq!(read_wire(&db, oid, ccp), [0x21, 11], "{oid:?}");
write_wire(&mut db, oid, P::PRESENT_VALUE, value, Some(2)).unwrap();
assert_eq!(read_wire(&db, oid, ccp), [0x21, 2], "{oid:?}");
assert_refused(
write_wire(&mut db, oid, ccp, PropertyValue::Unsigned(5), None),
ErrorCode::WRITE_ACCESS_DENIED,
);
write_wire(&mut db, oid, P::PRESENT_VALUE, PropertyValue::Null, Some(2)).unwrap();
assert_eq!(read_wire(&db, oid, ccp), [0x21, 11], "{oid:?}");
write_wire(
&mut db,
oid,
P::PRESENT_VALUE,
PropertyValue::Null,
Some(11),
)
.unwrap();
assert_eq!(read_wire(&db, oid, ccp), [0x00], "{oid:?}");
}
}
#[test]
fn integer_value_cov_increment_is_a_writable_unsigned_over_the_wire() {
let cov = P::COV_INCREMENT;
for (object, _) in every_value_type().into_iter().take(2) {
let (mut db, oid) = db_with(object);
assert_eq!(read_wire(&db, oid, cov), [0x21, 0x00], "{oid:?}");
write_wire(&mut db, oid, cov, PropertyValue::Unsigned(300), None).unwrap();
assert_eq!(read_wire(&db, oid, cov), [0x22, 0x01, 0x2C], "{oid:?}");
for wrong in [
PropertyValue::Signed(-1),
PropertyValue::Real(1.0),
PropertyValue::Double(1.0),
] {
assert_refused(
write_wire(&mut db, oid, cov, wrong, None),
ErrorCode::INVALID_DATA_TYPE,
);
}
assert_eq!(read_wire(&db, oid, cov), [0x22, 0x01, 0x2C], "{oid:?}");
}
}
#[test]
fn large_analog_value_cov_increment_is_a_writable_double_over_the_wire() {
let cov = P::COV_INCREMENT;
let (mut db, oid) = db_with(Box::new(LargeAnalogValueObject::new(3, "LAV-3").unwrap()));
assert_eq!(
read_wire(&db, oid, cov),
[0x55, 0x08, 0, 0, 0, 0, 0, 0, 0, 0]
);
write_wire(&mut db, oid, cov, PropertyValue::Double(0.5), None).unwrap();
let half = [0x55, 0x08, 0x3F, 0xE0, 0, 0, 0, 0, 0, 0];
assert_eq!(read_wire(&db, oid, cov), half);
for outside in [-0.5, f64::NAN, f64::INFINITY] {
assert_refused(
write_wire(&mut db, oid, cov, PropertyValue::Double(outside), None),
ErrorCode::VALUE_OUT_OF_RANGE,
);
}
for wrong in [PropertyValue::Real(0.5), PropertyValue::Unsigned(1)] {
assert_refused(
write_wire(&mut db, oid, cov, wrong, None),
ErrorCode::INVALID_DATA_TYPE,
);
}
assert_eq!(read_wire(&db, oid, cov), half);
}
#[test]
fn pics_value_types_list_current_command_priority_and_cov_increment() {
use crate::pics::{PicsConfig, generate_pics};
use crate::server::ServerConfig;
for (index, (object, _)) in every_value_type().into_iter().enumerate() {
let kind = object.object_identifier().object_type();
let mut db = ObjectDatabase::new();
db.add(object).unwrap();
let pics = generate_pics(&db, &ServerConfig::default(), &PicsConfig::default());
let support = &pics.supported_object_types[0];
assert_eq!(support.object_type, kind);
let row = |property: P| {
support
.supported_properties
.iter()
.find(|row| row.property_id == property)
};
let ccp = row(P::CURRENT_COMMAND_PRIORITY)
.unwrap_or_else(|| panic!("{kind:?} lists Current_Command_Priority"));
assert!(ccp.access.readable && ccp.access.optional, "{kind:?}");
assert!(!ccp.access.writable, "{kind:?}");
match row(P::COV_INCREMENT) {
Some(cov) => {
assert!(index < 3, "{kind:?} lists COV_Increment");
assert!(cov.access.readable && cov.access.optional, "{kind:?}");
assert!(cov.access.writable, "{kind:?}");
}
None => assert!(index >= 3, "{kind:?} lacks COV_Increment"),
}
}
}