bacnet_objects/access_control/door.rs
1use 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
10// AccessDoorObject (type 30)
11// ---------------------------------------------------------------------------
12
13/// Door_Pulse_Time a new door starts with, in tenths of a second (5 s).
14pub const DEFAULT_DOOR_PULSE_TIME: u32 = 50;
15/// Door_Extended_Pulse_Time a new door starts with, in tenths of a second
16/// (15 s).
17pub const DEFAULT_DOOR_EXTENDED_PULSE_TIME: u32 = 150;
18/// Door_Open_Too_Long_Time a new door starts with, in tenths of a second
19/// (30 s).
20pub const DEFAULT_DOOR_OPEN_TOO_LONG_TIME: u32 = 300;
21
22/// BACnet Access Door object (type 30).
23///
24/// Represents a physical door or barrier in an access control system.
25/// Present value carries the door command (BACnetDoorValue); Door_Status
26/// reports the physical DoorStatus.
27///
28/// A PULSE_UNLOCK or EXTENDED_PULSE_UNLOCK command holds its priority slot
29/// for Door_Pulse_Time or Door_Extended_Pulse_Time and is then relinquished,
30/// so the door relocks (Clauses 12.26.4, 12.26.16 and 12.26.17; #1073). The
31/// deadline is taken from the database's monotonic clock when the command is
32/// written, and the server's monotonic operation task relinquishes the slot
33/// at that deadline, the mechanism Binary Lighting Output egress uses. A
34/// pulse written below a priority already commanded is relinquished at once,
35/// and a pulse time of zero relinquishes at once too. Changing a pulse time
36/// leaves an armed deadline as it was.
37///
38/// While Out_Of_Service is TRUE a client can simulate Door_Status,
39/// Lock_Status and Door_Alarm_State by writing them, and the door's own values
40/// come back on the return to service (Clause 12.26.9, Table 12-30 footnote 1;
41/// #1131). The module `door_out_of_service` has the details.
42///
43/// Secured_Status isn't stored: each read works it out from what the door
44/// serves (Clause 12.26.14; #1148), so it moves with every command, relock,
45/// device report and simulated value. See `secured_status` for how the
46/// inputs combine and when the answer is UNKNOWN.
47///
48/// The door reports intrinsically on Door_Alarm_State with the
49/// CHANGE_OF_STATE algorithm (Clause 12.26; #1149): Event_State goes
50/// OFFNORMAL once Door_Alarm_State has stayed in Alarm_Values for Time_Delay
51/// seconds and back to NORMAL once it has stayed out of them for
52/// Time_Delay_Normal. Fault_Values feeds the FAULT_STATE algorithm, so a
53/// Door_Alarm_State among them makes Reliability MULTI_STATE_FAULT and
54/// Event_State FAULT. Masked_Alarm_Values keeps Door_Alarm_State out of the
55/// states it lists. The module `door_alarm` has the rules the three lists
56/// set; the event rows are served and written through
57/// `ChangeOfStateReporting`, and the server sends the notifications to the
58/// Notification Class recipients.
59///
60/// The application decides when the door is in alarm and reports it with
61/// [`Self::set_door_alarm_state`]; Clause 12.26.20 leaves that to the
62/// device. That includes DOOR_OPEN_TOO_LONG: the door serves
63/// Door_Open_Too_Long_Time for the application's logic to read but runs no
64/// timer of its own.
65///
66/// While the server holds the door, the application reports its
67/// Door_Status, Lock_Status and Door_Alarm_State through
68/// `BACnetServer::report_door_state_local` (#1132), which stores them as the
69/// setters do, out of service included.
70#[derive(Clone)]
71pub struct AccessDoorObject {
72 oid: ObjectIdentifier,
73 name: String,
74 description: String,
75 present_value: DoorValue,
76 /// Door_Status, Lock_Status and Door_Alarm_State as served: the device's,
77 /// or a client's simulation while Out_Of_Service is TRUE.
78 state: DoorState,
79 /// The device's own door state, put aside while Out_Of_Service is TRUE.
80 device_state: Option<DoorState>,
81 door_members: Vec<BACnetDeviceObjectReference>,
82 status_flags: StatusFlags,
83 out_of_service: bool,
84 /// Fault_Values and Masked_Alarm_Values.
85 alarm_lists: DoorAlarmLists,
86 /// Intrinsic reporting on Door_Alarm_State, Alarm_Values included.
87 reporting: ChangeOfStateReporting,
88 /// 16-level priority array for commandable Present_Value.
89 priority_array: [Option<DoorValue>; 16],
90 relinquish_default: DoorValue,
91 /// Door_Pulse_Time, tenths of a second.
92 door_pulse_time: u32,
93 /// Door_Extended_Pulse_Time, tenths of a second.
94 door_extended_pulse_time: u32,
95 /// Door_Open_Too_Long_Time, tenths of a second. Stored and served for
96 /// the application, which decides when the door has been open too long.
97 door_open_too_long_time: u32,
98 /// For each priority slot holding a pulse, the monotonic instant it is
99 /// relinquished.
100 pulse_deadlines: [Option<Duration>; 16],
101 monotonic_clock: Option<Arc<MonotonicClock>>,
102 /// Elapsed time for a door with no bound clock, advanced by
103 /// `advance_time_internal`.
104 logical_now: Duration,
105}
106
107impl AccessDoorObject {
108 /// Create a new Access Door object.
109 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
110 let oid = ObjectIdentifier::new(ObjectType::ACCESS_DOOR, instance)?;
111 Ok(Self {
112 oid,
113 name: name.into(),
114 description: String::new(),
115 present_value: DoorValue::LOCK,
116 state: DoorState::SECURE,
117 device_state: None,
118 door_members: Vec::new(),
119 status_flags: StatusFlags::empty(),
120 out_of_service: false,
121 alarm_lists: DoorAlarmLists::default(),
122 reporting: ChangeOfStateReporting::new(LISTED_STATES),
123 priority_array: Default::default(),
124 relinquish_default: DoorValue::LOCK,
125 door_pulse_time: DEFAULT_DOOR_PULSE_TIME,
126 door_extended_pulse_time: DEFAULT_DOOR_EXTENDED_PULSE_TIME,
127 door_open_too_long_time: DEFAULT_DOOR_OPEN_TOO_LONG_TIME,
128 pulse_deadlines: [None; 16],
129 monotonic_clock: None,
130 logical_now: Duration::ZERO,
131 })
132 }
133
134 /// Set the Relinquish_Default (#270).
135 ///
136 /// Table 12-30 types it as BACnetDoorValue, but Clause 12.26.11 admits
137 /// only LOCK and UNLOCK: a pulse can't be the resting command. Any other
138 /// value, including a `DoorValue::from_raw` outside the production, is
139 /// refused with VALUE_OUT_OF_RANGE and the stored default kept. After the
140 /// store, Present_Value is resolved anew from the priority array so an
141 /// empty array falls back to the new default immediately.
142 pub fn set_relinquish_default(&mut self, value: DoorValue) -> Result<(), Error> {
143 if !matches!(value, DoorValue::LOCK | DoorValue::UNLOCK) {
144 return Err(common::value_out_of_range_error());
145 }
146 self.relinquish_default = value;
147 self.recalculate_present_value();
148 Ok(())
149 }
150
151 /// Set Door_Members, the objects that make up the physical door: its
152 /// lock, contact, reader and anything else the application chooses
153 /// (Clause 12.26.15 leaves the choice to the device). A reference with no
154 /// Device identifier names an object in this device. The array is
155 /// read-only over the network.
156 ///
157 /// A reference whose device identifier isn't a Device object is refused
158 /// with VALUE_OUT_OF_RANGE and the members set before are kept (#1285).
159 pub fn set_door_members(
160 &mut self,
161 members: impl IntoIterator<Item = impl Into<BACnetDeviceObjectReference>>,
162 ) -> Result<(), Error> {
163 let members: Vec<BACnetDeviceObjectReference> =
164 members.into_iter().map(Into::into).collect();
165 crate::device_reference::check_device_members(&members)?;
166 self.door_members = members;
167 Ok(())
168 }
169
170 /// Set Door_Pulse_Time, in tenths of a second.
171 pub fn set_door_pulse_time(&mut self, tenths: u32) {
172 self.door_pulse_time = tenths;
173 }
174
175 /// Set Door_Extended_Pulse_Time, in tenths of a second.
176 pub fn set_door_extended_pulse_time(&mut self, tenths: u32) {
177 self.door_extended_pulse_time = tenths;
178 }
179
180 /// Set Door_Open_Too_Long_Time, in tenths of a second.
181 pub fn set_door_open_too_long_time(&mut self, tenths: u32) {
182 self.door_open_too_long_time = tenths;
183 }
184
185 /// Set Door_Status, the open or closed state the door's contact reports.
186 ///
187 /// While Out_Of_Service is TRUE a client's simulated value keeps being
188 /// served, and this one takes over on the return to service.
189 pub fn set_door_status(&mut self, status: DoorStatus) {
190 self.device_state_mut().door_status = status;
191 }
192
193 /// Set Lock_Status, the state the door lock's monitor reports.
194 ///
195 /// While Out_Of_Service is TRUE a client's simulated value keeps being
196 /// served, and this one takes over on the return to service.
197 pub fn set_lock_status(&mut self, status: LockStatus) {
198 self.device_state_mut().lock_status = status;
199 }
200
201 /// Set Door_Alarm_State, the alarm condition the application's door
202 /// logic has worked out (Clause 12.26.20 leaves that to the device),
203 /// DOOR_OPEN_TOO_LONG included.
204 ///
205 /// The state has to be NORMAL or a member of Alarm_Values or
206 /// Fault_Values, and no member of Masked_Alarm_Values; any other is
207 /// refused with VALUE_OUT_OF_RANGE and the state held is kept. A change
208 /// of the value served triggers a SubscribeCOV notification (Table 13-1)
209 /// and feeds the event algorithm, which a running server evaluates at its
210 /// next one-second tick. While Out_Of_Service is TRUE a client's
211 /// simulated value keeps being served, and this one takes over on the
212 /// return to service.
213 pub fn set_door_alarm_state(&mut self, state: DoorAlarmState) -> Result<(), Error> {
214 if !self.admits(state) {
215 return Err(common::value_out_of_range_error());
216 }
217 self.device_state_mut().door_alarm_state = state;
218 Ok(())
219 }
220
221 /// Set Alarm_Values, the Door_Alarm_State values the door reports as
222 /// offnormal (Clause 12.26.25). NORMAL, or a value outside the
223 /// BACnetDoorAlarmState production, named or proprietary (256 to
224 /// 65535), is refused with VALUE_OUT_OF_RANGE and the values set before
225 /// are kept. A Door_Alarm_State the lists no longer admit drops to
226 /// NORMAL. Clients can write the list too, as they can the door's other
227 /// event configuration.
228 pub fn set_alarm_values(
229 &mut self,
230 states: impl IntoIterator<Item = DoorAlarmState>,
231 ) -> Result<(), Error> {
232 self.reporting.set_alarm_values(raw_states(states))?;
233 self.settle_door_alarm_state();
234 Ok(())
235 }
236
237 /// Set Fault_Values, the Door_Alarm_State values that make Reliability
238 /// MULTI_STATE_FAULT (Clause 12.26.26), checked as
239 /// [`Self::set_alarm_values`] checks its list.
240 pub fn set_fault_values(
241 &mut self,
242 states: impl IntoIterator<Item = DoorAlarmState>,
243 ) -> Result<(), Error> {
244 self.alarm_lists.set_fault_values(raw_states(states))?;
245 self.settle_door_alarm_state();
246 Ok(())
247 }
248
249 /// Set Masked_Alarm_Values, the states Door_Alarm_State is kept out of
250 /// (Clause 12.26.21), checked as [`Self::set_alarm_values`] checks its
251 /// list. A door in a state the list now masks returns to NORMAL at once.
252 pub fn set_masked_alarm_values(
253 &mut self,
254 states: impl IntoIterator<Item = DoorAlarmState>,
255 ) -> Result<(), Error> {
256 self.alarm_lists
257 .set_masked_alarm_values(raw_states(states))?;
258 self.settle_door_alarm_state();
259 Ok(())
260 }
261
262 /// Whether the door's lists let Door_Alarm_State take `state`.
263 fn admits(&self, state: DoorAlarmState) -> bool {
264 self.alarm_lists
265 .admits(self.reporting.alarm_values(), state)
266 }
267
268 /// Send a Door_Alarm_State the lists no longer admit back to NORMAL,
269 /// both the one served and the device's own one put aside out of
270 /// service.
271 fn settle_door_alarm_state(&mut self) {
272 let (lists, alarm_values) = (&self.alarm_lists, self.reporting.alarm_values());
273 for state in std::iter::once(&mut self.state).chain(self.device_state.as_mut()) {
274 if !lists.admits(alarm_values, state.door_alarm_state) {
275 state.door_alarm_state = DoorAlarmState::NORMAL;
276 }
277 }
278 }
279
280 /// The value the event algorithm watches: Door_Alarm_State, raw.
281 fn watched_alarm_state(&self) -> u32 {
282 self.state.door_alarm_state.to_raw()
283 }
284
285 /// Reliability as served: a client's simulated value while out of
286 /// service, else what the FAULT_STATE algorithm makes of the
287 /// Door_Alarm_State served.
288 fn served_reliability(&self) -> Reliability {
289 self.state
290 .reliability
291 .unwrap_or_else(|| self.alarm_lists.fault_state(self.state.door_alarm_state))
292 }
293
294 /// The device's own door state: the one put aside while out of service,
295 /// else the one served.
296 fn device_state_mut(&mut self) -> &mut DoorState {
297 self.device_state.as_mut().unwrap_or(&mut self.state)
298 }
299
300 fn recalculate_present_value(&mut self) {
301 self.present_value =
302 common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
303 }
304
305 fn monotonic_now(&self) -> Duration {
306 self.monotonic_clock
307 .as_ref()
308 .map_or(self.logical_now, |clock| clock())
309 }
310
311 /// Store a command (or a NULL relinquish) at `priority`, arming the
312 /// relock deadline for a pulse.
313 fn command(&mut self, priority: u8, value: Option<DoorValue>) {
314 let index = usize::from(priority - 1);
315 self.pulse_deadlines[index] = None;
316 let pulse = match value {
317 Some(DoorValue::PULSE_UNLOCK) => Some(self.door_pulse_time),
318 Some(DoorValue::EXTENDED_PULSE_UNLOCK) => Some(self.door_extended_pulse_time),
319 _ => None,
320 };
321 self.priority_array[index] = match pulse {
322 // A pulse below a live command, or of zero length, is
323 // relinquished as soon as it is written.
324 Some(tenths)
325 if tenths == 0 || self.priority_array[..index].iter().any(Option::is_some) =>
326 {
327 None
328 }
329 Some(tenths) => {
330 let length = Duration::from_millis(u64::from(tenths) * 100);
331 self.pulse_deadlines[index] = Some(self.monotonic_now().saturating_add(length));
332 value
333 }
334 None => value,
335 };
336 self.recalculate_present_value();
337 }
338
339 /// Relinquish every pulse whose deadline has passed; `true` when one was.
340 fn expire_pulses(&mut self, now: Duration) -> bool {
341 let mut expired = false;
342 for (slot, deadline) in self
343 .priority_array
344 .iter_mut()
345 .zip(&mut self.pulse_deadlines)
346 {
347 if deadline.is_some_and(|deadline| now >= deadline) {
348 *slot = None;
349 *deadline = None;
350 expired = true;
351 }
352 }
353 if expired {
354 self.recalculate_present_value();
355 }
356 expired
357 }
358
359 /// Whether Status_Flags carries IN_ALARM, which follows Event_State
360 /// (Clause 12.26.6). Status_Flags and Secured_Status both read it here,
361 /// so the two agree.
362 fn in_alarm(&self) -> bool {
363 self.reporting.event_state() != EventState::NORMAL
364 }
365
366 /// Secured_Status, worked out from what the door serves (Clause
367 /// 12.26.14).
368 ///
369 /// Each input comes out met, failed or undetermined. One failed input
370 /// makes the door UNSECURED whatever the rest say: a door commanded to
371 /// UNLOCK isn't secure even while its contact can't tell whether it is
372 /// shut. With none failed, one undetermined input makes it UNKNOWN, and
373 /// only a door with every input met is SECURED.
374 ///
375 /// An input is undetermined only when the door's own monitor says it
376 /// can't tell: Door_Status or Lock_Status reads UNKNOWN, or reports its
377 /// input faulted (DOOR_FAULT, LOCK_FAULT). Reliability is no input of its
378 /// own: a fault moves Event_State to FAULT, which sets IN_ALARM and so
379 /// fails the first input. Door_Status and Lock_Status are the served
380 /// values, so while Out_Of_Service is TRUE a client's simulation moves
381 /// the result as a device report would (Clause 12.26.9).
382 fn secured_status(&self) -> DoorSecuredStatus {
383 let inputs = [
384 Some(!self.in_alarm()),
385 // Any masked state fails the door, whichever it is.
386 Some(!self.alarm_lists.masks_any()),
387 Some(self.present_value == DoorValue::LOCK),
388 door_closed(self.state.door_status),
389 door_locked(self.state.lock_status),
390 ];
391 if inputs.contains(&Some(false)) {
392 DoorSecuredStatus::UNSECURED
393 } else if inputs.contains(&None) {
394 DoorSecuredStatus::UNKNOWN
395 } else {
396 DoorSecuredStatus::SECURED
397 }
398 }
399}
400
401/// Door_Status as a Secured_Status input: met for a shut door or one with no
402/// contact fitted (CLOSED, UNUSED), undetermined (`None`) while the contact
403/// can't tell or is faulted (UNKNOWN, DOOR_FAULT), and failed for any other
404/// value, a proprietary one included.
405fn door_closed(status: DoorStatus) -> Option<bool> {
406 match status {
407 DoorStatus::CLOSED | DoorStatus::UNUSED => Some(true),
408 DoorStatus::UNKNOWN | DoorStatus::DOOR_FAULT => None,
409 _ => Some(false),
410 }
411}
412
413/// Lock_Status as a Secured_Status input, read the same way: met for LOCKED
414/// or UNUSED, undetermined for UNKNOWN or LOCK_FAULT, failed for UNLOCKED.
415fn door_locked(status: LockStatus) -> Option<bool> {
416 match status {
417 LockStatus::LOCKED | LockStatus::UNUSED => Some(true),
418 LockStatus::UNKNOWN | LockStatus::LOCK_FAULT => None,
419 _ => Some(false),
420 }
421}
422
423/// Whether `value` is one of the four BACnetDoorValue members. The
424/// bacnet-types test `door_value_values_match_clause_21` pins the
425/// production's closed-set length, so this bound cannot drift from the enum.
426fn is_door_value(value: DoorValue) -> bool {
427 value.to_raw() <= DoorValue::EXTENDED_PULSE_UNLOCK.to_raw()
428}
429
430/// Decode a BACnetDoorValue write: an Enumerated within the closed set.
431fn checked_door_value(value: PropertyValue) -> Result<DoorValue, Error> {
432 let PropertyValue::Enumerated(raw) = value else {
433 return Err(common::invalid_data_type_error());
434 };
435 let value = DoorValue::from_raw(raw);
436 if !is_door_value(value) {
437 return Err(common::value_out_of_range_error());
438 }
439 Ok(value)
440}
441
442/// Decode a door time write: an Unsigned of at most 32 bits.
443fn checked_tenths(value: PropertyValue) -> Result<u32, Error> {
444 let PropertyValue::Unsigned(raw) = value else {
445 return Err(common::invalid_data_type_error());
446 };
447 common::u64_to_u32(raw)
448}
449
450impl BACnetObject for AccessDoorObject {
451 fn object_identifier(&self) -> ObjectIdentifier {
452 self.oid
453 }
454
455 fn object_name(&self) -> &str {
456 &self.name
457 }
458
459 fn read_property(
460 &self,
461 property: PropertyIdentifier,
462 array_index: Option<u32>,
463 ) -> Result<PropertyValue, Error> {
464 if let Some(result) = common::read_identity_properties!(self, property, array_index) {
465 return result;
466 }
467 if let Some(result) = self.reporting.read(property, array_index) {
468 return result;
469 }
470 if let Some(value) = self.alarm_lists.read(property) {
471 return Ok(value);
472 }
473 match property {
474 p if p == PropertyIdentifier::OBJECT_TYPE => {
475 Ok(PropertyValue::Enumerated(ObjectType::ACCESS_DOOR.to_raw()))
476 }
477 // IN_ALARM comes from the door's Event_State, the flag
478 // Secured_Status reads, and FAULT from the Reliability served.
479 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
480 self.status_flags,
481 self.served_reliability(),
482 self.out_of_service,
483 self.reporting.event_state(),
484 )),
485 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
486 Ok(PropertyValue::Boolean(self.out_of_service))
487 }
488 p if p == PropertyIdentifier::RELIABILITY => Ok(PropertyValue::Enumerated(
489 self.served_reliability().to_raw(),
490 )),
491 p if p == PropertyIdentifier::PRESENT_VALUE => {
492 Ok(PropertyValue::Enumerated(self.present_value.to_raw()))
493 }
494 p if p == PropertyIdentifier::DOOR_STATUS => {
495 Ok(PropertyValue::Enumerated(self.state.door_status.to_raw()))
496 }
497 p if p == PropertyIdentifier::LOCK_STATUS => {
498 Ok(PropertyValue::Enumerated(self.state.lock_status.to_raw()))
499 }
500 p if p == PropertyIdentifier::SECURED_STATUS => {
501 Ok(PropertyValue::Enumerated(self.secured_status().to_raw()))
502 }
503 p if p == PropertyIdentifier::DOOR_ALARM_STATE => Ok(PropertyValue::Enumerated(
504 self.state.door_alarm_state.to_raw(),
505 )),
506 p if p == PropertyIdentifier::DOOR_MEMBERS => common::read_array(
507 crate::device_reference::reference_elements(&self.door_members),
508 array_index,
509 ),
510 p if p == PropertyIdentifier::PRIORITY_ARRAY => {
511 common::read_priority_array!(self, array_index, |v: DoorValue| {
512 PropertyValue::Enumerated(v.to_raw())
513 })
514 }
515 p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
516 Ok(PropertyValue::Enumerated(self.relinquish_default.to_raw()))
517 }
518 p if p == PropertyIdentifier::DOOR_PULSE_TIME => {
519 Ok(PropertyValue::Unsigned(self.door_pulse_time.into()))
520 }
521 p if p == PropertyIdentifier::DOOR_EXTENDED_PULSE_TIME => Ok(PropertyValue::Unsigned(
522 self.door_extended_pulse_time.into(),
523 )),
524 p if p == PropertyIdentifier::DOOR_OPEN_TOO_LONG_TIME => {
525 Ok(PropertyValue::Unsigned(self.door_open_too_long_time.into()))
526 }
527 // NULL while Present_Value comes from Relinquish_Default
528 // (Clause 12.26.39).
529 p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
530 Ok(common::current_command_priority(&self.priority_array))
531 }
532 _ => Err(common::unknown_property_error()),
533 }
534 }
535
536 fn write_property(
537 &mut self,
538 property: PropertyIdentifier,
539 array_index: Option<u32>,
540 value: PropertyValue,
541 priority: Option<u8>,
542 ) -> Result<(), Error> {
543 // The door's entry edge is always seen (it starts in service and only
544 // this write moves Out_Of_Service), so there is no fallback state.
545 if let Some(result) = common::write_out_of_service_with_restore(
546 &mut self.out_of_service,
547 &mut self.state,
548 &mut self.device_state,
549 None,
550 property,
551 &value,
552 ) {
553 return result;
554 }
555 if let Some(result) = common::write_description(&mut self.description, property, &value) {
556 return result;
557 }
558 let (lists, alarm_values) = (&self.alarm_lists, self.reporting.alarm_values());
559 if let Some(result) = self.state.write(
560 self.out_of_service,
561 |state| lists.admits(alarm_values, state),
562 property,
563 &value,
564 ) {
565 return result;
566 }
567 // An edit of any of the three lists can leave Door_Alarm_State in a
568 // state they no longer admit.
569 let list_edit = match property {
570 p if p == PropertyIdentifier::ALARM_VALUES => {
571 self.reporting.write(property, array_index, &value)
572 }
573 _ => self.alarm_lists.write(property, array_index, &value),
574 };
575 if let Some(result) = list_edit {
576 result?;
577 self.settle_door_alarm_state();
578 return Ok(());
579 }
580 if let Some(result) = self.reporting.write(property, array_index, &value) {
581 return result;
582 }
583 match property {
584 // A command (or a NULL relinquish) at the write priority, 16 when
585 // absent; Present_Value is then resolved from the priority array.
586 p if p == PropertyIdentifier::PRESENT_VALUE => {
587 let priority = priority.unwrap_or(16);
588 if !(1..=16).contains(&priority) {
589 return Err(common::value_out_of_range_error());
590 }
591 let value = match value {
592 PropertyValue::Null => None,
593 other => Some(checked_door_value(other)?),
594 };
595 self.command(priority, value);
596 Ok(())
597 }
598 // Table 12-30 carries Relinquish_Default R (BACnetDoorValue) for
599 // the commandable Access Door; the standard permits writability.
600 p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
601 self.set_relinquish_default(checked_door_value(value)?)
602 }
603 p if p == PropertyIdentifier::DOOR_PULSE_TIME => {
604 self.door_pulse_time = checked_tenths(value)?;
605 Ok(())
606 }
607 p if p == PropertyIdentifier::DOOR_EXTENDED_PULSE_TIME => {
608 self.door_extended_pulse_time = checked_tenths(value)?;
609 Ok(())
610 }
611 p if p == PropertyIdentifier::DOOR_OPEN_TOO_LONG_TIME => {
612 self.door_open_too_long_time = checked_tenths(value)?;
613 Ok(())
614 }
615 _ => Err(crate::common::unhandled_write_error(
616 self.property_metadata().as_ref(),
617 property,
618 array_index,
619 )),
620 }
621 }
622
623 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
624 super::metadata_topology::for_access_door_object(self)
625 }
626
627 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
628 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
629 }
630
631 fn supports_cov(&self) -> bool {
632 true
633 }
634
635 crate::event::impl_change_of_state_reporting!(
636 reporting,
637 Self::watched_alarm_state,
638 Self::served_reliability
639 );
640
641 fn advance_time_internal(&mut self, elapsed: Duration) -> bool {
642 self.logical_now = self.logical_now.saturating_add(elapsed);
643 self.expire_pulses(self.logical_now)
644 }
645
646 fn bind_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
647 self.monotonic_clock = clock;
648 }
649
650 fn advance_monotonic_time_internal(&mut self, now: Duration) -> bool {
651 self.expire_pulses(now)
652 }
653
654 fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
655 self.pulse_deadlines.iter().flatten().min().copied()
656 }
657
658 fn cov_snapshot_internal(&self) -> Option<Box<dyn BACnetObject>> {
659 Some(Box::new(self.clone()))
660 }
661
662 /// Take what the door's hardware reports (#1132): Door_Status,
663 /// Lock_Status and Door_Alarm_State, each one given, together. A value
664 /// outside its production, or an alarm state the door's lists don't
665 /// admit, is VALUE_OUT_OF_RANGE and changes nothing. Stored as the
666 /// setters store the device's values: while Out_Of_Service is TRUE the
667 /// report replaces the values put aside, and a client's simulated ones
668 /// stay served until the return to service. Any other record is
669 /// OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED.
670 fn report_access_input_internal(&mut self, input: AccessControlInput) -> Result<(), Error> {
671 let AccessControlInput::DoorState(report) = input else {
672 return Err(common::optional_functionality_not_supported_error());
673 };
674 let (lists, alarm_values) = (&self.alarm_lists, self.reporting.alarm_values());
675 // The device's own state: put aside out of service, else served.
676 let device = self.device_state.as_mut().unwrap_or(&mut self.state);
677 device.report(report, |state| lists.admits(alarm_values, state))
678 }
679}
680
681/// The raw values of `states`, for the list setters.
682fn raw_states(states: impl IntoIterator<Item = DoorAlarmState>) -> Vec<u32> {
683 states.into_iter().map(DoorAlarmState::to_raw).collect()
684}
685
686#[cfg(test)]
687#[path = "door_secured_status_tests.rs"]
688mod secured_status_tests;
689
690// ---------------------------------------------------------------------------