use super::cov_wire_test_support::*;
use super::*;
use bacnet_objects::accumulator::PulseConverterObject;
use bacnet_objects::multistate::MultiStateValueObject;
use bacnet_services::cov::{COVNotificationRequest, SubscribeCOVRequest};
use bacnet_services::object_mgmt::{CreateObjectRequest, DeleteObjectRequest, ObjectSpecifier};
use bacnet_types::constructed::BACnetObjectPropertyReference;
use bacnet_types::enums::{EngineeringUnits, ObjectType, Reliability};
const FAULT: u8 = 0x40;
fn pc1() -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::PULSE_CONVERTER, 1).unwrap()
}
fn msv5() -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::MULTI_STATE_VALUE, 5).unwrap()
}
fn with_converter(db: &mut ObjectDatabase) {
let mut pc = PulseConverterObject::new(1, "PC-1", EngineeringUnits::NO_UNITS).unwrap();
pc.set_input_reference(BACnetObjectPropertyReference::new(msv5(), PV.to_raw()));
db.add(Box::new(pc)).unwrap();
}
async fn subscribe(h: &mut Harness) {
let mut body = BytesMut::new();
SubscribeCOVRequest {
subscriber_process_identifier: 1341,
monitored_object_identifier: pc1(),
issue_confirmed_notifications: Some(false),
lifetime: Some(300),
}
.encode(&mut body)
.unwrap();
h.request(ConfirmedServiceChoice::SUBSCRIBE_COV, body).await;
}
fn flags(notification: &COVNotificationRequest) -> u8 {
assert_eq!(notification.monitored_object_identifier, pc1());
let value = ¬ification
.list_of_values
.iter()
.find(|value| value.property_identifier == SF)
.expect("Status_Flags reported")
.value;
assert_eq!(&value[..2], &[0x82, 0x04], "four-bit Status_Flags");
value[2]
}
async fn reliability(h: &Harness) -> PropertyValue {
h.server
.database()
.read()
.await
.get(&pc1())
.unwrap()
.read_property(PropertyIdentifier::RELIABILITY, None)
.unwrap()
}
fn configuration_error() -> PropertyValue {
PropertyValue::Enumerated(Reliability::CONFIGURATION_ERROR.to_raw())
}
fn no_fault() -> PropertyValue {
PropertyValue::Enumerated(Reliability::NO_FAULT_DETECTED.to_raw())
}
#[tokio::test(start_paused = true)]
async fn an_applications_add_and_remove_report_the_fault_without_a_write() {
let mut h = Harness::start_with(ServerConfig::default(), with_converter).await;
subscribe(&mut h).await;
assert_eq!(flags(&h.cov_notification().await), FAULT);
assert_eq!(reliability(&h).await, configuration_error());
h.server
.database()
.write()
.await
.add(Box::new(MultiStateValueObject::new(5, "MSV-5", 3).unwrap()))
.unwrap();
assert_eq!(flags(&h.cov_notification().await), 0);
assert_eq!(reliability(&h).await, no_fault());
h.server.database().write().await.remove(&msv5()).unwrap();
assert_eq!(flags(&h.cov_notification().await), FAULT);
assert_eq!(reliability(&h).await, configuration_error());
}
#[tokio::test(start_paused = true)]
async fn create_object_and_delete_object_report_the_fault_they_move() {
let mut h = Harness::start_with(ServerConfig::default(), with_converter).await;
subscribe(&mut h).await;
assert_eq!(flags(&h.cov_notification().await), FAULT);
h.server
.database()
.write()
.await
.set_membership_waker_internal(None);
let mut body = BytesMut::new();
CreateObjectRequest {
object_specifier: ObjectSpecifier::Identifier(msv5()),
list_of_initial_values: Vec::new(),
}
.encode(&mut body);
h.request(ConfirmedServiceChoice::CREATE_OBJECT, body).await;
assert_eq!(flags(&h.cov_notification().await), 0);
assert_eq!(reliability(&h).await, no_fault());
let mut body = BytesMut::new();
DeleteObjectRequest {
object_identifier: msv5(),
}
.encode(&mut body);
h.request(ConfirmedServiceChoice::DELETE_OBJECT, body).await;
assert_eq!(flags(&h.cov_notification().await), FAULT);
assert_eq!(reliability(&h).await, configuration_error());
}
#[tokio::test(start_paused = true)]
async fn a_local_write_of_input_reference_is_judged_at_once() {
let h = Harness::start_with(ServerConfig::default(), |db| {
with_converter(db);
db.add(Box::new(MultiStateValueObject::new(5, "MSV-5", 3).unwrap()))
.unwrap();
})
.await;
assert_eq!(reliability(&h).await, no_fault());
let analog_value = vec![0x0C, 0x00, 0x80, 0x00, 0x01, 0x19, 0x55];
let multi_state_value = vec![0x0C, 0x04, 0xC0, 0x00, 0x05, 0x19, 0x55];
for (octets, expected) in [
(analog_value, configuration_error()),
(multi_state_value, no_fault()),
] {
h.server
.write_local(
&pc1(),
PropertyIdentifier::INPUT_REFERENCE,
None,
PropertyValue::ApplicationData(octets),
None,
crate::LocalCommandSource::ServerDevice,
)
.await
.unwrap();
assert_eq!(reliability(&h).await, expected);
}
}
#[tokio::test(start_paused = true)]
async fn a_running_server_counts_the_input_into_count_and_reports_it() {
let mut h = Harness::start_with(ServerConfig::default(), |db| {
with_converter(db);
db.add(Box::new(
MultiStateValueObject::new(5, "MSV-5", 10).unwrap(),
))
.unwrap();
})
.await;
subscribe(&mut h).await;
h.cov_notification().await;
h.server
.write_local(
&msv5(),
PV,
None,
PropertyValue::Unsigned(4),
Some(8),
crate::LocalCommandSource::ServerDevice,
)
.await
.unwrap();
tokio::time::sleep(Duration::from_secs(2)).await;
let count = h
.server
.database()
.read()
.await
.get(&pc1())
.unwrap()
.read_property(PropertyIdentifier::COUNT, None)
.unwrap();
assert_eq!(count, PropertyValue::Unsigned(3));
let notification = h.cov_notification().await;
let present_value = ¬ification
.list_of_values
.iter()
.find(|value| value.property_identifier == PV)
.expect("Present_Value reported")
.value;
assert_eq!(present_value, &[0x44, 0x40, 0x40, 0x00, 0x00]);
}