use std::sync::Arc;
use std::time::Duration;
use super::door_alarm::{DoorAlarmLists, LISTED_STATES};
use super::door_out_of_service::DoorState;
use super::*;
use crate::event::state_reporting::ChangeOfStateReporting;
use crate::traits::MonotonicClock;
pub const DEFAULT_DOOR_PULSE_TIME: u32 = 50;
pub const DEFAULT_DOOR_EXTENDED_PULSE_TIME: u32 = 150;
pub const DEFAULT_DOOR_OPEN_TOO_LONG_TIME: u32 = 300;
#[derive(Clone)]
pub struct AccessDoorObject {
oid: ObjectIdentifier,
name: String,
description: String,
present_value: DoorValue,
state: DoorState,
device_state: Option<DoorState>,
door_members: Vec<BACnetDeviceObjectReference>,
status_flags: StatusFlags,
out_of_service: bool,
alarm_lists: DoorAlarmLists,
reporting: ChangeOfStateReporting,
priority_array: [Option<DoorValue>; 16],
relinquish_default: DoorValue,
door_pulse_time: u32,
door_extended_pulse_time: u32,
door_open_too_long_time: u32,
pulse_deadlines: [Option<Duration>; 16],
monotonic_clock: Option<Arc<MonotonicClock>>,
logical_now: Duration,
}
impl AccessDoorObject {
pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
let oid = ObjectIdentifier::new(ObjectType::ACCESS_DOOR, instance)?;
Ok(Self {
oid,
name: name.into(),
description: String::new(),
present_value: DoorValue::LOCK,
state: DoorState::SECURE,
device_state: None,
door_members: Vec::new(),
status_flags: StatusFlags::empty(),
out_of_service: false,
alarm_lists: DoorAlarmLists::default(),
reporting: ChangeOfStateReporting::new(LISTED_STATES),
priority_array: Default::default(),
relinquish_default: DoorValue::LOCK,
door_pulse_time: DEFAULT_DOOR_PULSE_TIME,
door_extended_pulse_time: DEFAULT_DOOR_EXTENDED_PULSE_TIME,
door_open_too_long_time: DEFAULT_DOOR_OPEN_TOO_LONG_TIME,
pulse_deadlines: [None; 16],
monotonic_clock: None,
logical_now: Duration::ZERO,
})
}
pub fn set_relinquish_default(&mut self, value: DoorValue) -> Result<(), Error> {
if !matches!(value, DoorValue::LOCK | DoorValue::UNLOCK) {
return Err(common::value_out_of_range_error());
}
self.relinquish_default = value;
self.recalculate_present_value();
Ok(())
}
pub fn set_door_members(
&mut self,
members: impl IntoIterator<Item = impl Into<BACnetDeviceObjectReference>>,
) -> Result<(), Error> {
let members: Vec<BACnetDeviceObjectReference> =
members.into_iter().map(Into::into).collect();
crate::device_reference::check_device_members(&members)?;
self.door_members = members;
Ok(())
}
pub fn set_door_pulse_time(&mut self, tenths: u32) {
self.door_pulse_time = tenths;
}
pub fn set_door_extended_pulse_time(&mut self, tenths: u32) {
self.door_extended_pulse_time = tenths;
}
pub fn set_door_open_too_long_time(&mut self, tenths: u32) {
self.door_open_too_long_time = tenths;
}
pub fn set_door_status(&mut self, status: DoorStatus) {
self.device_state_mut().door_status = status;
}
pub fn set_lock_status(&mut self, status: LockStatus) {
self.device_state_mut().lock_status = status;
}
pub fn set_door_alarm_state(&mut self, state: DoorAlarmState) -> Result<(), Error> {
if !self.admits(state) {
return Err(common::value_out_of_range_error());
}
self.device_state_mut().door_alarm_state = state;
Ok(())
}
pub fn set_alarm_values(
&mut self,
states: impl IntoIterator<Item = DoorAlarmState>,
) -> Result<(), Error> {
self.reporting.set_alarm_values(raw_states(states))?;
self.settle_door_alarm_state();
Ok(())
}
pub fn set_fault_values(
&mut self,
states: impl IntoIterator<Item = DoorAlarmState>,
) -> Result<(), Error> {
self.alarm_lists.set_fault_values(raw_states(states))?;
self.settle_door_alarm_state();
Ok(())
}
pub fn set_masked_alarm_values(
&mut self,
states: impl IntoIterator<Item = DoorAlarmState>,
) -> Result<(), Error> {
self.alarm_lists
.set_masked_alarm_values(raw_states(states))?;
self.settle_door_alarm_state();
Ok(())
}
fn admits(&self, state: DoorAlarmState) -> bool {
self.alarm_lists
.admits(self.reporting.alarm_values(), state)
}
fn settle_door_alarm_state(&mut self) {
let (lists, alarm_values) = (&self.alarm_lists, self.reporting.alarm_values());
for state in std::iter::once(&mut self.state).chain(self.device_state.as_mut()) {
if !lists.admits(alarm_values, state.door_alarm_state) {
state.door_alarm_state = DoorAlarmState::NORMAL;
}
}
}
fn watched_alarm_state(&self) -> u32 {
self.state.door_alarm_state.to_raw()
}
fn served_reliability(&self) -> Reliability {
self.state
.reliability
.unwrap_or_else(|| self.alarm_lists.fault_state(self.state.door_alarm_state))
}
fn device_state_mut(&mut self) -> &mut DoorState {
self.device_state.as_mut().unwrap_or(&mut self.state)
}
fn recalculate_present_value(&mut self) {
self.present_value =
common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
}
fn monotonic_now(&self) -> Duration {
self.monotonic_clock
.as_ref()
.map_or(self.logical_now, |clock| clock())
}
fn command(&mut self, priority: u8, value: Option<DoorValue>) {
let index = usize::from(priority - 1);
self.pulse_deadlines[index] = None;
let pulse = match value {
Some(DoorValue::PULSE_UNLOCK) => Some(self.door_pulse_time),
Some(DoorValue::EXTENDED_PULSE_UNLOCK) => Some(self.door_extended_pulse_time),
_ => None,
};
self.priority_array[index] = match pulse {
Some(tenths)
if tenths == 0 || self.priority_array[..index].iter().any(Option::is_some) =>
{
None
}
Some(tenths) => {
let length = Duration::from_millis(u64::from(tenths) * 100);
self.pulse_deadlines[index] = Some(self.monotonic_now().saturating_add(length));
value
}
None => value,
};
self.recalculate_present_value();
}
fn expire_pulses(&mut self, now: Duration) -> bool {
let mut expired = false;
for (slot, deadline) in self
.priority_array
.iter_mut()
.zip(&mut self.pulse_deadlines)
{
if deadline.is_some_and(|deadline| now >= deadline) {
*slot = None;
*deadline = None;
expired = true;
}
}
if expired {
self.recalculate_present_value();
}
expired
}
fn in_alarm(&self) -> bool {
self.reporting.event_state() != EventState::NORMAL
}
fn secured_status(&self) -> DoorSecuredStatus {
let inputs = [
Some(!self.in_alarm()),
Some(!self.alarm_lists.masks_any()),
Some(self.present_value == DoorValue::LOCK),
door_closed(self.state.door_status),
door_locked(self.state.lock_status),
];
if inputs.contains(&Some(false)) {
DoorSecuredStatus::UNSECURED
} else if inputs.contains(&None) {
DoorSecuredStatus::UNKNOWN
} else {
DoorSecuredStatus::SECURED
}
}
}
fn door_closed(status: DoorStatus) -> Option<bool> {
match status {
DoorStatus::CLOSED | DoorStatus::UNUSED => Some(true),
DoorStatus::UNKNOWN | DoorStatus::DOOR_FAULT => None,
_ => Some(false),
}
}
fn door_locked(status: LockStatus) -> Option<bool> {
match status {
LockStatus::LOCKED | LockStatus::UNUSED => Some(true),
LockStatus::UNKNOWN | LockStatus::LOCK_FAULT => None,
_ => Some(false),
}
}
fn is_door_value(value: DoorValue) -> bool {
value.to_raw() <= DoorValue::EXTENDED_PULSE_UNLOCK.to_raw()
}
fn checked_door_value(value: PropertyValue) -> Result<DoorValue, Error> {
let PropertyValue::Enumerated(raw) = value else {
return Err(common::invalid_data_type_error());
};
let value = DoorValue::from_raw(raw);
if !is_door_value(value) {
return Err(common::value_out_of_range_error());
}
Ok(value)
}
fn checked_tenths(value: PropertyValue) -> Result<u32, Error> {
let PropertyValue::Unsigned(raw) = value else {
return Err(common::invalid_data_type_error());
};
common::u64_to_u32(raw)
}
impl BACnetObject for AccessDoorObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
&self.name
}
fn read_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
if let Some(result) = common::read_identity_properties!(self, property, array_index) {
return result;
}
if let Some(result) = self.reporting.read(property, array_index) {
return result;
}
if let Some(value) = self.alarm_lists.read(property) {
return Ok(value);
}
match property {
p if p == PropertyIdentifier::OBJECT_TYPE => {
Ok(PropertyValue::Enumerated(ObjectType::ACCESS_DOOR.to_raw()))
}
p if p == PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
self.status_flags,
self.served_reliability(),
self.out_of_service,
self.reporting.event_state(),
)),
p if p == PropertyIdentifier::OUT_OF_SERVICE => {
Ok(PropertyValue::Boolean(self.out_of_service))
}
p if p == PropertyIdentifier::RELIABILITY => Ok(PropertyValue::Enumerated(
self.served_reliability().to_raw(),
)),
p if p == PropertyIdentifier::PRESENT_VALUE => {
Ok(PropertyValue::Enumerated(self.present_value.to_raw()))
}
p if p == PropertyIdentifier::DOOR_STATUS => {
Ok(PropertyValue::Enumerated(self.state.door_status.to_raw()))
}
p if p == PropertyIdentifier::LOCK_STATUS => {
Ok(PropertyValue::Enumerated(self.state.lock_status.to_raw()))
}
p if p == PropertyIdentifier::SECURED_STATUS => {
Ok(PropertyValue::Enumerated(self.secured_status().to_raw()))
}
p if p == PropertyIdentifier::DOOR_ALARM_STATE => Ok(PropertyValue::Enumerated(
self.state.door_alarm_state.to_raw(),
)),
p if p == PropertyIdentifier::DOOR_MEMBERS => common::read_array(
crate::device_reference::reference_elements(&self.door_members),
array_index,
),
p if p == PropertyIdentifier::PRIORITY_ARRAY => {
common::read_priority_array!(self, array_index, |v: DoorValue| {
PropertyValue::Enumerated(v.to_raw())
})
}
p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
Ok(PropertyValue::Enumerated(self.relinquish_default.to_raw()))
}
p if p == PropertyIdentifier::DOOR_PULSE_TIME => {
Ok(PropertyValue::Unsigned(self.door_pulse_time.into()))
}
p if p == PropertyIdentifier::DOOR_EXTENDED_PULSE_TIME => Ok(PropertyValue::Unsigned(
self.door_extended_pulse_time.into(),
)),
p if p == PropertyIdentifier::DOOR_OPEN_TOO_LONG_TIME => {
Ok(PropertyValue::Unsigned(self.door_open_too_long_time.into()))
}
p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
Ok(common::current_command_priority(&self.priority_array))
}
_ => Err(common::unknown_property_error()),
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
priority: Option<u8>,
) -> Result<(), Error> {
if let Some(result) = common::write_out_of_service_with_restore(
&mut self.out_of_service,
&mut self.state,
&mut self.device_state,
None,
property,
&value,
) {
return result;
}
if let Some(result) = common::write_description(&mut self.description, property, &value) {
return result;
}
let (lists, alarm_values) = (&self.alarm_lists, self.reporting.alarm_values());
if let Some(result) = self.state.write(
self.out_of_service,
|state| lists.admits(alarm_values, state),
property,
&value,
) {
return result;
}
let list_edit = match property {
p if p == PropertyIdentifier::ALARM_VALUES => {
self.reporting.write(property, array_index, &value)
}
_ => self.alarm_lists.write(property, array_index, &value),
};
if let Some(result) = list_edit {
result?;
self.settle_door_alarm_state();
return Ok(());
}
if let Some(result) = self.reporting.write(property, array_index, &value) {
return result;
}
match property {
p if p == PropertyIdentifier::PRESENT_VALUE => {
let priority = priority.unwrap_or(16);
if !(1..=16).contains(&priority) {
return Err(common::value_out_of_range_error());
}
let value = match value {
PropertyValue::Null => None,
other => Some(checked_door_value(other)?),
};
self.command(priority, value);
Ok(())
}
p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
self.set_relinquish_default(checked_door_value(value)?)
}
p if p == PropertyIdentifier::DOOR_PULSE_TIME => {
self.door_pulse_time = checked_tenths(value)?;
Ok(())
}
p if p == PropertyIdentifier::DOOR_EXTENDED_PULSE_TIME => {
self.door_extended_pulse_time = checked_tenths(value)?;
Ok(())
}
p if p == PropertyIdentifier::DOOR_OPEN_TOO_LONG_TIME => {
self.door_open_too_long_time = checked_tenths(value)?;
Ok(())
}
_ => Err(crate::common::unhandled_write_error(
self.property_metadata().as_ref(),
property,
array_index,
)),
}
}
fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
super::metadata_topology::for_access_door_object(self)
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
}
fn supports_cov(&self) -> bool {
true
}
crate::event::impl_change_of_state_reporting!(
reporting,
Self::watched_alarm_state,
Self::served_reliability
);
fn advance_time_internal(&mut self, elapsed: Duration) -> bool {
self.logical_now = self.logical_now.saturating_add(elapsed);
self.expire_pulses(self.logical_now)
}
fn bind_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
self.monotonic_clock = clock;
}
fn advance_monotonic_time_internal(&mut self, now: Duration) -> bool {
self.expire_pulses(now)
}
fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
self.pulse_deadlines.iter().flatten().min().copied()
}
fn cov_snapshot_internal(&self) -> Option<Box<dyn BACnetObject>> {
Some(Box::new(self.clone()))
}
fn report_access_input_internal(&mut self, input: AccessControlInput) -> Result<(), Error> {
let AccessControlInput::DoorState(report) = input else {
return Err(common::optional_functionality_not_supported_error());
};
let (lists, alarm_values) = (&self.alarm_lists, self.reporting.alarm_values());
let device = self.device_state.as_mut().unwrap_or(&mut self.state);
device.report(report, |state| lists.admits(alarm_values, state))
}
}
fn raw_states(states: impl IntoIterator<Item = DoorAlarmState>) -> Vec<u32> {
states.into_iter().map(DoorAlarmState::to_raw).collect()
}
#[cfg(test)]
#[path = "door_secured_status_tests.rs"]
mod secured_status_tests;