1use bacnet_types::constructed::BACnetDeviceObjectReference;
61use bacnet_types::enums::{
62 ErrorClass, ErrorCode, EventState, LifeSafetyMode, LifeSafetyOperation, LifeSafetyState,
63 ObjectType, PropertyIdentifier, Reliability, SilencedState,
64};
65use bacnet_types::error::Error;
66use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
67use std::borrow::Cow;
68
69use crate::common::{self, read_common_properties};
70use crate::device_reference::reference_list;
71use crate::traits::{BACnetObject, LifeSafetyOperationEffect, LifeSafetyOperationOutcome};
72
73mod application;
74mod membership;
75mod metadata;
76mod out_of_service;
77mod reset;
78
79use out_of_service::{DeviceValues, Simulation};
80
81pub use reset::{
82 LifeSafetyPointResetCommit, LifeSafetyPointResetContext, LifeSafetyPointResetExecutor,
83 LifeSafetyResetError, LifeSafetyZoneResetCommit, LifeSafetyZoneResetContext,
84 LifeSafetyZoneResetExecutor,
85};
86
87fn valid_life_safety_state(state: LifeSafetyState) -> bool {
90 LifeSafetyState::ALL_NAMED
91 .iter()
92 .any(|&(_, named)| named == state)
93 || (256..=65_535).contains(&state.to_raw())
94}
95
96fn life_safety_error(code: ErrorCode) -> Error {
97 Error::Protocol {
98 class: ErrorClass::OBJECT.to_raw() as u32,
99 code: code.to_raw() as u32,
100 }
101}
102
103fn apply_silenced_operation(
104 silenced: &mut SilencedState,
105 operation_expected: &mut LifeSafetyOperation,
106 operation: LifeSafetyOperation,
107) -> Result<LifeSafetyOperationEffect, Error> {
108 let current = silenced.to_raw();
112 if current > SilencedState::ALL_SILENCED.to_raw() {
113 return Err(life_safety_error(
114 ErrorCode::INVALID_OPERATION_IN_THIS_STATE,
115 ));
116 }
117
118 let desired = if operation == LifeSafetyOperation::SILENCE {
119 SilencedState::ALL_SILENCED
120 } else if operation == LifeSafetyOperation::SILENCE_AUDIBLE {
121 SilencedState::from_raw(current | SilencedState::AUDIBLE_SILENCED.to_raw())
122 } else if operation == LifeSafetyOperation::SILENCE_VISUAL {
123 SilencedState::from_raw(current | SilencedState::VISIBLE_SILENCED.to_raw())
124 } else if operation == LifeSafetyOperation::UNSILENCE {
125 SilencedState::UNSILENCED
126 } else if operation == LifeSafetyOperation::UNSILENCE_AUDIBLE {
127 SilencedState::from_raw(current & !SilencedState::AUDIBLE_SILENCED.to_raw())
128 } else if operation == LifeSafetyOperation::UNSILENCE_VISUAL {
129 SilencedState::from_raw(current & !SilencedState::VISIBLE_SILENCED.to_raw())
130 } else {
131 return Err(life_safety_error(ErrorCode::VALUE_OUT_OF_RANGE));
132 };
133
134 if *operation_expected != operation {
135 return Err(life_safety_error(
136 ErrorCode::INVALID_OPERATION_IN_THIS_STATE,
137 ));
138 }
139
140 *silenced = desired;
141 *operation_expected = LifeSafetyOperation::NONE;
142 Ok(LifeSafetyOperationEffect::Applied)
143}
144
145const COV_PROPERTIES: [PropertyIdentifier; 5] = [
148 PropertyIdentifier::PRESENT_VALUE,
149 PropertyIdentifier::TRACKING_VALUE,
150 PropertyIdentifier::SILENCED,
151 PropertyIdentifier::OPERATION_EXPECTED,
152 PropertyIdentifier::STATUS_FLAGS,
153];
154
155fn standard_life_safety_modes() -> Vec<LifeSafetyMode> {
157 LifeSafetyMode::ALL_NAMED
158 .iter()
159 .map(|&(_, mode)| mode)
160 .collect()
161}
162
163fn distinct_modes(modes: impl IntoIterator<Item = LifeSafetyMode>) -> Vec<LifeSafetyMode> {
165 let mut distinct = Vec::new();
166 for mode in modes {
167 if !distinct.contains(&mode) {
168 distinct.push(mode);
169 }
170 }
171 distinct
172}
173
174fn read_accepted_modes(accepted_modes: &[LifeSafetyMode]) -> PropertyValue {
175 PropertyValue::List(
176 accepted_modes
177 .iter()
178 .map(|mode| PropertyValue::Enumerated(mode.to_raw()))
179 .collect(),
180 )
181}
182
183fn write_mode(
187 mode: &mut LifeSafetyMode,
188 accepted_modes: &[LifeSafetyMode],
189 value: &PropertyValue,
190) -> Result<(), Error> {
191 let PropertyValue::Enumerated(raw) = value else {
192 return Err(common::invalid_data_type_error());
193 };
194 let requested = LifeSafetyMode::from_raw(*raw);
195 if !accepted_modes.contains(&requested) {
196 return Err(common::value_out_of_range_error());
197 }
198 *mode = requested;
199 Ok(())
200}
201
202fn operation_outcome(
203 object: &dyn BACnetObject,
204 before: Vec<(PropertyIdentifier, PropertyValue)>,
205 effect: LifeSafetyOperationEffect,
206) -> LifeSafetyOperationOutcome {
207 let changed_properties = before
208 .into_iter()
209 .filter_map(|(property, previous)| {
210 object
211 .read_property(property, None)
212 .is_ok_and(|current| current != previous)
213 .then_some(property)
214 })
215 .collect();
216 LifeSafetyOperationOutcome {
217 effect,
218 changed_properties,
219 }
220}
221
222pub struct LifeSafetyPointObject {
233 oid: ObjectIdentifier,
234 name: String,
235 description: String,
236 present_value: LifeSafetyState,
238 mode: LifeSafetyMode,
240 accepted_modes: Vec<LifeSafetyMode>,
242 silenced: SilencedState,
244 operation_expected: LifeSafetyOperation,
246 tracking_value: LifeSafetyState,
248 member_of: Vec<BACnetDeviceObjectReference>,
250 direct_reading: f32,
252 maintenance_required: bool,
254 event_state: EventState,
256 status_flags: StatusFlags,
257 out_of_service: bool,
258 reliability: Reliability,
260 device_values: DeviceValues,
263 reset_executor: Option<LifeSafetyPointResetExecutor>,
265}
266
267impl LifeSafetyPointObject {
268 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
274 let oid = ObjectIdentifier::new(ObjectType::LIFE_SAFETY_POINT, instance)?;
275 Ok(Self {
276 oid,
277 name: name.into(),
278 description: String::new(),
279 present_value: LifeSafetyState::QUIET,
280 mode: LifeSafetyMode::OFF,
281 accepted_modes: standard_life_safety_modes(),
282 silenced: SilencedState::UNSILENCED,
283 operation_expected: LifeSafetyOperation::NONE,
284 tracking_value: LifeSafetyState::QUIET,
285 member_of: Vec::new(),
286 direct_reading: 0.0,
287 maintenance_required: false,
288 event_state: EventState::NORMAL,
289 status_flags: StatusFlags::empty(),
290 out_of_service: false,
291 reliability: Reliability::NO_FAULT_DETECTED,
292 device_values: DeviceValues::default(),
293 reset_executor: None,
294 })
295 }
296
297 pub fn set_present_value(&mut self, state: LifeSafetyState) {
299 self.present_value = state;
300 }
301
302 pub fn set_mode(&mut self, mode: LifeSafetyMode) {
305 self.mode = mode;
306 }
307
308 pub fn set_accepted_modes(&mut self, modes: impl IntoIterator<Item = LifeSafetyMode>) {
313 self.accepted_modes = distinct_modes(modes);
314 }
315
316 pub fn set_tracking_value(&mut self, state: LifeSafetyState) {
321 self.simulation().track(state);
322 }
323
324 fn simulation(&mut self) -> Simulation<'_> {
325 Simulation {
326 out_of_service: &mut self.out_of_service,
327 reliability: &mut self.reliability,
328 tracking_value: &mut self.tracking_value,
329 device_values: &mut self.device_values,
330 }
331 }
332
333 pub fn set_silenced(&mut self, state: SilencedState) {
335 self.silenced = state;
336 }
337
338 pub fn set_operation_expected(&mut self, operation: LifeSafetyOperation) {
340 self.operation_expected = operation;
341 }
342
343 pub fn set_reset_executor(&mut self, executor: LifeSafetyPointResetExecutor) {
348 self.reset_executor = Some(executor);
349 }
350
351 pub fn with_reset_executor(mut self, executor: LifeSafetyPointResetExecutor) -> Self {
353 self.set_reset_executor(executor);
354 self
355 }
356
357 pub fn set_direct_reading(&mut self, value: f32) {
359 self.direct_reading = value;
360 }
361
362 pub fn set_description(&mut self, desc: impl Into<String>) {
364 self.description = desc.into();
365 }
366
367 pub fn add_member(
374 &mut self,
375 zone: impl Into<BACnetDeviceObjectReference>,
376 ) -> Result<(), Error> {
377 membership::add(&mut self.member_of, zone.into(), membership::ZONES)
378 }
379}
380
381impl BACnetObject for LifeSafetyPointObject {
382 fn object_identifier(&self) -> ObjectIdentifier {
383 self.oid
384 }
385
386 fn object_name(&self) -> &str {
387 &self.name
388 }
389
390 fn read_property(
391 &self,
392 property: PropertyIdentifier,
393 array_index: Option<u32>,
394 ) -> Result<PropertyValue, Error> {
395 if let Some(result) = read_common_properties!(self, property, array_index) {
396 return result;
397 }
398 match property {
399 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
400 ObjectType::LIFE_SAFETY_POINT.to_raw(),
401 )),
402 p if p == PropertyIdentifier::PRESENT_VALUE => {
403 Ok(PropertyValue::Enumerated(self.present_value.to_raw()))
404 }
405 p if p == PropertyIdentifier::MODE => Ok(PropertyValue::Enumerated(self.mode.to_raw())),
406 p if p == PropertyIdentifier::ACCEPTED_MODES => {
407 Ok(read_accepted_modes(&self.accepted_modes))
408 }
409 p if p == PropertyIdentifier::SILENCED => {
410 Ok(PropertyValue::Enumerated(self.silenced.to_raw()))
411 }
412 p if p == PropertyIdentifier::OPERATION_EXPECTED => {
413 Ok(PropertyValue::Enumerated(self.operation_expected.to_raw()))
414 }
415 p if p == PropertyIdentifier::TRACKING_VALUE => {
416 Ok(PropertyValue::Enumerated(self.tracking_value.to_raw()))
417 }
418 p if p == PropertyIdentifier::MEMBER_OF => Ok(reference_list(&self.member_of)),
419 p if p == PropertyIdentifier::DIRECT_READING => {
420 Ok(PropertyValue::Real(self.direct_reading))
421 }
422 p if p == PropertyIdentifier::MAINTENANCE_REQUIRED => {
423 Ok(PropertyValue::Boolean(self.maintenance_required))
424 }
425 p if p == PropertyIdentifier::EVENT_STATE => {
426 Ok(PropertyValue::Enumerated(self.event_state.to_raw()))
427 }
428 _ => Err(common::unknown_property_error()),
429 }
430 }
431
432 fn write_property(
433 &mut self,
434 property: PropertyIdentifier,
435 _array_index: Option<u32>,
436 value: PropertyValue,
437 _priority: Option<u8>,
438 ) -> Result<(), Error> {
439 if property == PropertyIdentifier::PRESENT_VALUE {
441 return Err(common::write_access_denied_error());
442 }
443 if property == PropertyIdentifier::MODE {
444 return write_mode(&mut self.mode, &self.accepted_modes, &value);
445 }
446 if property == PropertyIdentifier::SILENCED
447 || property == PropertyIdentifier::OPERATION_EXPECTED
448 {
449 return Err(common::write_access_denied_error());
450 }
451 if property == PropertyIdentifier::DIRECT_READING {
452 if let PropertyValue::Real(v) = value {
453 common::reject_non_finite(v)?;
454 self.direct_reading = v;
455 return Ok(());
456 }
457 return Err(common::invalid_data_type_error());
458 }
459 if property == PropertyIdentifier::MAINTENANCE_REQUIRED {
460 if let PropertyValue::Boolean(v) = value {
461 self.maintenance_required = v;
462 return Ok(());
463 }
464 return Err(common::invalid_data_type_error());
465 }
466 if let Some(result) = self.simulation().write(property, &value) {
467 return result;
468 }
469 if let Some(result) = common::write_description(&mut self.description, property, &value) {
470 return result;
471 }
472 Err(crate::common::unhandled_write_error(
473 self.property_metadata().as_ref(),
474 property,
475 _array_index,
476 ))
477 }
478
479 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
480 metadata::for_point(self)
481 }
482
483 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
484 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
485 }
486
487 fn supports_cov(&self) -> bool {
488 true
489 }
490
491 fn supports_cov_property(&self, property: PropertyIdentifier) -> bool {
492 COV_PROPERTIES.contains(&property)
493 }
494
495 fn apply_life_safety_operation(
496 &mut self,
497 operation: LifeSafetyOperation,
498 ) -> Result<LifeSafetyOperationOutcome, Error> {
499 let before = COV_PROPERTIES
500 .into_iter()
501 .filter_map(|property| {
502 self.read_property(property, None)
503 .ok()
504 .map(|value| (property, value))
505 })
506 .collect();
507 let effect = if reset::is_reset_operation(operation) {
508 self.apply_reset_operation(operation)?
509 } else {
510 apply_silenced_operation(&mut self.silenced, &mut self.operation_expected, operation)?
511 };
512 Ok(operation_outcome(self, before, effect))
513 }
514
515 fn set_life_safety_operation_expected_internal(
516 &mut self,
517 operation: LifeSafetyOperation,
518 ) -> Result<(), Error> {
519 self.set_operation_expected(operation);
520 Ok(())
521 }
522
523 fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
524 self.simulation().set_reliability(reliability)
525 }
526
527 fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
528 self.present_value = application::life_safety_state(&value)?;
529 Ok(())
530 }
531
532 fn set_tracking_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
533 let state = application::life_safety_state(&value)?;
534 self.set_tracking_value(state);
535 Ok(())
536 }
537}
538
539pub struct LifeSafetyZoneObject {
551 oid: ObjectIdentifier,
552 name: String,
553 description: String,
554 present_value: LifeSafetyState,
556 mode: LifeSafetyMode,
558 accepted_modes: Vec<LifeSafetyMode>,
560 silenced: SilencedState,
562 operation_expected: LifeSafetyOperation,
564 tracking_value: LifeSafetyState,
566 zone_members: Vec<BACnetDeviceObjectReference>,
568 member_of: Vec<BACnetDeviceObjectReference>,
570 event_state: EventState,
572 status_flags: StatusFlags,
573 out_of_service: bool,
574 reliability: Reliability,
576 device_values: DeviceValues,
579 reset_executor: Option<LifeSafetyZoneResetExecutor>,
581}
582
583impl LifeSafetyZoneObject {
584 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
590 let oid = ObjectIdentifier::new(ObjectType::LIFE_SAFETY_ZONE, instance)?;
591 Ok(Self {
592 oid,
593 name: name.into(),
594 description: String::new(),
595 present_value: LifeSafetyState::QUIET,
596 mode: LifeSafetyMode::OFF,
597 accepted_modes: standard_life_safety_modes(),
598 silenced: SilencedState::UNSILENCED,
599 operation_expected: LifeSafetyOperation::NONE,
600 tracking_value: LifeSafetyState::QUIET,
601 zone_members: Vec::new(),
602 member_of: Vec::new(),
603 event_state: EventState::NORMAL,
604 status_flags: StatusFlags::empty(),
605 out_of_service: false,
606 reliability: Reliability::NO_FAULT_DETECTED,
607 device_values: DeviceValues::default(),
608 reset_executor: None,
609 })
610 }
611
612 pub fn set_present_value(&mut self, state: LifeSafetyState) {
614 self.present_value = state;
615 }
616
617 pub fn set_mode(&mut self, mode: LifeSafetyMode) {
620 self.mode = mode;
621 }
622
623 pub fn set_accepted_modes(&mut self, modes: impl IntoIterator<Item = LifeSafetyMode>) {
628 self.accepted_modes = distinct_modes(modes);
629 }
630
631 pub fn set_tracking_value(&mut self, state: LifeSafetyState) {
636 self.simulation().track(state);
637 }
638
639 fn simulation(&mut self) -> Simulation<'_> {
640 Simulation {
641 out_of_service: &mut self.out_of_service,
642 reliability: &mut self.reliability,
643 tracking_value: &mut self.tracking_value,
644 device_values: &mut self.device_values,
645 }
646 }
647
648 pub fn set_silenced(&mut self, state: SilencedState) {
650 self.silenced = state;
651 }
652
653 pub fn set_operation_expected(&mut self, operation: LifeSafetyOperation) {
655 self.operation_expected = operation;
656 }
657
658 pub fn set_reset_executor(&mut self, executor: LifeSafetyZoneResetExecutor) {
663 self.reset_executor = Some(executor);
664 }
665
666 pub fn with_reset_executor(mut self, executor: LifeSafetyZoneResetExecutor) -> Self {
668 self.set_reset_executor(executor);
669 self
670 }
671
672 pub fn set_description(&mut self, desc: impl Into<String>) {
674 self.description = desc.into();
675 }
676
677 pub fn add_zone_member(
685 &mut self,
686 member: impl Into<BACnetDeviceObjectReference>,
687 ) -> Result<(), Error> {
688 membership::add(
689 &mut self.zone_members,
690 member.into(),
691 membership::ZONE_MEMBERS,
692 )
693 }
694
695 pub fn add_member(
698 &mut self,
699 zone: impl Into<BACnetDeviceObjectReference>,
700 ) -> Result<(), Error> {
701 membership::add(&mut self.member_of, zone.into(), membership::ZONES)
702 }
703}
704
705impl BACnetObject for LifeSafetyZoneObject {
706 fn object_identifier(&self) -> ObjectIdentifier {
707 self.oid
708 }
709
710 fn object_name(&self) -> &str {
711 &self.name
712 }
713
714 fn read_property(
715 &self,
716 property: PropertyIdentifier,
717 array_index: Option<u32>,
718 ) -> Result<PropertyValue, Error> {
719 if let Some(result) = read_common_properties!(self, property, array_index) {
720 return result;
721 }
722 match property {
723 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
724 ObjectType::LIFE_SAFETY_ZONE.to_raw(),
725 )),
726 p if p == PropertyIdentifier::PRESENT_VALUE => {
727 Ok(PropertyValue::Enumerated(self.present_value.to_raw()))
728 }
729 p if p == PropertyIdentifier::MODE => Ok(PropertyValue::Enumerated(self.mode.to_raw())),
730 p if p == PropertyIdentifier::ACCEPTED_MODES => {
731 Ok(read_accepted_modes(&self.accepted_modes))
732 }
733 p if p == PropertyIdentifier::SILENCED => {
734 Ok(PropertyValue::Enumerated(self.silenced.to_raw()))
735 }
736 p if p == PropertyIdentifier::OPERATION_EXPECTED => {
737 Ok(PropertyValue::Enumerated(self.operation_expected.to_raw()))
738 }
739 p if p == PropertyIdentifier::TRACKING_VALUE => {
740 Ok(PropertyValue::Enumerated(self.tracking_value.to_raw()))
741 }
742 p if p == PropertyIdentifier::ZONE_MEMBERS => Ok(reference_list(&self.zone_members)),
743 p if p == PropertyIdentifier::MEMBER_OF => Ok(reference_list(&self.member_of)),
744 p if p == PropertyIdentifier::EVENT_STATE => {
745 Ok(PropertyValue::Enumerated(self.event_state.to_raw()))
746 }
747 _ => Err(common::unknown_property_error()),
748 }
749 }
750
751 fn write_property(
752 &mut self,
753 property: PropertyIdentifier,
754 _array_index: Option<u32>,
755 value: PropertyValue,
756 _priority: Option<u8>,
757 ) -> Result<(), Error> {
758 if property == PropertyIdentifier::PRESENT_VALUE {
760 return Err(common::write_access_denied_error());
761 }
762 if property == PropertyIdentifier::MODE {
763 return write_mode(&mut self.mode, &self.accepted_modes, &value);
764 }
765 if property == PropertyIdentifier::SILENCED
766 || property == PropertyIdentifier::OPERATION_EXPECTED
767 {
768 return Err(common::write_access_denied_error());
769 }
770 if let Some(result) = self.simulation().write(property, &value) {
771 return result;
772 }
773 if let Some(result) = common::write_description(&mut self.description, property, &value) {
774 return result;
775 }
776 Err(crate::common::unhandled_write_error(
777 self.property_metadata().as_ref(),
778 property,
779 _array_index,
780 ))
781 }
782
783 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
784 metadata::for_zone(self)
785 }
786
787 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
788 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
789 }
790
791 fn supports_cov(&self) -> bool {
792 true
793 }
794
795 fn supports_cov_property(&self, property: PropertyIdentifier) -> bool {
796 COV_PROPERTIES.contains(&property)
797 }
798
799 fn apply_life_safety_operation(
800 &mut self,
801 operation: LifeSafetyOperation,
802 ) -> Result<LifeSafetyOperationOutcome, Error> {
803 let before = COV_PROPERTIES
804 .into_iter()
805 .filter_map(|property| {
806 self.read_property(property, None)
807 .ok()
808 .map(|value| (property, value))
809 })
810 .collect();
811 let effect = if reset::is_reset_operation(operation) {
812 self.apply_reset_operation(operation)?
813 } else {
814 apply_silenced_operation(&mut self.silenced, &mut self.operation_expected, operation)?
815 };
816 Ok(operation_outcome(self, before, effect))
817 }
818
819 fn set_life_safety_operation_expected_internal(
820 &mut self,
821 operation: LifeSafetyOperation,
822 ) -> Result<(), Error> {
823 self.set_operation_expected(operation);
824 Ok(())
825 }
826
827 fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
828 self.simulation().set_reliability(reliability)
829 }
830
831 fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
832 self.present_value = application::life_safety_state(&value)?;
833 Ok(())
834 }
835
836 fn set_tracking_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
837 let state = application::life_safety_state(&value)?;
838 self.set_tracking_value(state);
839 Ok(())
840 }
841}
842
843#[cfg(test)]
848mod tests;
849
850#[cfg(test)]
851mod reset_tests;
852
853#[cfg(test)]
854mod event_state_tests;
855
856#[cfg(test)]
857mod accepted_modes_tests;
858
859#[cfg(test)]
860mod out_of_service_tests;
861
862#[cfg(test)]
863mod application_tests;
864
865#[cfg(test)]
866mod membership_tests;