use super::super::*;
use super::{assert_invalid_data_type, assert_value_out_of_range};
use bacnet_types::constructed::BACnetDeviceObjectReference;
use bacnet_types::enums::{ErrorClass, ErrorCode};
const ENERGY_METER: PropertyIdentifier = PropertyIdentifier::ENERGY_METER;
const ENERGY_METER_REF: PropertyIdentifier = PropertyIdentifier::ENERGY_METER_REF;
trait Metered: BACnetObject {
fn energy_meter_ref(&self) -> &BACnetDeviceObjectReference;
fn set_energy_meter_ref(&mut self, reference: BACnetDeviceObjectReference)
-> Result<(), Error>;
}
macro_rules! impl_metered {
($object:ty) => {
impl Metered for $object {
fn energy_meter_ref(&self) -> &BACnetDeviceObjectReference {
<$object>::energy_meter_ref(self)
}
fn set_energy_meter_ref(
&mut self,
reference: BACnetDeviceObjectReference,
) -> Result<(), Error> {
<$object>::set_energy_meter_ref(self, reference)
}
}
};
}
impl_metered!(LiftObject);
impl_metered!(EscalatorObject);
fn objects() -> [Box<dyn Metered>; 2] {
[
Box::new(LiftObject::new(1, "LIFT-1", 3).unwrap()),
Box::new(EscalatorObject::new(1, "ESC-1").unwrap()),
]
}
fn reference(
device: Option<u32>,
object_type: ObjectType,
instance: u32,
) -> BACnetDeviceObjectReference {
BACnetDeviceObjectReference {
device_identifier: device
.map(|instance| ObjectIdentifier::new(ObjectType::DEVICE, instance).unwrap()),
object_identifier: ObjectIdentifier::new(object_type, instance).unwrap(),
}
}
fn uninitialized() -> BACnetDeviceObjectReference {
reference(
None,
ObjectType::ACCUMULATOR,
ObjectIdentifier::MAX_INSTANCE,
)
}
fn write(
object: &mut dyn Metered,
property: PropertyIdentifier,
value: PropertyValue,
) -> Result<(), Error> {
object.write_property(property, None, value, None)
}
fn read(object: &dyn Metered, property: PropertyIdentifier) -> PropertyValue {
object.read_property(property, None).unwrap()
}
fn assert_write_access_denied(result: Result<(), Error>, context: &str) {
match result {
Err(Error::Protocol { class, code }) => {
assert_eq!(class, ErrorClass::PROPERTY.to_raw() as u32, "{context}");
assert_eq!(
code,
ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
"{context}"
);
}
other => panic!("{context}: expected PROPERTY/WRITE_ACCESS_DENIED, got {other:?}"),
}
}
#[test]
fn energy_meter_ref_starts_uninitialized_and_is_read_only_over_the_network() {
for mut object in objects() {
let name = object.object_name().to_owned();
assert_eq!(*object.energy_meter_ref(), uninitialized(), "{name}");
let unset = PropertyValue::ApplicationData(vec![0x1C, 0x05, 0xFF, 0xFF, 0xFF]);
assert_eq!(read(&*object, ENERGY_METER_REF), unset, "{name}");
assert!(object.property_list().contains(&ENERGY_METER_REF), "{name}");
assert!(!object.required_properties().contains(&ENERGY_METER_REF));
assert!(!object.is_writable_property(ENERGY_METER_REF), "{name}");
object
.set_energy_meter_ref(reference(None, ObjectType::ACCUMULATOR, 7))
.unwrap();
let set = read(&*object, ENERGY_METER_REF);
for value in [
set.clone(),
unset,
PropertyValue::ObjectIdentifier(
ObjectIdentifier::new(ObjectType::ACCUMULATOR, 8).unwrap(),
),
] {
for out_of_service in [false, true] {
write(
&mut *object,
PropertyIdentifier::OUT_OF_SERVICE,
PropertyValue::Boolean(out_of_service),
)
.unwrap();
assert_write_access_denied(
write(&mut *object, ENERGY_METER_REF, value.clone()),
&format!("{name} OOS={out_of_service} {value:?}"),
);
}
}
assert_eq!(read(&*object, ENERGY_METER_REF), set, "{name}");
}
}
#[test]
fn set_energy_meter_ref_takes_local_and_remote_meter_objects() {
for mut object in objects() {
let name = object.object_name().to_owned();
for (object_type, wire_type) in [
(ObjectType::ACCUMULATOR, [0x05, 0xC0]),
(ObjectType::PULSE_CONVERTER, [0x06, 0x00]),
(ObjectType::ANALOG_INPUT, [0x00, 0x00]),
(ObjectType::ANALOG_VALUE, [0x00, 0x80]),
(ObjectType::INTEGER_VALUE, [0x0B, 0x40]),
(ObjectType::LARGE_ANALOG_VALUE, [0x0B, 0x80]),
(ObjectType::POSITIVE_INTEGER_VALUE, [0x0C, 0x00]),
(ObjectType::from_raw(128), [0x20, 0x00]),
(ObjectType::from_raw(1023), [0xFF, 0xC0]),
] {
let meter = reference(None, object_type, 5);
object.set_energy_meter_ref(meter.clone()).unwrap();
assert_eq!(*object.energy_meter_ref(), meter, "{name}");
assert_eq!(
read(&*object, ENERGY_METER_REF),
PropertyValue::ApplicationData(vec![0x1C, wire_type[0], wire_type[1], 0x00, 0x05]),
"{name} {object_type:?}"
);
}
let remote = reference(Some(9), ObjectType::ANALOG_INPUT, 3);
object.set_energy_meter_ref(remote).unwrap();
assert_eq!(
read(&*object, ENERGY_METER_REF),
PropertyValue::ApplicationData(vec![
0x0C, 0x02, 0x00, 0x00, 0x09, 0x1C, 0x00, 0x00, 0x00, 0x03
]),
"{name}"
);
object.set_energy_meter_ref(uninitialized()).unwrap();
assert_eq!(*object.energy_meter_ref(), uninitialized(), "{name}");
}
}
#[test]
fn set_energy_meter_ref_refuses_objects_that_are_not_meters_atomically() {
for mut object in objects() {
let name = object.object_name().to_owned();
let meter = reference(Some(4), ObjectType::ACCUMULATOR, 7);
object.set_energy_meter_ref(meter.clone()).unwrap();
let wire = read(&*object, ENERGY_METER_REF);
for refused in [
reference(None, ObjectType::DEVICE, 4),
reference(None, ObjectType::ANALOG_OUTPUT, 1),
reference(None, ObjectType::BINARY_INPUT, 1),
reference(None, ObjectType::MULTI_STATE_VALUE, 1),
reference(None, ObjectType::TREND_LOG, 1),
reference(None, ObjectType::ELEVATOR_GROUP, 1),
reference(None, ObjectType::LIFT, 1),
reference(None, ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE),
BACnetDeviceObjectReference {
device_identifier: Some(
ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 4).unwrap(),
),
object_identifier: ObjectIdentifier::new(ObjectType::ACCUMULATOR, 7).unwrap(),
},
] {
assert_value_out_of_range(
object.set_energy_meter_ref(refused.clone()),
&format!("{name} {refused:?}"),
);
assert_eq!(*object.energy_meter_ref(), meter, "{name}");
assert_eq!(read(&*object, ENERGY_METER_REF), wire, "{name}");
}
}
}
#[test]
fn energy_meter_reads_zero_while_a_reference_is_set() {
for mut object in objects() {
let name = object.object_name().to_owned();
write(&mut *object, ENERGY_METER, PropertyValue::Real(42.5)).unwrap();
assert_eq!(read(&*object, ENERGY_METER), PropertyValue::Real(42.5));
object
.set_energy_meter_ref(reference(None, ObjectType::ACCUMULATOR, 7))
.unwrap();
assert_eq!(read(&*object, ENERGY_METER), PropertyValue::Real(0.0));
for out_of_service in [false, true] {
write(
&mut *object,
PropertyIdentifier::OUT_OF_SERVICE,
PropertyValue::Boolean(out_of_service),
)
.unwrap();
for value in [5.0f32, -1.5, f32::MIN_POSITIVE, f32::NAN, f32::INFINITY] {
assert_value_out_of_range(
write(&mut *object, ENERGY_METER, PropertyValue::Real(value)),
&format!("{name} OOS={out_of_service} Energy_Meter {value}"),
);
}
assert_invalid_data_type(
write(&mut *object, ENERGY_METER, PropertyValue::Unsigned(0)),
&format!("{name} Unsigned Energy_Meter"),
);
for zero in [0.0f32, -0.0] {
write(&mut *object, ENERGY_METER, PropertyValue::Real(zero)).unwrap();
assert!(
matches!(read(&*object, ENERGY_METER), PropertyValue::Real(v) if v.to_bits() == 0),
"{name} {zero}"
);
}
}
object
.set_energy_meter_ref(reference(Some(9), ObjectType::ANALOG_INPUT, 3))
.unwrap();
assert_value_out_of_range(
write(&mut *object, ENERGY_METER, PropertyValue::Real(1.0)),
&format!("{name} remote meter"),
);
object
.set_energy_meter_ref(reference(
None,
ObjectType::ANALOG_INPUT,
ObjectIdentifier::MAX_INSTANCE,
))
.unwrap();
assert_eq!(read(&*object, ENERGY_METER), PropertyValue::Real(0.0));
write(&mut *object, ENERGY_METER, PropertyValue::Real(12.5)).unwrap();
assert_eq!(
read(&*object, ENERGY_METER),
PropertyValue::Real(12.5),
"{name}"
);
}
}