use bacnet_types::enums::{
DoorAlarmState, DoorStatus, LockStatus, PropertyIdentifier, Reliability,
};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use super::{simulated_reliability, DoorStateReport};
use crate::common;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct DoorState {
pub(super) door_status: DoorStatus,
pub(super) lock_status: LockStatus,
pub(super) door_alarm_state: DoorAlarmState,
pub(super) reliability: Option<Reliability>,
}
impl DoorState {
pub(super) const SECURE: Self = Self {
door_status: DoorStatus::CLOSED,
lock_status: LockStatus::LOCKED,
door_alarm_state: DoorAlarmState::NORMAL,
reliability: None,
};
pub(super) fn write(
&mut self,
out_of_service: bool,
admits: impl Fn(DoorAlarmState) -> bool,
property: PropertyIdentifier,
value: &PropertyValue,
) -> Option<Result<(), Error>> {
const SIMULATED: [PropertyIdentifier; 4] = [
PropertyIdentifier::DOOR_STATUS,
PropertyIdentifier::LOCK_STATUS,
PropertyIdentifier::DOOR_ALARM_STATE,
PropertyIdentifier::RELIABILITY,
];
if !SIMULATED.contains(&property) {
return None;
}
if !out_of_service {
return Some(Err(common::write_access_denied_error()));
}
if property == PropertyIdentifier::RELIABILITY {
return Some(simulated_reliability(value).map(|reliability| {
self.reliability = Some(reliability);
}));
}
let PropertyValue::Enumerated(raw) = *value else {
return Some(Err(common::invalid_data_type_error()));
};
Some(match property {
p if p == PropertyIdentifier::DOOR_STATUS => {
checked(DoorStatus::from_raw(raw), door_status_in_range)
.map(|status| self.door_status = status)
}
p if p == PropertyIdentifier::LOCK_STATUS => {
checked(LockStatus::from_raw(raw), lock_status_in_range)
.map(|status| self.lock_status = status)
}
_ => checked(DoorAlarmState::from_raw(raw), door_alarm_state_in_range)
.and_then(|state| checked(state, &admits))
.map(|state| self.door_alarm_state = state),
})
}
}
impl DoorState {
pub(super) fn report(
&mut self,
report: DoorStateReport,
admits: impl Fn(DoorAlarmState) -> bool,
) -> Result<(), Error> {
let door_status = report
.door_status
.map(|status| checked(status, door_status_in_range))
.transpose()?;
let lock_status = report
.lock_status
.map(|status| checked(status, lock_status_in_range))
.transpose()?;
let door_alarm_state = report
.door_alarm_state
.map(|state| {
checked(state, door_alarm_state_in_range).and_then(|state| checked(state, &admits))
})
.transpose()?;
self.door_status = door_status.unwrap_or(self.door_status);
self.lock_status = lock_status.unwrap_or(self.lock_status);
self.door_alarm_state = door_alarm_state.unwrap_or(self.door_alarm_state);
Ok(())
}
}
fn checked<T: Copy>(value: T, in_range: impl Fn(T) -> bool) -> Result<T, Error> {
if in_range(value) {
Ok(value)
} else {
Err(common::value_out_of_range_error())
}
}
fn door_status_in_range(status: DoorStatus) -> bool {
DoorStatus::ALL_NAMED
.iter()
.any(|&(_, named)| named == status)
|| (1024..=65_535).contains(&status.to_raw())
}
fn lock_status_in_range(status: LockStatus) -> bool {
LockStatus::ALL_NAMED
.iter()
.any(|&(_, named)| named == status)
}
pub(super) fn door_alarm_state_in_range(state: DoorAlarmState) -> bool {
DoorAlarmState::ALL_NAMED
.iter()
.any(|&(_, named)| named == state)
|| (256..=65_535).contains(&state.to_raw())
}