use super::channel_wire_tests::{ch, channel, member, settled, start, write_channel, write_wire};
use super::command_action_wire_tests::{ao, outputs, read_db, read_wire, slot8};
use super::cov_wire_test_support::*;
use super::*;
use bacnet_objects::binary::BinaryOutputObject;
use bacnet_types::enums::{Reliability, WriteStatus};
const RELIABILITY: PropertyIdentifier = PropertyIdentifier::RELIABILITY;
async fn reliability(h: &mut Harness, instance: u32) -> (Reliability, bool) {
let reliability = match read_wire(h, ch(instance), RELIABILITY, None).await.unwrap()[..] {
[0x91, raw] => Reliability::from_raw(raw.into()),
ref other => panic!("Reliability read {other:?}"),
};
let fault = match read_wire(h, ch(instance), SF, None).await.unwrap()[..] {
[0x82, 0x04, flags] => flags & 0x40 != 0,
ref other => panic!("Status_Flags read {other:?}"),
};
(reliability, fault)
}
async fn distribute(h: &mut Harness, instance: u32, value: PropertyValue) -> WriteStatus {
write_channel(h, instance, &value, Some(8)).await.unwrap();
settled(h, instance).await
}
#[tokio::test(start_paused = true)]
async fn channel_member_that_refuses_is_a_process_error_until_a_good_run_clears_it() {
let mut h = start().await;
assert_eq!(
reliability(&mut h, 1).await,
(Reliability::NO_FAULT_DETECTED, false)
);
let failed = distribute(&mut h, 1, PropertyValue::Unsigned(2)).await;
assert_eq!(failed, WriteStatus::FAILED);
assert_eq!(
reliability(&mut h, 1).await,
(Reliability::PROCESS_ERROR, true)
);
let cleared = distribute(&mut h, 1, PropertyValue::Real(1.0)).await;
assert_eq!(cleared, WriteStatus::SUCCESSFUL);
assert_eq!(
reliability(&mut h, 1).await,
(Reliability::NO_FAULT_DETECTED, false)
);
let relinquished = distribute(&mut h, 1, PropertyValue::Null).await;
assert_eq!(relinquished, WriteStatus::SUCCESSFUL);
assert_eq!(
reliability(&mut h, 1).await,
(Reliability::NO_FAULT_DETECTED, false)
);
}
#[tokio::test(start_paused = true)]
async fn channel_member_naming_a_missing_object_is_a_configuration_error() {
let mut h = start().await;
write_channel(&mut h, 3, &PropertyValue::Real(30.0), Some(10))
.await
.unwrap();
tokio::time::sleep(Duration::from_millis(150)).await;
assert_eq!(
reliability(&mut h, 3).await,
(Reliability::NO_FAULT_DETECTED, false)
);
tokio::time::sleep(Duration::from_millis(60)).await;
assert_eq!(settled(&mut h, 3).await, WriteStatus::FAILED);
assert_eq!(
reliability(&mut h, 3).await,
(Reliability::CONFIGURATION_ERROR, true)
);
}
#[tokio::test(start_paused = true)]
async fn channel_value_a_member_cannot_take_is_a_configuration_error() {
let mut h = start().await;
let failed = distribute(&mut h, 1, PropertyValue::CharacterString("scene".into())).await;
assert_eq!(failed, WriteStatus::FAILED);
assert_eq!(
reliability(&mut h, 1).await,
(Reliability::CONFIGURATION_ERROR, true)
);
let refused = distribute(&mut h, 1, PropertyValue::Boolean(false)).await;
assert_eq!(refused, WriteStatus::FAILED);
assert_eq!(
reliability(&mut h, 1).await,
(Reliability::PROCESS_ERROR, true)
);
}
#[tokio::test(start_paused = true)]
async fn channel_reliability_takes_a_client_value_only_out_of_service() {
let mut h = start().await;
let process_error = vec![0x91, Reliability::PROCESS_ERROR.to_raw() as u8];
let refused = write_wire(
&mut h,
ch(1),
RELIABILITY,
None,
process_error.clone(),
None,
)
.await;
assert_eq!(
refused.unwrap_err().error_code,
ErrorCode::WRITE_ACCESS_DENIED
);
let failed = distribute(&mut h, 1, PropertyValue::Unsigned(2)).await;
assert_eq!(failed, WriteStatus::FAILED);
let out_of_service = PropertyIdentifier::OUT_OF_SERVICE;
write_wire(&mut h, ch(1), out_of_service, None, vec![0x11], None)
.await
.unwrap();
let simulated = vec![0x91, Reliability::NO_FAULT_DETECTED.to_raw() as u8];
write_wire(&mut h, ch(1), RELIABILITY, None, simulated, None)
.await
.unwrap();
assert_eq!(
reliability(&mut h, 1).await,
(Reliability::NO_FAULT_DETECTED, false)
);
write_wire(&mut h, ch(1), out_of_service, None, vec![0x10], None)
.await
.unwrap();
assert_eq!(
reliability(&mut h, 1).await,
(Reliability::PROCESS_ERROR, true)
);
}
async fn start_ordered() -> Harness {
let bo1 = ObjectIdentifier::new(ObjectType::BINARY_OUTPUT, 1).unwrap();
let ao9 = member(ao(9), PV);
Harness::start_with(ServerConfig::default(), move |db| {
outputs(db);
db.add(Box::new(BinaryOutputObject::new(1, "BO-1").unwrap()))
.unwrap();
let bo1 = member(bo1, PV);
let channels = [
(4, vec![(ao9.clone(), 0), (bo1.clone(), 0)]),
(5, vec![(bo1, 0), (ao9.clone(), 0)]),
(6, vec![(ao9, 0), (member(ao(2), PV), 1000)]),
];
for (instance, members) in channels {
db.add(Box::new(channel(instance, 30, members))).unwrap();
}
})
.await
}
#[tokio::test(start_paused = true)]
async fn channel_reliability_names_the_first_failure_in_write_order() {
let mut h = start_ordered().await;
for (instance, first) in [
(4, Reliability::CONFIGURATION_ERROR),
(5, Reliability::PROCESS_ERROR),
] {
let failed = distribute(&mut h, instance, PropertyValue::Unsigned(2)).await;
assert_eq!(failed, WriteStatus::FAILED);
assert_eq!(reliability(&mut h, instance).await, (first, true));
}
}
#[tokio::test(start_paused = true)]
async fn stop_after_a_member_failed_keeps_that_failure() {
let mut h = start_ordered().await;
write_channel(&mut h, 6, &PropertyValue::Real(5.0), Some(8))
.await
.unwrap();
tokio::time::sleep(Duration::from_millis(500)).await;
h.server.stop().await.unwrap();
assert_eq!(
read_db(&h, ch(6), PropertyIdentifier::WRITE_STATUS, None).await,
PropertyValue::Enumerated(WriteStatus::FAILED.to_raw())
);
assert_eq!(
read_db(&h, ch(6), RELIABILITY, None).await,
PropertyValue::Enumerated(Reliability::CONFIGURATION_ERROR.to_raw())
);
assert_eq!(slot8(&h, ao(2)).await, PropertyValue::Null);
}