bacnet_objects/access_control/
door.rs1use std::sync::Arc;
2use std::time::Duration;
3
4use super::door_alarm::{DoorAlarmLists, LISTED_STATES};
5use super::door_out_of_service::DoorState;
6use super::*;
7use crate::event::state_reporting::ChangeOfStateReporting;
8use crate::traits::MonotonicClock;
9
10pub const DEFAULT_DOOR_PULSE_TIME: u32 = 50;
15pub const DEFAULT_DOOR_EXTENDED_PULSE_TIME: u32 = 150;
18pub const DEFAULT_DOOR_OPEN_TOO_LONG_TIME: u32 = 300;
21
22#[derive(Clone)]
66pub struct AccessDoorObject {
67 oid: ObjectIdentifier,
68 name: String,
69 description: String,
70 present_value: DoorValue,
71 state: DoorState,
74 device_state: Option<DoorState>,
76 door_members: Vec<BACnetDeviceObjectReference>,
77 status_flags: StatusFlags,
78 out_of_service: bool,
79 alarm_lists: DoorAlarmLists,
81 reporting: ChangeOfStateReporting,
83 priority_array: [Option<DoorValue>; 16],
85 relinquish_default: DoorValue,
86 door_pulse_time: u32,
88 door_extended_pulse_time: u32,
90 door_open_too_long_time: u32,
93 pulse_deadlines: [Option<Duration>; 16],
96 monotonic_clock: Option<Arc<MonotonicClock>>,
97 logical_now: Duration,
100}
101
102impl AccessDoorObject {
103 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
105 let oid = ObjectIdentifier::new(ObjectType::ACCESS_DOOR, instance)?;
106 Ok(Self {
107 oid,
108 name: name.into(),
109 description: String::new(),
110 present_value: DoorValue::LOCK,
111 state: DoorState::SECURE,
112 device_state: None,
113 door_members: Vec::new(),
114 status_flags: StatusFlags::empty(),
115 out_of_service: false,
116 alarm_lists: DoorAlarmLists::default(),
117 reporting: ChangeOfStateReporting::new(LISTED_STATES),
118 priority_array: Default::default(),
119 relinquish_default: DoorValue::LOCK,
120 door_pulse_time: DEFAULT_DOOR_PULSE_TIME,
121 door_extended_pulse_time: DEFAULT_DOOR_EXTENDED_PULSE_TIME,
122 door_open_too_long_time: DEFAULT_DOOR_OPEN_TOO_LONG_TIME,
123 pulse_deadlines: [None; 16],
124 monotonic_clock: None,
125 logical_now: Duration::ZERO,
126 })
127 }
128
129 pub fn set_relinquish_default(&mut self, value: DoorValue) -> Result<(), Error> {
138 if !matches!(value, DoorValue::LOCK | DoorValue::UNLOCK) {
139 return Err(common::value_out_of_range_error());
140 }
141 self.relinquish_default = value;
142 self.recalculate_present_value();
143 Ok(())
144 }
145
146 pub fn set_door_members(
155 &mut self,
156 members: impl IntoIterator<Item = impl Into<BACnetDeviceObjectReference>>,
157 ) -> Result<(), Error> {
158 let members: Vec<BACnetDeviceObjectReference> =
159 members.into_iter().map(Into::into).collect();
160 crate::device_reference::check_device_members(&members)?;
161 self.door_members = members;
162 Ok(())
163 }
164
165 pub fn set_door_pulse_time(&mut self, tenths: u32) {
167 self.door_pulse_time = tenths;
168 }
169
170 pub fn set_door_extended_pulse_time(&mut self, tenths: u32) {
172 self.door_extended_pulse_time = tenths;
173 }
174
175 pub fn set_door_open_too_long_time(&mut self, tenths: u32) {
177 self.door_open_too_long_time = tenths;
178 }
179
180 pub fn set_door_status(&mut self, status: DoorStatus) {
185 self.device_state_mut().door_status = status;
186 }
187
188 pub fn set_lock_status(&mut self, status: LockStatus) {
193 self.device_state_mut().lock_status = status;
194 }
195
196 pub fn set_door_alarm_state(&mut self, state: DoorAlarmState) -> Result<(), Error> {
209 if !self.admits(state) {
210 return Err(common::value_out_of_range_error());
211 }
212 self.device_state_mut().door_alarm_state = state;
213 Ok(())
214 }
215
216 pub fn set_alarm_values(
224 &mut self,
225 states: impl IntoIterator<Item = DoorAlarmState>,
226 ) -> Result<(), Error> {
227 self.reporting.set_alarm_values(raw_states(states))?;
228 self.settle_door_alarm_state();
229 Ok(())
230 }
231
232 pub fn set_fault_values(
236 &mut self,
237 states: impl IntoIterator<Item = DoorAlarmState>,
238 ) -> Result<(), Error> {
239 self.alarm_lists.set_fault_values(raw_states(states))?;
240 self.settle_door_alarm_state();
241 Ok(())
242 }
243
244 pub fn set_masked_alarm_values(
248 &mut self,
249 states: impl IntoIterator<Item = DoorAlarmState>,
250 ) -> Result<(), Error> {
251 self.alarm_lists
252 .set_masked_alarm_values(raw_states(states))?;
253 self.settle_door_alarm_state();
254 Ok(())
255 }
256
257 fn admits(&self, state: DoorAlarmState) -> bool {
259 self.alarm_lists
260 .admits(self.reporting.alarm_values(), state)
261 }
262
263 fn settle_door_alarm_state(&mut self) {
267 let (lists, alarm_values) = (&self.alarm_lists, self.reporting.alarm_values());
268 for state in std::iter::once(&mut self.state).chain(self.device_state.as_mut()) {
269 if !lists.admits(alarm_values, state.door_alarm_state) {
270 state.door_alarm_state = DoorAlarmState::NORMAL;
271 }
272 }
273 }
274
275 fn watched_alarm_state(&self) -> u32 {
277 self.state.door_alarm_state.to_raw()
278 }
279
280 fn served_reliability(&self) -> Reliability {
284 self.state
285 .reliability
286 .unwrap_or_else(|| self.alarm_lists.fault_state(self.state.door_alarm_state))
287 }
288
289 fn device_state_mut(&mut self) -> &mut DoorState {
292 self.device_state.as_mut().unwrap_or(&mut self.state)
293 }
294
295 fn recalculate_present_value(&mut self) {
296 self.present_value =
297 common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
298 }
299
300 fn monotonic_now(&self) -> Duration {
301 self.monotonic_clock
302 .as_ref()
303 .map_or(self.logical_now, |clock| clock())
304 }
305
306 fn command(&mut self, priority: u8, value: Option<DoorValue>) {
309 let index = usize::from(priority - 1);
310 self.pulse_deadlines[index] = None;
311 let pulse = match value {
312 Some(DoorValue::PULSE_UNLOCK) => Some(self.door_pulse_time),
313 Some(DoorValue::EXTENDED_PULSE_UNLOCK) => Some(self.door_extended_pulse_time),
314 _ => None,
315 };
316 self.priority_array[index] = match pulse {
317 Some(tenths)
320 if tenths == 0 || self.priority_array[..index].iter().any(Option::is_some) =>
321 {
322 None
323 }
324 Some(tenths) => {
325 let length = Duration::from_millis(u64::from(tenths) * 100);
326 self.pulse_deadlines[index] = Some(self.monotonic_now().saturating_add(length));
327 value
328 }
329 None => value,
330 };
331 self.recalculate_present_value();
332 }
333
334 fn expire_pulses(&mut self, now: Duration) -> bool {
336 let mut expired = false;
337 for (slot, deadline) in self
338 .priority_array
339 .iter_mut()
340 .zip(&mut self.pulse_deadlines)
341 {
342 if deadline.is_some_and(|deadline| now >= deadline) {
343 *slot = None;
344 *deadline = None;
345 expired = true;
346 }
347 }
348 if expired {
349 self.recalculate_present_value();
350 }
351 expired
352 }
353
354 fn in_alarm(&self) -> bool {
358 self.reporting.event_state() != EventState::NORMAL
359 }
360
361 fn secured_status(&self) -> DoorSecuredStatus {
378 let inputs = [
379 Some(!self.in_alarm()),
380 Some(!self.alarm_lists.masks_any()),
382 Some(self.present_value == DoorValue::LOCK),
383 door_closed(self.state.door_status),
384 door_locked(self.state.lock_status),
385 ];
386 if inputs.contains(&Some(false)) {
387 DoorSecuredStatus::UNSECURED
388 } else if inputs.contains(&None) {
389 DoorSecuredStatus::UNKNOWN
390 } else {
391 DoorSecuredStatus::SECURED
392 }
393 }
394}
395
396fn door_closed(status: DoorStatus) -> Option<bool> {
401 match status {
402 DoorStatus::CLOSED | DoorStatus::UNUSED => Some(true),
403 DoorStatus::UNKNOWN | DoorStatus::DOOR_FAULT => None,
404 _ => Some(false),
405 }
406}
407
408fn door_locked(status: LockStatus) -> Option<bool> {
411 match status {
412 LockStatus::LOCKED | LockStatus::UNUSED => Some(true),
413 LockStatus::UNKNOWN | LockStatus::LOCK_FAULT => None,
414 _ => Some(false),
415 }
416}
417
418fn is_door_value(value: DoorValue) -> bool {
422 value.to_raw() <= DoorValue::EXTENDED_PULSE_UNLOCK.to_raw()
423}
424
425fn checked_door_value(value: PropertyValue) -> Result<DoorValue, Error> {
427 let PropertyValue::Enumerated(raw) = value else {
428 return Err(common::invalid_data_type_error());
429 };
430 let value = DoorValue::from_raw(raw);
431 if !is_door_value(value) {
432 return Err(common::value_out_of_range_error());
433 }
434 Ok(value)
435}
436
437fn checked_tenths(value: PropertyValue) -> Result<u32, Error> {
439 let PropertyValue::Unsigned(raw) = value else {
440 return Err(common::invalid_data_type_error());
441 };
442 common::u64_to_u32(raw)
443}
444
445impl BACnetObject for AccessDoorObject {
446 fn object_identifier(&self) -> ObjectIdentifier {
447 self.oid
448 }
449
450 fn object_name(&self) -> &str {
451 &self.name
452 }
453
454 fn read_property(
455 &self,
456 property: PropertyIdentifier,
457 array_index: Option<u32>,
458 ) -> Result<PropertyValue, Error> {
459 if let Some(result) = common::read_identity_properties!(self, property, array_index) {
460 return result;
461 }
462 if let Some(result) = self.reporting.read(property, array_index) {
463 return result;
464 }
465 if let Some(value) = self.alarm_lists.read(property) {
466 return Ok(value);
467 }
468 match property {
469 p if p == PropertyIdentifier::OBJECT_TYPE => {
470 Ok(PropertyValue::Enumerated(ObjectType::ACCESS_DOOR.to_raw()))
471 }
472 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
475 self.status_flags,
476 self.served_reliability(),
477 self.out_of_service,
478 self.reporting.event_state(),
479 )),
480 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
481 Ok(PropertyValue::Boolean(self.out_of_service))
482 }
483 p if p == PropertyIdentifier::RELIABILITY => Ok(PropertyValue::Enumerated(
484 self.served_reliability().to_raw(),
485 )),
486 p if p == PropertyIdentifier::PRESENT_VALUE => {
487 Ok(PropertyValue::Enumerated(self.present_value.to_raw()))
488 }
489 p if p == PropertyIdentifier::DOOR_STATUS => {
490 Ok(PropertyValue::Enumerated(self.state.door_status.to_raw()))
491 }
492 p if p == PropertyIdentifier::LOCK_STATUS => {
493 Ok(PropertyValue::Enumerated(self.state.lock_status.to_raw()))
494 }
495 p if p == PropertyIdentifier::SECURED_STATUS => {
496 Ok(PropertyValue::Enumerated(self.secured_status().to_raw()))
497 }
498 p if p == PropertyIdentifier::DOOR_ALARM_STATE => Ok(PropertyValue::Enumerated(
499 self.state.door_alarm_state.to_raw(),
500 )),
501 p if p == PropertyIdentifier::DOOR_MEMBERS => common::read_array(
502 crate::device_reference::reference_elements(&self.door_members),
503 array_index,
504 ),
505 p if p == PropertyIdentifier::PRIORITY_ARRAY => {
506 common::read_priority_array!(self, array_index, |v: DoorValue| {
507 PropertyValue::Enumerated(v.to_raw())
508 })
509 }
510 p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
511 Ok(PropertyValue::Enumerated(self.relinquish_default.to_raw()))
512 }
513 p if p == PropertyIdentifier::DOOR_PULSE_TIME => {
514 Ok(PropertyValue::Unsigned(self.door_pulse_time.into()))
515 }
516 p if p == PropertyIdentifier::DOOR_EXTENDED_PULSE_TIME => Ok(PropertyValue::Unsigned(
517 self.door_extended_pulse_time.into(),
518 )),
519 p if p == PropertyIdentifier::DOOR_OPEN_TOO_LONG_TIME => {
520 Ok(PropertyValue::Unsigned(self.door_open_too_long_time.into()))
521 }
522 p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
525 Ok(common::current_command_priority(&self.priority_array))
526 }
527 _ => Err(common::unknown_property_error()),
528 }
529 }
530
531 fn write_property(
532 &mut self,
533 property: PropertyIdentifier,
534 array_index: Option<u32>,
535 value: PropertyValue,
536 priority: Option<u8>,
537 ) -> Result<(), Error> {
538 if let Some(result) = common::write_out_of_service_with_restore(
541 &mut self.out_of_service,
542 &mut self.state,
543 &mut self.device_state,
544 None,
545 property,
546 &value,
547 ) {
548 return result;
549 }
550 if let Some(result) = common::write_description(&mut self.description, property, &value) {
551 return result;
552 }
553 let (lists, alarm_values) = (&self.alarm_lists, self.reporting.alarm_values());
554 if let Some(result) = self.state.write(
555 self.out_of_service,
556 |state| lists.admits(alarm_values, state),
557 property,
558 &value,
559 ) {
560 return result;
561 }
562 let list_edit = match property {
565 p if p == PropertyIdentifier::ALARM_VALUES => {
566 self.reporting.write(property, array_index, &value)
567 }
568 _ => self.alarm_lists.write(property, array_index, &value),
569 };
570 if let Some(result) = list_edit {
571 result?;
572 self.settle_door_alarm_state();
573 return Ok(());
574 }
575 if let Some(result) = self.reporting.write(property, array_index, &value) {
576 return result;
577 }
578 match property {
579 p if p == PropertyIdentifier::PRESENT_VALUE => {
582 let priority = priority.unwrap_or(16);
583 if !(1..=16).contains(&priority) {
584 return Err(common::value_out_of_range_error());
585 }
586 let value = match value {
587 PropertyValue::Null => None,
588 other => Some(checked_door_value(other)?),
589 };
590 self.command(priority, value);
591 Ok(())
592 }
593 p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
596 self.set_relinquish_default(checked_door_value(value)?)
597 }
598 p if p == PropertyIdentifier::DOOR_PULSE_TIME => {
599 self.door_pulse_time = checked_tenths(value)?;
600 Ok(())
601 }
602 p if p == PropertyIdentifier::DOOR_EXTENDED_PULSE_TIME => {
603 self.door_extended_pulse_time = checked_tenths(value)?;
604 Ok(())
605 }
606 p if p == PropertyIdentifier::DOOR_OPEN_TOO_LONG_TIME => {
607 self.door_open_too_long_time = checked_tenths(value)?;
608 Ok(())
609 }
610 _ => Err(crate::common::unhandled_write_error(
611 self.property_metadata().as_ref(),
612 property,
613 array_index,
614 )),
615 }
616 }
617
618 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
619 super::metadata_topology::for_access_door_object(self)
620 }
621
622 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
623 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
624 }
625
626 fn supports_cov(&self) -> bool {
627 true
628 }
629
630 crate::event::impl_change_of_state_reporting!(
631 reporting,
632 Self::watched_alarm_state,
633 Self::served_reliability
634 );
635
636 fn advance_time_internal(&mut self, elapsed: Duration) -> bool {
637 self.logical_now = self.logical_now.saturating_add(elapsed);
638 self.expire_pulses(self.logical_now)
639 }
640
641 fn bind_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
642 self.monotonic_clock = clock;
643 }
644
645 fn advance_monotonic_time_internal(&mut self, now: Duration) -> bool {
646 self.expire_pulses(now)
647 }
648
649 fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
650 self.pulse_deadlines.iter().flatten().min().copied()
651 }
652
653 fn cov_snapshot_internal(&self) -> Option<Box<dyn BACnetObject>> {
654 Some(Box::new(self.clone()))
655 }
656}
657
658fn raw_states(states: impl IntoIterator<Item = DoorAlarmState>) -> Vec<u32> {
660 states.into_iter().map(DoorAlarmState::to_raw).collect()
661}
662
663#[cfg(test)]
664#[path = "door_secured_status_tests.rs"]
665mod secured_status_tests;
666
667