use super::cov_wire_test_support::*;
use super::*;
use bacnet_objects::schedule::ScheduleObject;
use bacnet_services::common::BACnetPropertyValue;
use bacnet_services::wpm::{WriteAccessSpecification, WritePropertyMultipleRequest};
use bacnet_services::write_property::WritePropertyRequest;
use bacnet_types::constructed::BACnetObjectPropertyReference;
use bacnet_types::enums::ObjectType;
use bacnet_types::error::RemoteError;
pub(super) fn sch5() -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::SCHEDULE, 5).unwrap()
}
pub(super) fn schedule(db: &mut ObjectDatabase) {
let mut schedule = ScheduleObject::new(5, "SCH-5", PropertyValue::Real(10.0)).unwrap();
schedule
.add_object_property_reference(BACnetObjectPropertyReference::new(av1(), PV.to_raw()))
.unwrap();
db.add(Box::new(schedule)).unwrap();
}
pub(super) fn from_three(value: f32) -> Vec<u8> {
[&[0x0E, 0xB4, 15, 0, 0, 0][..], &real(value), &[0x0F]].concat()
}
fn every_day(value: f32) -> Vec<u8> {
[
&[0x0E, 0x2B, 0xFF, 0xFF, 0xFF, 0x0F, 0x2E, 0xB4, 0, 0, 0, 0][..],
&real(value),
&[0x2F, 0x39, 1],
]
.concat()
}
pub(super) async fn start() -> Harness {
let mut h = Harness::start_with(ServerConfig::default(), schedule).await;
h.subscribe_cov().await;
assert_eq!(
h.cov_notification().await.list_of_values[0].value,
real(10.0)
);
h
}
pub(super) async fn read(
h: &Harness,
oid: ObjectIdentifier,
property: PropertyIdentifier,
) -> PropertyValue {
h.server
.database()
.read()
.await
.get(&oid)
.unwrap()
.read_property(property, None)
.unwrap()
}
pub(super) async fn assert_commanded(h: &Harness, value: f32) {
assert_eq!(read(h, av1(), PV).await, PropertyValue::Real(value));
assert_eq!(read(h, sch5(), PV).await, PropertyValue::Real(value));
let notification = h.cov_notification().await;
assert_eq!(notification.monitored_object_identifier, av1());
assert_eq!(notification.list_of_values[0].value, real(value));
}
pub(super) async fn write_property(
h: &mut Harness,
property: PropertyIdentifier,
index: Option<u32>,
value: Vec<u8>,
) -> Result<(), ErrorCode> {
let mut body = BytesMut::new();
WritePropertyRequest {
object_identifier: sch5(),
property_identifier: property,
property_array_index: index,
property_value: value,
priority: None,
}
.encode(&mut body)
.unwrap();
h.request(ConfirmedServiceChoice::WRITE_PROPERTY, body)
.await;
response(h).await
}
pub(super) async fn write_property_multiple(
h: &mut Harness,
properties: Vec<(PropertyIdentifier, Vec<u8>)>,
) -> Result<(), ErrorCode> {
let mut body = BytesMut::new();
WritePropertyMultipleRequest {
list_of_write_access_specs: vec![WriteAccessSpecification {
object_identifier: sch5(),
list_of_properties: properties
.into_iter()
.map(|(property, value)| BACnetPropertyValue {
property_identifier: property,
property_array_index: None,
value,
priority: None,
})
.collect(),
}],
}
.encode(&mut body)
.unwrap();
h.request(ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE, body)
.await;
response(h).await
}
#[tokio::test(start_paused = true)]
async fn write_property_of_a_weekly_day_commands_the_target_at_once() {
let mut h = start().await;
write_property(
&mut h,
PropertyIdentifier::WEEKLY_SCHEDULE,
Some(2),
from_three(21.5),
)
.await
.unwrap();
assert_commanded(&h, 21.5).await;
write_property(
&mut h,
PropertyIdentifier::WEEKLY_SCHEDULE,
None,
[0x0E, 0x0F].repeat(7),
)
.await
.unwrap();
assert_commanded(&h, 10.0).await;
h.no_notification().await;
}
#[tokio::test(start_paused = true)]
async fn a_refused_write_changes_neither_the_schedule_nor_the_target() {
let mut h = start().await;
let before = read(&h, sch5(), PropertyIdentifier::WEEKLY_SCHEDULE).await;
let duplicate = [
&[0x0E, 0xB4, 15, 0, 0, 0][..],
&real(21.5),
&[0xB4, 15, 0, 0, 0],
&real(19.0),
&[0x0F],
]
.concat();
assert_eq!(
write_property(
&mut h,
PropertyIdentifier::WEEKLY_SCHEDULE,
Some(2),
duplicate,
)
.await,
Err(ErrorCode::DUPLICATE_ENTRY)
);
assert_eq!(
read(&h, sch5(), PropertyIdentifier::WEEKLY_SCHEDULE).await,
before
);
assert_eq!(read(&h, av1(), PV).await, PropertyValue::Real(10.0));
h.no_notification().await;
}
#[tokio::test(start_paused = true)]
async fn write_property_multiple_of_exceptions_and_period_commands_the_target_at_once() {
let mut h = start().await;
write_property_multiple(
&mut h,
vec![
(PropertyIdentifier::EXCEPTION_SCHEDULE, every_day(5.0)),
(
PropertyIdentifier::EFFECTIVE_PERIOD,
vec![0xA4, 126, 9, 1, 2, 0xA4, 126, 9, 30, 3],
),
],
)
.await
.unwrap();
assert_commanded(&h, 5.0).await;
write_property(
&mut h,
PropertyIdentifier::EFFECTIVE_PERIOD,
None,
vec![0xA4, 126, 9, 1, 2, 0xA4, 126, 9, 28, 1],
)
.await
.unwrap();
assert_eq!(read(&h, sch5(), PV).await, PropertyValue::Real(5.0));
h.no_notification().await;
}
#[tokio::test(start_paused = true)]
async fn write_local_of_a_weekly_day_commands_the_target_at_once() {
let h = start().await;
h.server
.write_local(
&sch5(),
PropertyIdentifier::WEEKLY_SCHEDULE,
Some(2),
PropertyValue::ApplicationData(from_three(18.0)),
None,
crate::LocalCommandSource::ServerDevice,
)
.await
.unwrap();
assert_commanded(&h, 18.0).await;
}
const OUT_OF_SERVICE: PropertyIdentifier = PropertyIdentifier::OUT_OF_SERVICE;
const TRUE: &[u8] = &[0x11];
const FALSE: &[u8] = &[0x10];
#[tokio::test(start_paused = true)]
async fn write_property_of_present_value_follows_out_of_service() {
let mut h = start().await;
assert_eq!(
write_property(&mut h, PV, None, real(30.0)).await,
Err(ErrorCode::WRITE_ACCESS_DENIED)
);
assert_eq!(read(&h, sch5(), PV).await, PropertyValue::Real(10.0));
h.no_notification().await;
write_property(&mut h, OUT_OF_SERVICE, None, TRUE.to_vec())
.await
.unwrap();
h.no_notification().await;
write_property(&mut h, PV, None, real(30.0)).await.unwrap();
assert_commanded(&h, 30.0).await;
write_property(
&mut h,
PropertyIdentifier::WEEKLY_SCHEDULE,
Some(2),
from_three(21.5),
)
.await
.unwrap();
tokio::time::sleep(Duration::from_secs(61)).await;
assert_eq!(read(&h, sch5(), PV).await, PropertyValue::Real(30.0));
assert_eq!(read(&h, av1(), PV).await, PropertyValue::Real(30.0));
h.no_notification().await;
write_property(&mut h, PV, None, vec![0x00]).await.unwrap();
assert_eq!(read(&h, sch5(), PV).await, PropertyValue::Null);
assert_eq!(read(&h, av1(), PV).await, PropertyValue::Real(0.0));
assert_eq!(
h.cov_notification().await.list_of_values[0].value,
real(0.0)
);
write_property(&mut h, OUT_OF_SERVICE, None, FALSE.to_vec())
.await
.unwrap();
assert_commanded(&h, 21.5).await;
h.no_notification().await;
}
#[tokio::test(start_paused = true)]
async fn write_property_multiple_of_present_value_around_out_of_service() {
let mut h = start().await;
assert_eq!(
write_property_multiple(&mut h, vec![(PV, real(25.0))]).await,
Err(ErrorCode::WRITE_ACCESS_DENIED)
);
write_property_multiple(
&mut h,
vec![(OUT_OF_SERVICE, TRUE.to_vec()), (PV, real(25.0))],
)
.await
.unwrap();
assert_commanded(&h, 25.0).await;
write_property_multiple(
&mut h,
vec![(PV, real(10.0)), (OUT_OF_SERVICE, FALSE.to_vec())],
)
.await
.unwrap();
assert_commanded(&h, 10.0).await;
h.no_notification().await;
}
#[tokio::test(start_paused = true)]
async fn write_local_of_present_value_follows_out_of_service() {
let h = start().await;
let sch5 = sch5();
let write = |property: PropertyIdentifier, value: PropertyValue| {
h.server.write_local(
&sch5,
property,
None,
value,
None,
crate::LocalCommandSource::ServerDevice,
)
};
let refused = write(PV, PropertyValue::Real(18.0)).await.unwrap_err();
assert!(
matches!(refused, Error::Remote(RemoteError::Error { code, .. })
if code == ErrorCode::WRITE_ACCESS_DENIED),
"{refused:?}"
);
write(OUT_OF_SERVICE, PropertyValue::Boolean(true))
.await
.unwrap();
write(PV, PropertyValue::Real(18.0)).await.unwrap();
assert_commanded(&h, 18.0).await;
write(OUT_OF_SERVICE, PropertyValue::Boolean(false))
.await
.unwrap();
assert_commanded(&h, 10.0).await;
}
#[tokio::test(start_paused = true)]
async fn an_event_priority_out_of_range_is_value_out_of_range_over_the_wire() {
let mut h = start().await;
let before = read(&h, sch5(), PropertyIdentifier::EXCEPTION_SCHEDULE).await;
let with_priority = |encoded: &[u8]| {
let mut event = every_day(5.0);
event.truncate(event.len() - 2);
[event.as_slice(), encoded].concat()
};
for encoded in [&[0x39, 0][..], &[0x39, 17], &[0x3A, 0x01, 0x2C]] {
assert_eq!(
write_property(
&mut h,
PropertyIdentifier::EXCEPTION_SCHEDULE,
None,
with_priority(encoded),
)
.await,
Err(ErrorCode::VALUE_OUT_OF_RANGE),
"{encoded:02X?}"
);
assert_eq!(
write_property_multiple(
&mut h,
vec![(
PropertyIdentifier::EXCEPTION_SCHEDULE,
with_priority(encoded)
)],
)
.await,
Err(ErrorCode::VALUE_OUT_OF_RANGE),
"{encoded:02X?}"
);
}
assert_eq!(
read(&h, sch5(), PropertyIdentifier::EXCEPTION_SCHEDULE).await,
before
);
assert_eq!(read(&h, av1(), PV).await, PropertyValue::Real(10.0));
h.no_notification().await;
}