use super::*;
use crate::audit::ObjectAuditPolicy;
use crate::command_source::{CommandDeviceBinding, CommandOrigin};
use crate::property_metadata::PropertyPresenceCondition;
use crate::traits::BACnetObject;
use bacnet_types::constructed::{BACnetAddress, BACnetColorCommand, BACnetXyColor};
use bacnet_types::enums::{
AuditLevel, ColorOperation, ErrorCode, ObjectType, PropertyIdentifier as P,
};
use bacnet_types::primitives::ObjectIdentifier;
use bacnet_types::MacAddr;
fn device(instance: u32) -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::DEVICE, instance).unwrap()
}
fn remote(instance: u32, mac: u8) -> CommandOrigin {
CommandOrigin::Remote {
actual_address: BACnetAddress {
network_number: 0,
mac_address: MacAddr::from_slice(&[mac]),
},
binding: CommandDeviceBinding::Unique(device(instance)),
}
}
fn names(object: ObjectIdentifier) -> PropertyValue {
let mut bytes = vec![0x1E, 0x1C];
bytes.extend(object.encode());
bytes.push(0x1F);
PropertyValue::ApplicationData(bytes)
}
fn none() -> PropertyValue {
PropertyValue::ApplicationData(vec![0x08])
}
fn xy(x: f32, y: f32) -> PropertyValue {
PropertyValue::List(vec![PropertyValue::Real(x), PropertyValue::Real(y)])
}
fn encoded(command: BACnetColorCommand) -> PropertyValue {
command::encode(&command)
}
fn fade(x: f32, y: f32) -> PropertyValue {
encoded(BACnetColorCommand {
target_color: Some(BACnetXyColor::new(x, y)),
fade_time: Some(1_000),
..BACnetColorCommand::new(ColorOperation::FADE_TO_COLOR)
})
}
fn stop() -> PropertyValue {
encoded(BACnetColorCommand::new(ColorOperation::STOP))
}
fn assert_code(result: Result<impl std::fmt::Debug, Error>, expected: ErrorCode) {
match result {
Err(Error::Protocol { code, .. }) => {
assert_eq!(code, expected.to_raw() as u32, "expected {expected:?}")
}
other => panic!("expected {expected:?}, got {other:?}"),
}
}
fn source(object: &dyn BACnetObject) -> PropertyValue {
object.read_property(P::VALUE_SOURCE, None).unwrap()
}
fn write_from(
object: &mut dyn BACnetObject,
property: P,
value: PropertyValue,
origin: &CommandOrigin,
) -> Result<(), Error> {
object.write_property_from(property, None, value, None, origin)
}
fn tracked_color() -> ColorObject {
let mut color = ColorObject::new(1, "CLR-1").unwrap();
color.set_value_source_tracking(true);
color
}
fn tracked_temperature() -> ColorTemperatureObject {
let mut temperature = ColorTemperatureObject::new(1, "CT-1").unwrap();
temperature.set_value_source_tracking(true);
temperature
}
#[test]
fn the_row_is_absent_until_tracked_then_required_after_transition() {
let mut color = ColorObject::new(1, "CLR-1").unwrap();
let untracked = color.property_list().into_owned();
assert!(!untracked.contains(&P::VALUE_SOURCE));
assert_code(
color.read_property(P::VALUE_SOURCE, None),
ErrorCode::UNKNOWN_PROPERTY,
);
assert_code(
write_from(&mut color, P::VALUE_SOURCE, none(), &remote(30, 1)),
ErrorCode::UNKNOWN_PROPERTY,
);
color
.write_property(P::PRESENT_VALUE, None, xy(0.2, 0.3), None)
.unwrap();
let objects: [(Box<dyn BACnetObject>, Box<dyn BACnetObject>); 2] = [
(
Box::new(ColorObject::new(1, "CLR-1").unwrap()),
Box::new(tracked_color()),
),
(
Box::new(ColorTemperatureObject::new(1, "CT-1").unwrap()),
Box::new(tracked_temperature()),
),
];
for (untracked, tracked) in objects {
let mut expected = untracked.property_list().into_owned();
expected.push(P::VALUE_SOURCE);
assert_eq!(tracked.property_list().as_ref(), expected);
assert!(tracked.required_properties().contains(&P::VALUE_SOURCE));
let metadata = tracked.property_metadata();
let row = metadata
.iter()
.find(|r| r.property_identifier == P::VALUE_SOURCE);
assert_eq!(
row.unwrap().presence_condition,
Some(PropertyPresenceCondition::ValueSourceTracking)
);
assert_eq!(source(tracked.as_ref()), none());
}
let mut color = tracked_color();
color.set_audit_policy(ObjectAuditPolicy {
level: Some(AuditLevel::AUDIT_ALL),
..ObjectAuditPolicy::default()
});
let rows: Vec<_> = color
.property_metadata()
.iter()
.map(|row| row.property_identifier)
.collect();
assert_eq!(
rows[rows.len() - 4..],
[
P::TRANSITION,
P::VALUE_SOURCE,
P::AUDIT_LEVEL,
P::PROPERTY_LIST
]
);
}
#[test]
fn present_value_and_color_command_writers_become_the_source() {
let mut color = tracked_color();
write_from(&mut color, P::PRESENT_VALUE, xy(0.2, 0.3), &remote(30, 1)).unwrap();
assert_eq!(source(&color), names(device(30)));
write_from(&mut color, P::COLOR_COMMAND, fade(0.4, 0.4), &remote(31, 2)).unwrap();
assert_eq!(source(&color), names(device(31)));
write_from(&mut color, P::COLOR_COMMAND, stop(), &remote(32, 3)).unwrap();
assert_eq!(source(&color), names(device(32)));
write_from(&mut color, P::COLOR_COMMAND, stop(), &remote(33, 4)).unwrap();
assert_eq!(source(&color), names(device(32)));
assert_code(
write_from(&mut color, P::VALUE_SOURCE, none(), &remote(33, 4)),
ErrorCode::WRITE_ACCESS_DENIED,
);
write_from(&mut color, P::VALUE_SOURCE, none(), &remote(32, 3)).unwrap();
assert_eq!(source(&color), none());
assert_code(
write_from(&mut color, P::COLOR_COMMAND, fade(2.0, 0.0), &remote(34, 5)),
ErrorCode::VALUE_OUT_OF_RANGE,
);
assert_eq!(source(&color), none());
let mut temperature = tracked_temperature();
let step = encoded(BACnetColorCommand {
step_increment: Some(100),
..BACnetColorCommand::new(ColorOperation::STEP_UP_CCT)
});
write_from(&mut temperature, P::COLOR_COMMAND, step, &remote(30, 1)).unwrap();
assert_eq!(source(&temperature), names(device(30)));
write_from(
&mut temperature,
P::PRESENT_VALUE,
PropertyValue::Unsigned(3_000),
&remote(31, 2),
)
.unwrap();
assert_eq!(source(&temperature), names(device(31)));
}
#[test]
fn writes_naming_no_writer_are_refused_and_setters_name_nobody() {
let mut color = tracked_color();
for (property, value) in [
(P::PRESENT_VALUE, xy(0.2, 0.3)),
(P::COLOR_COMMAND, fade(0.4, 0.4)),
] {
assert_code(
color.write_property(property, None, value, None),
ErrorCode::WRITE_ACCESS_DENIED,
);
}
assert_code(
color.write_property(P::VALUE_SOURCE, None, none(), None),
ErrorCode::WRITE_ACCESS_DENIED,
);
color
.write_property(
P::DEFAULT_FADE_TIME,
None,
PropertyValue::Unsigned(500),
None,
)
.unwrap();
write_from(&mut color, P::PRESENT_VALUE, xy(0.2, 0.3), &remote(30, 1)).unwrap();
color
.set_present_value(BACnetXyColor::new(0.3, 0.3))
.unwrap();
assert_eq!(source(&color), none());
assert_code(
write_from(&mut color, P::VALUE_SOURCE, none(), &remote(30, 1)),
ErrorCode::WRITE_ACCESS_DENIED,
);
write_from(&mut color, P::PRESENT_VALUE, xy(0.2, 0.3), &remote(30, 1)).unwrap();
color
.set_color_command(BACnetColorCommand::new(ColorOperation::STOP))
.unwrap();
assert_eq!(
source(&color),
names(device(30)),
"an idle STOP sets nothing"
);
let mut temperature = tracked_temperature();
write_from(
&mut temperature,
P::PRESENT_VALUE,
PropertyValue::Unsigned(5_000),
&remote(30, 1),
)
.unwrap();
temperature.set_min_max(1_000, 6_000).unwrap();
assert_eq!(source(&temperature), names(device(30)));
temperature.set_min_max(1_000, 4_000).unwrap();
assert_eq!(source(&temperature), none());
}