use super::*;
use bacnet_encoding::constructed::encode_object_property_reference;
use bacnet_objects::binary::BinaryValueObject;
use bacnet_types::constructed::BACnetObjectPropertyReference;
use bytes::BytesMut;
fn ai() -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap()
}
fn switch() -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::BINARY_VALUE, 1).unwrap()
}
fn texts(texts: [&str; 3]) -> PropertyValue {
PropertyValue::List(
texts
.map(|text| PropertyValue::CharacterString(text.into()))
.to_vec(),
)
}
fn reference_to_switch() -> PropertyValue {
let mut octets = BytesMut::new();
encode_object_property_reference(
&mut octets,
&BACnetObjectPropertyReference::new(switch(), PropertyIdentifier::PRESENT_VALUE.to_raw()),
);
PropertyValue::ApplicationData(octets.to_vec())
}
fn event_state(db: &ObjectDatabase) -> PropertyValue {
db.get(&ai())
.unwrap()
.read_property(PropertyIdentifier::EVENT_STATE, None)
.unwrap()
}
async fn inhibited_fixture(active: bool) -> Arc<tokio::sync::RwLock<ObjectDatabase>> {
let db = db_with_high_limit_transition(0x80);
{
let mut db = db.write().await;
let mut bv = BinaryValueObject::new(1, "BV-1").unwrap();
bv.write_property_from(
PropertyIdentifier::PRESENT_VALUE,
None,
PropertyValue::Enumerated(u32::from(active)),
Some(8),
&crate::command_source::test_origin(),
)
.unwrap();
db.add(Box::new(bv)).unwrap();
db.get_mut(&ai())
.unwrap()
.write_property(
PropertyIdentifier::EVENT_ALGORITHM_INHIBIT_REF,
None,
reference_to_switch(),
None,
)
.unwrap();
}
db
}
#[tokio::test]
async fn a_configured_message_text_is_the_one_sent_and_stored() {
let db = db_with_high_limit_transition(0x80);
db.write()
.await
.get_mut(&ai())
.unwrap()
.write_property(
PropertyIdentifier::EVENT_MESSAGE_TEXTS_CONFIG,
None,
texts(["Too hot", "", ""]),
None,
)
.unwrap();
let sent = broadcasts_from_per_write_path(&db, DccState::Enable).await;
let notification = decode_broadcast_notification(&sent);
assert_eq!(notification.message_text, Some("Too hot".into()));
assert_eq!(
db.read()
.await
.get(&ai())
.unwrap()
.read_property(PropertyIdentifier::EVENT_MESSAGE_TEXTS, Some(1))
.unwrap(),
PropertyValue::CharacterString("Too hot".into())
);
}
#[tokio::test]
async fn the_write_path_follows_the_reference_before_it_evaluates() {
let db = inhibited_fixture(true).await;
let sent = broadcasts_from_per_write_path(&db, DccState::Enable).await;
assert!(sent.is_empty(), "an inhibited algorithm reports nothing");
{
let db = db.read().await;
assert_eq!(
db.get(&ai())
.unwrap()
.read_property(PropertyIdentifier::EVENT_ALGORITHM_INHIBIT, None)
.unwrap(),
PropertyValue::Boolean(true)
);
assert_eq!(
event_state(&db),
PropertyValue::Enumerated(EventState::NORMAL.to_raw())
);
}
db.write()
.await
.get_mut(&switch())
.unwrap()
.write_property_from(
PropertyIdentifier::PRESENT_VALUE,
None,
PropertyValue::Enumerated(0),
Some(8),
&crate::command_source::test_origin(),
)
.unwrap();
let sent = broadcasts_from_per_write_path(&db, DccState::Enable).await;
assert_eq!(
decode_broadcast_notification(&sent).to_state,
EventState::HIGH_LIMIT
);
}
#[tokio::test(start_paused = true)]
async fn the_tick_follows_the_reference_within_a_second() {
let (transport, sent) = recording_transport();
let Ok(db) = Arc::try_unwrap(inhibited_fixture(true).await) else {
panic!("the fixture holds the only handle");
};
let db = db.into_inner();
let server = BACnetServer::start(ServerConfig::default(), db, transport)
.await
.expect("server should start");
tokio::time::sleep(Duration::from_secs(3)).await;
assert!(sent.is_empty(), "inhibited while the switch is ACTIVE");
server
.write_local(
&switch(),
PropertyIdentifier::PRESENT_VALUE,
None,
PropertyValue::Enumerated(0),
Some(8),
crate::LocalCommandSource::ServerDevice,
)
.await
.expect("local write should succeed");
tokio::time::sleep(Duration::from_secs(3)).await;
assert_eq!(
decode_broadcast_notification(&sent.npdus()).to_state,
EventState::HIGH_LIMIT
);
server
.write_local(
&switch(),
PropertyIdentifier::PRESENT_VALUE,
None,
PropertyValue::Enumerated(1),
Some(8),
crate::LocalCommandSource::ServerDevice,
)
.await
.expect("local write should succeed");
tokio::time::sleep(Duration::from_secs(2)).await;
assert_eq!(
event_state(&*server.database().read().await),
PropertyValue::Enumerated(EventState::NORMAL.to_raw())
);
}