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