1use super::*;
2use crate::event::CommandFailureDetector;
3
4pub struct MultiStateOutputObject {
13 oid: ObjectIdentifier,
14 name: String,
15 description: String,
16 present_value: u32,
17 feedback_value: u32,
18 number_of_states: u32,
19 out_of_service: bool,
20 status_flags: StatusFlags,
21 priority_array: [Option<u32>; 16],
22 relinquish_default: u32,
23 reliability: u32,
25 reliability_before_out_of_service: Option<u32>,
26 reliability_inhibit: common::ReliabilityInhibitState,
27 reliability_evaluator: MultiStateReliabilityState,
28 event_detection_enable: bool,
29 state_text: Vec<String>,
30 event_detector: CommandFailureDetector,
32 pub(crate) event_history: EventHistory,
33 value_source: common::ValueSourceTracking,
35}
36
37impl MultiStateOutputObject {
38 pub fn new(
39 instance: u32,
40 name: impl Into<String>,
41 number_of_states: u32,
42 ) -> Result<Self, Error> {
43 let oid = ObjectIdentifier::new(ObjectType::MULTI_STATE_OUTPUT, instance)?;
44 require_nonzero_states(number_of_states)?;
45 Ok(Self {
46 oid,
47 name: name.into(),
48 description: String::new(),
49 present_value: 1,
50 feedback_value: 1,
51 number_of_states,
52 out_of_service: false,
53 status_flags: StatusFlags::empty(),
54 priority_array: [None; 16],
55 relinquish_default: 1,
56 reliability: 0,
57 reliability_before_out_of_service: None,
58 reliability_inhibit: common::ReliabilityInhibitState::default(),
59 reliability_evaluator: MultiStateReliabilityState::default(),
60 event_detection_enable: false,
61 state_text: (1..=number_of_states)
62 .map(|i| format!("State {i}"))
63 .collect(),
64 event_detector: CommandFailureDetector::default(),
65 event_history: EventHistory::default(),
66 value_source: common::ValueSourceTracking::default(),
67 })
68 }
69
70 pub fn set_description(&mut self, desc: impl Into<String>) {
72 self.description = desc.into();
73 }
74
75 fn recalculate_present_value(&mut self) {
76 self.present_value =
77 common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
78 let _ = self.recompute_reliability();
79 }
80
81 fn configuration_invalid(&self) -> bool {
82 let is_invalid = |value: u32| !(1..=self.number_of_states).contains(&value);
83 self.priority_array
84 .iter()
85 .flatten()
86 .copied()
87 .any(is_invalid)
88 || is_invalid(self.relinquish_default)
89 || is_invalid(self.feedback_value)
90 }
91
92 fn recompute_reliability(&mut self) -> ReliabilityEvaluation {
93 if self.out_of_service || self.reliability_inhibit.enabled() {
94 return ReliabilityEvaluation::Unchanged;
95 }
96 let configuration_invalid = self.configuration_invalid();
97 self.reliability_evaluator.evaluate(
98 configuration_invalid,
99 self.present_value,
100 self.number_of_states,
101 &mut self.reliability,
102 )
103 }
104
105 pub fn set_number_of_states(&mut self, number_of_states: u32) -> Result<(), Error> {
111 resize_state_text(
112 &mut self.number_of_states,
113 &mut self.state_text,
114 number_of_states,
115 )?;
116 let _ = self.recompute_reliability();
117 Ok(())
118 }
119
120 pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
135 if value < 1 || value > self.number_of_states {
136 return Err(common::value_out_of_range_error());
137 }
138 self.relinquish_default = value;
139 self.recalculate_present_value();
140 Ok(())
141 }
142}
143
144impl BACnetObject for MultiStateOutputObject {
145 fn object_identifier(&self) -> ObjectIdentifier {
146 self.oid
147 }
148
149 fn object_name(&self) -> &str {
150 &self.name
151 }
152
153 fn supports_cov(&self) -> bool {
154 true
155 }
156
157 crate::event::impl_builtin_intrinsic_reporting!(
158 event_detector,
159 event_history,
160 [present_value, feedback_value],
161 reliability,
162 event_detection_enable,
163 CommandFailureDetector::ALGORITHM
164 );
165 impl_intrinsic_write_rollback!(
166 event_detector,
167 event_detection_enable,
168 event_history,
169 reliability_inhibit,
170 reliability,
171 out_of_service,
172 reliability_before_out_of_service;
173 reliability_evaluator,
174 priority_array,
175 relinquish_default,
176 present_value
177 );
178
179 fn acknowledge_alarm_correlated_internal(
180 &mut self,
181 event_state: EventState,
182 timestamp: &BACnetTimeStamp,
183 ) -> Result<(), Error> {
184 if !self.event_detection_enable {
185 return Err(Error::Protocol {
186 class: ErrorClass::OBJECT.to_raw() as u32,
187 code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
188 });
189 }
190 self.event_history.acknowledge_correlated(
191 &mut self.event_detector.acked_transitions,
192 event_state,
193 timestamp,
194 )
195 }
196
197 fn read_property(
198 &self,
199 property: PropertyIdentifier,
200 array_index: Option<u32>,
201 ) -> Result<PropertyValue, Error> {
202 if property == PropertyIdentifier::STATUS_FLAGS {
203 return Ok(common::compute_status_flags(
204 self.status_flags,
205 self.reliability,
206 self.out_of_service,
207 self.event_detector.event_state.to_raw(),
208 ));
209 }
210 if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
211 return Ok(PropertyValue::Boolean(self.event_detection_enable));
212 }
213 if let Some(value) = self.reliability_inhibit.read(property) {
214 return Ok(value);
215 }
216 if let Some(result) = read_common_properties!(self, property, array_index) {
217 return result;
218 }
219 if let Some(result) = read_generic_event_properties!(self, property) {
220 return result;
221 }
222 if let Some(result) = self.event_history.read(property, array_index) {
223 return result;
224 }
225 match property {
226 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
227 ObjectType::MULTI_STATE_OUTPUT.to_raw(),
228 )),
229 p if p == PropertyIdentifier::PRESENT_VALUE => {
230 Ok(PropertyValue::Unsigned(self.present_value as u64))
231 }
232 p if p == PropertyIdentifier::FEEDBACK_VALUE => {
233 Ok(PropertyValue::Unsigned(self.feedback_value as u64))
234 }
235 p if p == PropertyIdentifier::NUMBER_OF_STATES => {
236 Ok(PropertyValue::Unsigned(self.number_of_states as u64))
237 }
238 p if p == PropertyIdentifier::VALUE_SOURCE => {
239 Ok(self.value_source.value_source.clone())
240 }
241 p if p == PropertyIdentifier::LAST_COMMAND_TIME => Ok(PropertyValue::Unsigned(
242 match self.value_source.last_command_time {
243 BACnetTimeStamp::SequenceNumber(n) => u64::from(n),
244 _ => 0,
245 },
246 )),
247 p if p == PropertyIdentifier::PRIORITY_ARRAY => {
248 common::read_priority_array!(self, array_index, |v: u32| PropertyValue::Unsigned(
249 v as u64
250 ))
251 }
252 p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
253 Ok(PropertyValue::Unsigned(self.relinquish_default as u64))
254 }
255 p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
256 Ok(common::current_command_priority(&self.priority_array))
257 }
258 p if p == PropertyIdentifier::STATE_TEXT => match array_index {
259 None => Ok(PropertyValue::List(
260 self.state_text
261 .iter()
262 .map(|s| PropertyValue::CharacterString(s.clone()))
263 .collect(),
264 )),
265 Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
266 Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
267 PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
268 ),
269 _ => Err(common::invalid_array_index_error()),
270 },
271 _ => Err(common::unknown_property_error()),
272 }
273 }
274
275 fn write_property(
276 &mut self,
277 property: PropertyIdentifier,
278 array_index: Option<u32>,
279 value: PropertyValue,
280 priority: Option<u8>,
281 ) -> Result<(), Error> {
282 {
283 let num_states = self.number_of_states;
284 common::write_priority_array_direct!(self, property, array_index, value, |v| {
285 if let PropertyValue::Unsigned(u) = v {
286 if u < 1 || u > num_states as u64 {
287 Err(common::value_out_of_range_error())
288 } else {
289 Ok(u as u32)
290 }
291 } else {
292 Err(common::invalid_data_type_error())
293 }
294 });
295 }
296 if property == PropertyIdentifier::PRESENT_VALUE {
297 let num_states = self.number_of_states;
298 return common::write_priority_array!(self, value, priority, |v| {
299 if let PropertyValue::Unsigned(u) = v {
300 if u < 1 || u > num_states as u64 {
301 Err(common::value_out_of_range_error())
302 } else {
303 Ok(u as u32)
304 }
305 } else {
306 Err(common::invalid_data_type_error())
307 }
308 });
309 }
310 if property == PropertyIdentifier::FEEDBACK_VALUE {
311 if let PropertyValue::Unsigned(u) = value {
312 self.feedback_value = common::u64_to_u32(u)?;
329 let _ = self.recompute_reliability();
330 return Ok(());
331 }
332 return Err(common::invalid_data_type_error());
333 }
334 if property == PropertyIdentifier::STATE_TEXT {
335 match array_index {
336 Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => {
337 if let PropertyValue::CharacterString(s) = value {
338 self.state_text[(idx - 1) as usize] = s;
339 return Ok(());
340 }
341 return Err(common::invalid_data_type_error());
342 }
343 None => return Err(common::write_access_denied_error()),
344 _ => return Err(common::invalid_array_index_error()),
345 }
346 }
347 if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
348 if let PropertyValue::Boolean(v) = value {
349 self.event_detection_enable = v;
350 if !v {
351 self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
352 self.event_detector.acked_transitions = 0b111;
353 self.event_detector.pending = None;
354 self.event_detector.fault_reliability = None;
355 self.event_history.reset();
356 }
357 return Ok(());
358 }
359 return Err(common::invalid_data_type_error());
360 }
361 if let Some(result) = write_generic_event_properties!(self, property, value) {
362 return result;
363 }
364 if let Some(result) = self.reliability_inhibit.write_inhibit(
365 &mut self.reliability,
366 self.out_of_service,
367 property,
368 &value,
369 ) {
370 result?;
371 let _ = self.recompute_reliability();
372 return Ok(());
373 }
374 if let Some(result) = self.reliability_inhibit.write_out_of_service(
375 &mut self.out_of_service,
376 &mut self.reliability,
377 &mut self.reliability_before_out_of_service,
378 property,
379 &value,
380 ) {
381 result?;
382 let _ = self.recompute_reliability();
383 return Ok(());
384 }
385 if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
386 return result;
387 }
388 if let Some(result) = common::write_description(&mut self.description, property, &value) {
389 return result;
390 }
391 if let Some(result) = self.reliability_inhibit.write_client_reliability(
397 self.out_of_service,
398 &mut self.reliability,
399 property,
400 &value,
401 ) {
402 return result;
403 }
404 if property == PropertyIdentifier::RELINQUISH_DEFAULT {
405 if let PropertyValue::Unsigned(u) = value {
406 let v = common::u64_to_u32(u)?;
407 return self.set_relinquish_default(v);
408 }
409 return Err(common::invalid_data_type_error());
410 }
411 Err(common::write_access_denied_error())
412 }
413
414 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
415 static PROPS: &[PropertyIdentifier] = &[
416 PropertyIdentifier::OBJECT_IDENTIFIER,
417 PropertyIdentifier::OBJECT_NAME,
418 PropertyIdentifier::DESCRIPTION,
419 PropertyIdentifier::OBJECT_TYPE,
420 PropertyIdentifier::PRESENT_VALUE,
421 PropertyIdentifier::FEEDBACK_VALUE,
422 PropertyIdentifier::STATUS_FLAGS,
423 PropertyIdentifier::EVENT_STATE,
424 PropertyIdentifier::EVENT_DETECTION_ENABLE,
425 PropertyIdentifier::EVENT_ENABLE,
426 PropertyIdentifier::TIME_DELAY,
427 PropertyIdentifier::TIME_DELAY_NORMAL,
428 PropertyIdentifier::NOTIFY_TYPE,
429 PropertyIdentifier::NOTIFICATION_CLASS,
430 PropertyIdentifier::ACKED_TRANSITIONS,
431 PropertyIdentifier::EVENT_TIME_STAMPS,
432 PropertyIdentifier::EVENT_MESSAGE_TEXTS,
433 PropertyIdentifier::OUT_OF_SERVICE,
434 PropertyIdentifier::NUMBER_OF_STATES,
435 PropertyIdentifier::PRIORITY_ARRAY,
436 PropertyIdentifier::RELINQUISH_DEFAULT,
437 PropertyIdentifier::CURRENT_COMMAND_PRIORITY,
438 PropertyIdentifier::RELIABILITY,
439 PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT,
440 PropertyIdentifier::STATE_TEXT,
441 ];
442 Cow::Borrowed(PROPS)
443 }
444
445 fn is_createable(&self) -> bool {
446 true
447 }
448 fn set_reliability_internal(&mut self, reliability: u32) -> Result<(), Error> {
449 if self.out_of_service || self.reliability_inhibit.enabled() {
450 return Err(common::write_access_denied_error());
451 }
452 if !common::is_reliability_value_valid(reliability) {
453 return Err(common::value_out_of_range_error());
454 }
455 self.reliability = reliability;
456 self.reliability_evaluator.clear_ownership();
457 Ok(())
458 }
459
460 fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
461 Ok(self.recompute_reliability())
462 }
463
464 fn reliability_evaluation_inhibited_internal(&self) -> bool {
465 self.reliability_inhibit.enabled()
466 }
467
468 fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
469 common::is_multistate_commandable_writable(property)
471 || property == PropertyIdentifier::RELIABILITY
472 || property == PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT
473 || common::is_generic_event_property_writable(property)
474 || property == PropertyIdentifier::FEEDBACK_VALUE
475 || property == PropertyIdentifier::EVENT_DETECTION_ENABLE
476 }
477}
478
479#[cfg(test)]
480mod command_failure_tests {
481 use super::*;
482 use bacnet_types::enums::{EventState, EventType};
483
484 fn write_unsigned(
485 object: &mut MultiStateOutputObject,
486 property: PropertyIdentifier,
487 value: u64,
488 ) {
489 object
490 .write_property(property, None, PropertyValue::Unsigned(value), None)
491 .unwrap();
492 }
493
494 fn set_detection_enabled(object: &mut MultiStateOutputObject, enabled: bool) {
495 object
496 .write_property(
497 PropertyIdentifier::EVENT_DETECTION_ENABLE,
498 None,
499 PropertyValue::Boolean(enabled),
500 None,
501 )
502 .unwrap();
503 }
504
505 #[test]
506 fn feedback_value_round_trips_and_is_advertised_writable() {
507 let mut mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
508
509 write_unsigned(&mut mso, PropertyIdentifier::FEEDBACK_VALUE, 2);
510
511 assert_eq!(
512 mso.read_property(PropertyIdentifier::FEEDBACK_VALUE, None)
513 .unwrap(),
514 PropertyValue::Unsigned(2)
515 );
516 assert!(mso
517 .property_list()
518 .contains(&PropertyIdentifier::FEEDBACK_VALUE));
519 assert!(mso.is_writable_property(PropertyIdentifier::FEEDBACK_VALUE));
520 assert!(mso
521 .write_property(
522 PropertyIdentifier::FEEDBACK_VALUE,
523 None,
524 PropertyValue::Enumerated(2),
525 None,
526 )
527 .is_err());
528 }
529
530 #[test]
535 fn feedback_value_outside_the_state_set_is_accepted_unlike_present_value() {
536 let mut mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
537
538 write_unsigned(&mut mso, PropertyIdentifier::FEEDBACK_VALUE, 7);
539 assert_eq!(
540 mso.read_property(PropertyIdentifier::FEEDBACK_VALUE, None)
541 .unwrap(),
542 PropertyValue::Unsigned(7)
543 );
544
545 assert!(mso
546 .write_property(
547 PropertyIdentifier::PRESENT_VALUE,
548 None,
549 PropertyValue::Unsigned(7),
550 None,
551 )
552 .is_err());
553 }
554
555 #[test]
561 fn fresh_object_reports_nothing_when_detection_is_enabled() {
562 let mut mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
563 set_detection_enabled(&mut mso, true);
564
565 assert_eq!(mso.evaluate_intrinsic_reporting(), None);
566 assert_eq!(
567 mso.read_property(PropertyIdentifier::EVENT_STATE, None)
568 .unwrap(),
569 PropertyValue::Enumerated(EventState::NORMAL.to_raw())
570 );
571 }
572
573 #[test]
578 fn oversized_feedback_value_is_rejected_rather_than_wrapped_into_agreement() {
579 let mut mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
580 set_detection_enabled(&mut mso, true);
581 write_unsigned(&mut mso, PropertyIdentifier::PRESENT_VALUE, 2);
582
583 assert!(mso
584 .write_property(
585 PropertyIdentifier::FEEDBACK_VALUE,
586 None,
587 PropertyValue::Unsigned(0x1_0000_0002),
588 None,
589 )
590 .is_err());
591
592 assert_eq!(
595 mso.read_property(PropertyIdentifier::FEEDBACK_VALUE, None)
596 .unwrap(),
597 PropertyValue::Unsigned(1)
598 );
599 let outcome = mso.evaluate_intrinsic_reporting().unwrap();
600 assert_eq!(outcome.change.to, EventState::OFFNORMAL);
601 assert_eq!(outcome.event_type, EventType::COMMAND_FAILURE);
602 }
603
604 #[test]
605 fn command_failure_uses_present_and_feedback() {
606 let mut mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
607 set_detection_enabled(&mut mso, true);
608 write_unsigned(&mut mso, PropertyIdentifier::PRESENT_VALUE, 2);
609
610 let proposal = mso.evaluate_intrinsic_reporting().unwrap();
611 let outcome = crate::event::commit_test_proposal(&mut mso, proposal);
612 assert_eq!(outcome.change.to, EventState::OFFNORMAL);
613 assert_eq!(outcome.event_type, EventType::COMMAND_FAILURE);
614
615 write_unsigned(&mut mso, PropertyIdentifier::FEEDBACK_VALUE, 2);
616 let returned = mso.evaluate_intrinsic_reporting().unwrap();
617 assert_eq!(returned.change.from, EventState::OFFNORMAL);
618 assert_eq!(returned.change.to, EventState::NORMAL);
619 assert_eq!(returned.event_type, EventType::COMMAND_FAILURE);
620 }
621
622 #[test]
623 fn time_delay_gates_command_failure() {
624 let mut mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
625 set_detection_enabled(&mut mso, true);
626 write_unsigned(&mut mso, PropertyIdentifier::TIME_DELAY, 2);
627 write_unsigned(&mut mso, PropertyIdentifier::PRESENT_VALUE, 2);
628
629 assert_eq!(mso.evaluate_intrinsic_reporting(), None);
630 assert_eq!(mso.tick_intrinsic_reporting(), None);
631 let outcome = mso.tick_intrinsic_reporting().unwrap();
632 assert_eq!(outcome.change.to, EventState::OFFNORMAL);
633 assert_eq!(outcome.event_type, EventType::COMMAND_FAILURE);
634 }
635
636 #[test]
637 fn event_enable_to_offnormal_bit_controls_distribution() {
638 for (encoded, expected) in [(0x80, true), (0x00, false)] {
639 let mut mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
640 set_detection_enabled(&mut mso, true);
641 mso.write_property(
642 PropertyIdentifier::EVENT_ENABLE,
643 None,
644 PropertyValue::BitString {
645 unused_bits: 5,
646 data: vec![encoded],
647 },
648 None,
649 )
650 .unwrap();
651 write_unsigned(&mut mso, PropertyIdentifier::PRESENT_VALUE, 2);
652 assert_eq!(
653 mso.evaluate_intrinsic_reporting().unwrap().distribute,
654 expected
655 );
656 }
657 }
658
659 #[test]
660 fn generic_event_properties_round_trip_and_match_pics() {
661 let mut mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
662 let writes = [
663 (
664 PropertyIdentifier::NOTIFY_TYPE,
665 PropertyValue::Enumerated(1),
666 ),
667 (
668 PropertyIdentifier::NOTIFICATION_CLASS,
669 PropertyValue::Unsigned(42),
670 ),
671 ];
672 for (property, value) in writes {
673 mso.write_property(property, None, value.clone(), None)
674 .unwrap();
675 assert_eq!(mso.read_property(property, None).unwrap(), value);
676 }
677
678 assert!(mso
684 .write_property(
685 PropertyIdentifier::ACKED_TRANSITIONS,
686 None,
687 PropertyValue::BitString {
688 unused_bits: 5,
689 data: vec![0x80],
690 },
691 None,
692 )
693 .is_err());
694 assert!(!mso.is_writable_property(PropertyIdentifier::ACKED_TRANSITIONS));
695
696 for property in [
697 PropertyIdentifier::EVENT_ENABLE,
698 PropertyIdentifier::TIME_DELAY,
699 PropertyIdentifier::TIME_DELAY_NORMAL,
700 PropertyIdentifier::NOTIFY_TYPE,
701 PropertyIdentifier::NOTIFICATION_CLASS,
702 ] {
703 assert!(mso.property_list().contains(&property));
704 assert!(mso.is_writable_property(property));
705 }
706 assert!(mso
707 .write_property(
708 PropertyIdentifier::EVENT_STATE,
709 None,
710 PropertyValue::Enumerated(EventState::NORMAL.to_raw()),
711 None,
712 )
713 .is_err());
714 assert!(!mso.is_writable_property(PropertyIdentifier::EVENT_STATE));
715 }
716
717 #[test]
718 fn detection_enable_is_a_disabled_by_default_invariant() {
719 let mut mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
720 write_unsigned(&mut mso, PropertyIdentifier::PRESENT_VALUE, 2);
721
722 assert_eq!(mso.evaluate_intrinsic_reporting(), None);
723 assert_eq!(mso.tick_intrinsic_reporting(), None);
724 assert_eq!(
725 mso.read_property(PropertyIdentifier::EVENT_STATE, None)
726 .unwrap(),
727 PropertyValue::Enumerated(EventState::NORMAL.to_raw())
728 );
729 assert_eq!(
730 mso.read_property(PropertyIdentifier::STATUS_FLAGS, None)
731 .unwrap(),
732 PropertyValue::BitString {
733 unused_bits: 4,
734 data: vec![0],
735 }
736 );
737
738 set_detection_enabled(&mut mso, true);
739 assert_eq!(
740 mso.evaluate_intrinsic_reporting().unwrap().change.to,
741 EventState::OFFNORMAL
742 );
743 mso.event_detector.acked_transitions = 0;
744 set_detection_enabled(&mut mso, false);
745
746 assert_eq!(
747 mso.read_property(PropertyIdentifier::EVENT_STATE, None)
748 .unwrap(),
749 PropertyValue::Enumerated(EventState::NORMAL.to_raw())
750 );
751 assert_eq!(
752 mso.read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
753 .unwrap(),
754 PropertyValue::BitString {
755 unused_bits: 5,
756 data: vec![0xe0],
757 }
758 );
759 assert_eq!(mso.evaluate_intrinsic_reporting(), None);
760 assert_eq!(mso.tick_intrinsic_reporting(), None);
761
762 mso.reliability = 1;
763 assert_eq!(
764 mso.read_property(PropertyIdentifier::STATUS_FLAGS, None)
765 .unwrap(),
766 PropertyValue::BitString {
767 unused_bits: 4,
768 data: vec![0x40],
769 }
770 );
771 assert_eq!(
772 mso.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
773 .unwrap(),
774 PropertyValue::Boolean(false)
775 );
776 assert!(mso
777 .property_list()
778 .contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
779 assert!(mso.is_writable_property(PropertyIdentifier::EVENT_DETECTION_ENABLE));
780 }
781}
782
783#[cfg(test)]
784mod reliability_evaluator_tests {
785 use super::*;
786 use bacnet_types::enums::Reliability;
787
788 #[test]
789 fn configuration_error_dominates_bypassed_invalid_present_value() {
790 let mut mso = MultiStateOutputObject::new(1, "MSO-dominance", 2).unwrap();
791 mso.present_value = 3;
792 mso.feedback_value = 3;
793
794 mso.evaluate_reliability_internal().unwrap();
795 assert_eq!(
796 mso.reliability,
797 Reliability::CONFIGURATION_ERROR.to_raw(),
798 "invalid configuration must dominate invalid Present_Value"
799 );
800 mso.feedback_value = 1;
801 mso.evaluate_reliability_internal().unwrap();
802 assert_eq!(
803 mso.reliability,
804 Reliability::MULTI_STATE_OUT_OF_RANGE.to_raw()
805 );
806 mso.write_property(
807 PropertyIdentifier::PRESENT_VALUE,
808 None,
809 PropertyValue::Unsigned(1),
810 None,
811 )
812 .unwrap();
813 assert_eq!(
814 mso.reliability,
815 Reliability::NO_FAULT_DETECTED.to_raw(),
816 "the central priority recalculation must recover synchronously"
817 );
818 }
819}