use super::*;
const CVR: PropertyIdentifier = PropertyIdentifier::CONTROLLED_VARIABLE_REFERENCE;
const MVR: PropertyIdentifier = PropertyIdentifier::MANIPULATED_VARIABLE_REFERENCE;
const SR: PropertyIdentifier = PropertyIdentifier::SETPOINT_REFERENCE;
const PRESENT_VALUE: u32 = 85;
const AI_7_PV: [u8; 7] = [0x0C, 0x00, 0x00, 0x00, 0x07, 0x19, 0x55];
const AO_3_PV_4: [u8; 9] = [0x0C, 0x00, 0x40, 0x00, 0x03, 0x19, 0x55, 0x29, 0x04];
const AV_10_PV_FRAMED: [u8; 9] = [0x0E, 0x0C, 0x00, 0x80, 0x00, 0x0A, 0x19, 0x55, 0x0F];
const UNSET_CVR: [u8; 7] = [0x0C, 0x00, 0x3F, 0xFF, 0xFF, 0x19, 0x55];
const UNSET_MVR: [u8; 7] = [0x0C, 0x00, 0x7F, 0xFF, 0xFF, 0x19, 0x55];
fn oid(object_type: ObjectType, instance: u32) -> ObjectIdentifier {
ObjectIdentifier::new(object_type, instance).unwrap()
}
fn data(bytes: &[u8]) -> PropertyValue {
PropertyValue::ApplicationData(bytes.to_vec())
}
fn read(lo: &LoopObject, property: PropertyIdentifier) -> PropertyValue {
lo.read_property(property, None).unwrap()
}
fn write(lo: &mut LoopObject, property: PropertyIdentifier, value: PropertyValue) {
lo.write_property(property, None, value, None)
.unwrap_or_else(|e| panic!("{property:?} write refused: {e:?}"));
}
fn assert_refused(
lo: &mut LoopObject,
property: PropertyIdentifier,
value: PropertyValue,
expected: ErrorCode,
) {
let before = read(lo, property);
let error = lo
.write_property(property, None, value.clone(), None)
.unwrap_err();
assert!(
matches!(error, Error::Protocol { class, code }
if class == ErrorClass::PROPERTY.to_raw() as u32 && code == expected.to_raw() as u32),
"{property:?} <- {value:?}: expected PROPERTY/{expected:?}, got {error:?}"
);
assert_eq!(read(lo, property), before, "{property:?} changed");
}
fn configured_loop() -> LoopObject {
let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
lo.set_controlled_variable_reference(BACnetObjectPropertyReference::new(
oid(ObjectType::ANALOG_INPUT, 7),
PRESENT_VALUE,
));
lo.set_manipulated_variable_reference(BACnetObjectPropertyReference::new_indexed(
oid(ObjectType::ANALOG_OUTPUT, 3),
PRESENT_VALUE,
4,
));
lo.set_setpoint_reference(BACnetObjectPropertyReference::new(
oid(ObjectType::ANALOG_VALUE, 10),
PRESENT_VALUE,
));
lo
}
#[test]
fn loop_unset_references_read_the_reserved_instance_or_the_empty_setpoint_reference() {
let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
assert_eq!(read(&lo, CVR), data(&UNSET_CVR));
assert_eq!(read(&lo, MVR), data(&UNSET_MVR));
assert_eq!(read(&lo, SR), data(&[]));
}
#[test]
fn loop_set_references_read_as_their_clause_21_encodings() {
let lo = configured_loop();
assert_eq!(read(&lo, CVR), data(&AI_7_PV));
assert_eq!(read(&lo, MVR), data(&AO_3_PV_4));
assert_eq!(read(&lo, SR), data(&AV_10_PV_FRAMED));
}
#[test]
fn loop_references_in_property_list() {
let lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
let list = lo.property_list();
for property in [CVR, MVR, SR] {
assert!(list.contains(&property), "{property:?} missing");
}
}
#[test]
fn loop_reference_writes_take_the_encodings_they_read_as() {
let mut lo = LoopObject::new(1, "LOOP-1", 62).unwrap();
for (property, bytes) in [
(CVR, &AO_3_PV_4[..]),
(MVR, &AI_7_PV[..]),
(SR, &AV_10_PV_FRAMED[..]),
] {
write(&mut lo, property, data(bytes));
assert_eq!(read(&lo, property), data(bytes), "{property:?}");
}
let source = configured_loop();
for property in [CVR, MVR, SR] {
write(&mut lo, property, read(&source, property));
assert_eq!(read(&lo, property), read(&source, property));
}
}
#[test]
fn loop_reference_writes_clear_with_the_unset_values_they_read_as() {
let mut lo = configured_loop();
write(&mut lo, CVR, data(&UNSET_CVR));
write(&mut lo, MVR, data(&UNSET_MVR));
write(&mut lo, SR, data(&[]));
assert_eq!(read(&lo, CVR), data(&UNSET_CVR));
assert_eq!(read(&lo, MVR), data(&UNSET_MVR));
assert_eq!(read(&lo, SR), data(&[]));
let unset = LoopObject::new(2, "LOOP-2", 62).unwrap();
for property in [CVR, MVR] {
let mut lo = configured_loop();
write(
&mut lo,
property,
data(&[0x0C, 0x00, 0xBF, 0xFF, 0xFF, 0x19, 0x57]),
);
assert_eq!(read(&lo, property), read(&unset, property), "{property:?}");
}
let mut lo = configured_loop();
lo.set_controlled_variable_reference(crate::reference::unset_reference(
ObjectType::ANALOG_VALUE,
));
assert_eq!(read(&lo, CVR), data(&UNSET_CVR));
}
#[test]
fn loop_null_reference_writes_are_refused_as_another_datatype() {
let mut lo = configured_loop();
for property in [CVR, MVR, SR] {
assert_refused(
&mut lo,
property,
PropertyValue::Null,
ErrorCode::INVALID_DATA_TYPE,
);
}
}
#[test]
fn loop_flat_reference_writes_are_refused_and_change_nothing() {
let mut lo = configured_loop();
let target = oid(ObjectType::ANALOG_INPUT, 9);
for property in [CVR, MVR, SR] {
for flat in [
PropertyValue::List(vec![
PropertyValue::ObjectIdentifier(target),
PropertyValue::Enumerated(PRESENT_VALUE),
]),
PropertyValue::List(vec![
PropertyValue::ObjectIdentifier(target),
PropertyValue::Enumerated(PRESENT_VALUE),
PropertyValue::Unsigned(2),
]),
] {
assert_refused(&mut lo, property, flat, ErrorCode::INVALID_DATA_TYPE);
}
}
}
#[test]
fn loop_reference_writes_of_other_datatypes_or_bad_octets_change_nothing() {
let mut lo = configured_loop();
assert_refused(
&mut lo,
SR,
PropertyValue::Null,
ErrorCode::INVALID_DATA_TYPE,
);
assert_refused(&mut lo, SR, data(&AI_7_PV), ErrorCode::INVALID_DATA_TYPE);
assert_refused(
&mut lo,
CVR,
data(&AV_10_PV_FRAMED),
ErrorCode::INVALID_DATA_TYPE,
);
assert_refused(
&mut lo,
MVR,
PropertyValue::Unsigned(42),
ErrorCode::INVALID_DATA_TYPE,
);
let with_device = [&AI_7_PV[..], &[0x3C, 0x02, 0x00, 0x00, 0x4D]].concat();
assert_refused(
&mut lo,
CVR,
data(&with_device),
ErrorCode::INVALID_DATA_ENCODING,
);
assert_refused(
&mut lo,
SR,
data(&[0x0E, 0x0F]),
ErrorCode::INVALID_DATA_ENCODING,
);
assert_refused(
&mut lo,
MVR,
data(&AO_3_PV_4[..5]),
ErrorCode::INVALID_DATA_ENCODING,
);
assert_refused(&mut lo, CVR, data(&[]), ErrorCode::INVALID_DATA_ENCODING);
}