Skip to main content

bacnet_objects/multistate/
value.rs

1use super::*;
2use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
3use crate::present_value_access::PresentValueAccess;
4use crate::property_metadata::PropertyMetadata;
5use std::sync::Arc;
6
7mod metadata;
8
9// ---------------------------------------------------------------------------
10// MultiStateValue (type 19)
11// ---------------------------------------------------------------------------
12
13/// BACnet Multi-State Value object.
14///
15/// Present_Value is Unsigned, range 1..=number_of_states.
16pub struct MultiStateValueObject {
17    profile: ProfileState,
18    oid: ObjectIdentifier,
19    name: String,
20    description: String,
21    present_value: u32,
22    number_of_states: u32,
23    out_of_service: bool,
24    status_flags: StatusFlags,
25    access: PresentValueAccess,
26    priority_array: [Option<u32>; 16],
27    relinquish_default: u32,
28    /// Reliability; NO_FAULT_DETECTED until a fault is evaluated or simulated.
29    reliability: Reliability,
30    reliability_before_out_of_service: Option<Reliability>,
31    reliability_inhibit: common::ReliabilityInhibitState,
32    reliability_evaluator: MultiStateReliabilityState,
33    state_text: Vec<String>,
34    /// CHANGE_OF_STATE event detector.
35    event_detector: ChangeOfStateDetector,
36    /// Event_Detection_Enable (Clause 12.20). A FALSE value suspends
37    /// event-state-machine evaluation under Clause 13.2.2.1.
38    event_detection_enable: bool,
39    pub(crate) event_history: EventHistory,
40    /// Implemented paired command-source tracking (Clause 19.5).
41    value_source: crate::command_source::ValueSourceTracking,
42    /// The last writer of a noncommandable Present_Value, once tracked (#1552).
43    write_source: crate::command_source::SingleValueSource,
44}
45
46impl MultiStateValueObject {
47    /// Create a new Multi-State Value object with a commandable Present_Value.
48    pub fn new(
49        instance: u32,
50        name: impl Into<String>,
51        number_of_states: u32,
52    ) -> Result<Self, Error> {
53        Self::with_access(
54            instance,
55            name,
56            number_of_states,
57            PresentValueAccess::Commandable,
58        )
59    }
60
61    /// Create a new Multi-State Value object whose Present_Value is written as `access` says.
62    pub fn with_access(
63        instance: u32,
64        name: impl Into<String>,
65        number_of_states: u32,
66        access: PresentValueAccess,
67    ) -> Result<Self, Error> {
68        let oid = ObjectIdentifier::new(ObjectType::MULTI_STATE_VALUE, instance)?;
69        require_nonzero_states(number_of_states)?;
70        Ok(Self {
71            profile: ProfileState::default(),
72            oid,
73            name: name.into(),
74            description: String::new(),
75            present_value: 1,
76            number_of_states,
77            out_of_service: false,
78            status_flags: StatusFlags::empty(),
79            access,
80            priority_array: [None; 16],
81            relinquish_default: 1,
82            reliability: Reliability::NO_FAULT_DETECTED,
83            reliability_before_out_of_service: None,
84            reliability_inhibit: common::ReliabilityInhibitState::default(),
85            reliability_evaluator: MultiStateReliabilityState::default(),
86            state_text: (1..=number_of_states)
87                .map(|i| format!("State {i}"))
88                .collect(),
89            event_detector: ChangeOfStateDetector::default(),
90            event_detection_enable: true,
91            event_history: EventHistory::default(),
92            value_source: crate::command_source::ValueSourceTracking::default(),
93            write_source: crate::command_source::SingleValueSource::default(),
94        })
95    }
96
97    /// Build with application-owned Tags storage and the requested Present_Value access.
98    /// Saved Tags override configured Tags only while [`Self::set_profile`]
99    /// provisions the row. Loaded data must satisfy the shared Tags rules and
100    /// the opt-in 1 MiB snapshot limit.
101    pub fn with_tags_persistence(
102        instance: u32,
103        name: impl Into<String>,
104        number_of_states: u32,
105        access: PresentValueAccess,
106        persistence: Arc<dyn TagsPersistence>,
107    ) -> Result<Self, Error> {
108        let mut object = Self::with_access(instance, name, number_of_states, access)?;
109        object.profile = ProfileState::persistent(object.oid, persistence)?;
110        Ok(object)
111    }
112
113    /// Provision independent optional Tags, Profile_Location and Profile_Name
114    /// rows before registration, in every Present_Value access mode. Tags is
115    /// writable; the text rows are network read-only. Invalid configuration
116    /// leaves the previous profile and its attached storage unchanged.
117    pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
118        self.profile.provision(profile)
119    }
120
121    /// Wait for queued save attempts to finish; writes report their own outcomes.
122    /// This is not a success receipt. Unstaged synchronous writes block their caller.
123    pub fn wait_for_tag_saves(&self) {
124        self.profile.wait_for_saves();
125    }
126
127    /// Track the source of a noncommandable Present_Value (Clause 19.5,
128    /// #1552), as [`AnalogValueObject::set_value_source_tracking`] does.
129    ///
130    /// [`AnalogValueObject::set_value_source_tracking`]: crate::analog::AnalogValueObject::set_value_source_tracking
131    pub fn set_value_source_tracking(&mut self, enabled: bool) {
132        self.write_source.set_enabled(enabled);
133    }
134
135    /// Set the description string.
136    pub fn set_description(&mut self, desc: impl Into<String>) {
137        self.description = desc.into();
138    }
139
140    fn recalculate_present_value(&mut self) {
141        self.present_value =
142            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
143        let _ = self.recompute_reliability();
144    }
145
146    fn configuration_invalid(&self) -> bool {
147        let is_invalid = |value: u32| !(1..=self.number_of_states).contains(&value);
148        self.priority_array
149            .iter()
150            .flatten()
151            .copied()
152            .any(is_invalid)
153            || is_invalid(self.relinquish_default)
154            || self
155                .event_detector
156                .alarm_values
157                .iter()
158                .copied()
159                .any(is_invalid)
160    }
161
162    fn recompute_reliability(&mut self) -> ReliabilityEvaluation {
163        if self.out_of_service || self.reliability_inhibit.enabled() {
164            return ReliabilityEvaluation::Unchanged;
165        }
166        let configuration_invalid = self.configuration_invalid();
167        self.reliability_evaluator.evaluate(
168            configuration_invalid,
169            self.present_value,
170            self.number_of_states,
171            &mut self.reliability,
172        )
173    }
174
175    /// Change the locally configured number of states.
176    ///
177    /// The BACnet `Number_Of_States` property remains read-only. A successful
178    /// local change resizes State_Text and immediately re-evaluates Reliability;
179    /// retained commands, defaults, alarms, and Present_Value are not repaired.
180    pub fn set_number_of_states(&mut self, number_of_states: u32) -> Result<(), Error> {
181        resize_state_text(
182            &mut self.number_of_states,
183            &mut self.state_text,
184            number_of_states,
185        )?;
186        let _ = self.recompute_reliability();
187        Ok(())
188    }
189
190    /// Set the alarm values and synchronously re-evaluate configuration Reliability.
191    ///
192    /// Unlike a network write, which refuses a state past Number_Of_States
193    /// (#1429), this takes any state; one past the count shows as
194    /// CONFIGURATION_ERROR.
195    pub fn set_alarm_values(&mut self, values: Vec<u32>) {
196        self.event_detector.alarm_values = values;
197        let _ = self.recompute_reliability();
198    }
199
200    /// Set the Relinquish_Default (#270).
201    ///
202    /// Validated the same way a commanded Present_Value is (Unsigned
203    /// 1..=Number_Of_States); after the store, Present_Value is resolved anew
204    /// from the priority array so an empty array falls back to the new
205    /// default immediately.
206    ///
207    /// Number_Of_States shrink interplay: if the state count ever shrinks
208    /// below this value, retained Priority_Array, Relinquish_Default,
209    /// Present_Value, and Alarm_Values are not auto-adjusted (Clause 12.20 /
210    /// Table 12-23). An out-of-range retained value is a configuration decision
211    /// for the application to resolve; the object-owned evaluator reports
212    /// CONFIGURATION_ERROR while that condition remains.
213    pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
214        if self.access != PresentValueAccess::Commandable {
215            return Err(common::unknown_property_error());
216        }
217        if value < 1 || value > self.number_of_states {
218            return Err(common::value_out_of_range_error());
219        }
220        self.relinquish_default = value;
221        self.recalculate_present_value();
222        Ok(())
223    }
224
225    fn checked_present_value(number_of_states: u32, value: PropertyValue) -> Result<u32, Error> {
226        let PropertyValue::Unsigned(u) = value else {
227            return Err(common::invalid_data_type_error());
228        };
229        if u < 1 || u > u64::from(number_of_states) {
230            return Err(common::value_out_of_range_error());
231        }
232        Ok(u as u32)
233    }
234
235    /// The values the object keeps that name a state, which a new count
236    /// may not strand: the commands in Priority_Array, Present_Value,
237    /// Relinquish_Default and Alarm_Values.
238    fn held_states(&self) -> impl Iterator<Item = u32> + '_ {
239        self.priority_array
240            .iter()
241            .flatten()
242            .copied()
243            .chain([self.present_value, self.relinquish_default])
244            .chain(self.event_detector.alarm_values.iter().copied())
245    }
246
247    /// A noncommandable Present_Value write, network-equivalent.
248    fn write_direct_present_value(
249        &mut self,
250        array_index: Option<u32>,
251        value: PropertyValue,
252    ) -> Result<(), Error> {
253        if array_index.is_some() {
254            return Err(common::property_is_not_an_array_error());
255        }
256        if self.access == PresentValueAccess::ReadOnly && !self.out_of_service {
257            return Err(common::write_access_denied_error());
258        }
259        // Clause 19.2: an otherwise permitted noncommandable NULL is a no-op.
260        if value == PropertyValue::Null {
261            return Ok(());
262        }
263        self.set_present_value_directly(value)
264    }
265
266    fn set_present_value_directly(&mut self, value: PropertyValue) -> Result<(), Error> {
267        self.present_value = Self::checked_present_value(self.number_of_states, value)?;
268        let _ = self.recompute_reliability();
269        Ok(())
270    }
271}
272
273impl BACnetObject for MultiStateValueObject {
274    fn object_identifier(&self) -> ObjectIdentifier {
275        self.oid
276    }
277
278    fn object_name(&self) -> &str {
279        &self.name
280    }
281
282    fn supports_cov(&self) -> bool {
283        true
284    }
285
286    crate::event::impl_builtin_intrinsic_reporting!(
287        event_detector,
288        event_history,
289        [present_value],
290        reliability,
291        event_detection_enable,
292        ChangeOfStateDetector::ALGORITHM
293    );
294
295    fn acknowledge_alarm_correlated_internal(
296        &mut self,
297        event_state: EventState,
298        timestamp: &BACnetTimeStamp,
299    ) -> Result<(), Error> {
300        if !self.event_detection_enable {
301            return Err(Error::Protocol {
302                class: ErrorClass::OBJECT.to_raw() as u32,
303                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
304            });
305        }
306        self.event_history.acknowledge_correlated(
307            &mut self.event_detector.acked_transitions,
308            event_state,
309            timestamp,
310        )
311    }
312
313    fn read_property(
314        &self,
315        property: PropertyIdentifier,
316        array_index: Option<u32>,
317    ) -> Result<PropertyValue, Error> {
318        if metadata::excludes(self, property) {
319            return Err(common::unknown_property_error());
320        }
321        let source = match self.access {
322            PresentValueAccess::Commandable => {
323                self.value_source
324                    .read(property, array_index, &self.priority_array)
325            }
326            _ => self.write_source.read(property, array_index),
327        };
328        if let Some(result) = source {
329            return result;
330        }
331        if let Some(result) = self.profile.read(property, array_index) {
332            return result;
333        }
334
335        if property == PropertyIdentifier::STATUS_FLAGS {
336            return Ok(common::compute_status_flags(
337                self.status_flags,
338                self.reliability,
339                self.out_of_service,
340                self.event_detector.event_state,
341            ));
342        }
343        if let Some(value) = self.reliability_inhibit.read(property) {
344            return Ok(value);
345        }
346        if let Some(result) = read_common_properties!(self, property, array_index) {
347            return result;
348        }
349        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
350            return Ok(PropertyValue::Boolean(self.event_detection_enable));
351        }
352        if let Some(result) = read_generic_event_properties!(self, property) {
353            return result;
354        }
355        if let Some(result) = self.event_history.read(property, array_index) {
356            return result;
357        }
358        match property {
359            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
360                ObjectType::MULTI_STATE_VALUE.to_raw(),
361            )),
362            p if p == PropertyIdentifier::PRESENT_VALUE => {
363                Ok(PropertyValue::Unsigned(self.present_value as u64))
364            }
365            p if p == PropertyIdentifier::NUMBER_OF_STATES => {
366                Ok(PropertyValue::Unsigned(self.number_of_states as u64))
367            }
368            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
369                common::read_priority_array!(self, array_index, |v: u32| PropertyValue::Unsigned(
370                    v as u64
371                ))
372            }
373            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
374                Ok(PropertyValue::Unsigned(self.relinquish_default as u64))
375            }
376            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
377                Ok(common::current_command_priority(&self.priority_array))
378            }
379            p if p == PropertyIdentifier::STATE_TEXT => match array_index {
380                None => Ok(PropertyValue::List(
381                    self.state_text
382                        .iter()
383                        .map(|s| PropertyValue::CharacterString(s.clone()))
384                        .collect(),
385                )),
386                Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
387                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
388                    PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
389                ),
390                _ => Err(common::invalid_array_index_error()),
391            },
392            p if p == PropertyIdentifier::ALARM_VALUES => Ok(PropertyValue::List(
393                self.event_detector
394                    .alarm_values
395                    .iter()
396                    .map(|v| PropertyValue::Unsigned(*v as u64))
397                    .collect(),
398            )),
399            _ => Err(common::unknown_property_error()),
400        }
401    }
402
403    fn write_property_from(
404        &mut self,
405        property: PropertyIdentifier,
406        array_index: Option<u32>,
407        value: PropertyValue,
408        priority: Option<u8>,
409        origin: &crate::command_source::CommandOrigin,
410    ) -> Result<(), Error> {
411        if matches!(
412            property,
413            PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
414        ) && array_index.is_some()
415        {
416            return Err(common::property_is_not_an_array_error());
417        }
418        if property == PropertyIdentifier::VALUE_SOURCE {
419            return match self.access {
420                PresentValueAccess::Commandable => {
421                    self.value_source.correct(value, priority, origin)
422                }
423                _ => self.write_source.correct(array_index, value, origin),
424            };
425        }
426        if property == PropertyIdentifier::PRESENT_VALUE {
427            let number_of_states = self.number_of_states;
428            return match self.access {
429                PresentValueAccess::Commandable => {
430                    crate::command_source::write_sourced_priority!(
431                        self,
432                        value,
433                        priority,
434                        origin,
435                        |v| Self::checked_present_value(number_of_states, v)
436                    )
437                }
438                PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
439                    self.write_source.admit(origin)?;
440                    // A NULL is a no-op (Clause 19.2), so it has no writer.
441                    let null = value == PropertyValue::Null;
442                    self.write_direct_present_value(array_index, value)?;
443                    if !null {
444                        self.write_source.record(origin);
445                    }
446                    Ok(())
447                }
448            };
449        }
450        self.write_property(property, array_index, value, priority)
451    }
452
453    fn write_property(
454        &mut self,
455        property: PropertyIdentifier,
456        array_index: Option<u32>,
457        value: PropertyValue,
458        _priority: Option<u8>,
459    ) -> Result<(), Error> {
460        if metadata::excludes(self, property) {
461            return Err(common::unknown_property_error());
462        }
463        if let Some(result) = self.profile.write(property, array_index, &value) {
464            return result;
465        }
466        if property == PropertyIdentifier::PRESENT_VALUE {
467            if self.access == PresentValueAccess::Commandable {
468                return Err(common::write_access_denied_error());
469            }
470            // A tracked source needs the writer (`write_property_from`).
471            self.write_source.unsourced()?;
472            return self.write_direct_present_value(array_index, value);
473        }
474        if property == PropertyIdentifier::VALUE_SOURCE {
475            return Err(common::write_access_denied_error());
476        }
477        if property == PropertyIdentifier::STATE_TEXT {
478            // Written whole, State_Text sets Number_Of_States too (#1443).
479            let held: Vec<u32> = self.held_states().collect();
480            write_state_text(
481                &mut self.number_of_states,
482                &mut self.state_text,
483                held,
484                array_index,
485                value,
486            )?;
487            let _ = self.recompute_reliability();
488            return Ok(());
489        }
490        if property == PropertyIdentifier::ALARM_VALUES {
491            let values = decode_alarm_values_write(array_index, value, self.number_of_states)?;
492            self.set_alarm_values(values);
493            return Ok(());
494        }
495        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
496            if let PropertyValue::Boolean(v) = value {
497                self.event_detection_enable = v;
498                if !v {
499                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
500                    self.event_detector.acked_transitions =
501                        bacnet_types::bitstring::EventTransitionBits::all();
502                    self.event_detector.pending = None;
503                    self.event_detector.fault_reliability = None;
504                    self.event_history.reset();
505                }
506                return Ok(());
507            }
508            return Err(common::invalid_data_type_error());
509        }
510        // Event_Message_Texts_Config and the Event_Algorithm_Inhibit pair (#1329).
511        if let Some(result) =
512            self.event_history
513                .write(property, array_index, &value, self.event_detection_enable)
514        {
515            return result;
516        }
517        if let Some(result) = write_generic_event_properties!(self, property, value) {
518            return result;
519        }
520        if let Some(result) = self.reliability_inhibit.write_inhibit(
521            &mut self.reliability,
522            self.out_of_service,
523            property,
524            &value,
525        ) {
526            result?;
527            let _ = self.recompute_reliability();
528            return Ok(());
529        }
530        if let Some(result) = self.reliability_inhibit.write_out_of_service(
531            &mut self.out_of_service,
532            &mut self.reliability,
533            &mut self.reliability_before_out_of_service,
534            property,
535            &value,
536        ) {
537            if result? == crate::reliability_inhibit::OutOfServiceWrite::Applied {
538                let _ = self.recompute_reliability();
539            }
540            return Ok(());
541        }
542        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
543            return result;
544        }
545        if let Some(result) = common::write_description(&mut self.description, property, &value) {
546            return result;
547        }
548        // Clause 12.20 requires simulation/test writes while Out_Of_Service is TRUE:
549        // Present_Value is writable, as is Reliability when that property exists
550        // and supports values beyond NO_FAULT_DETECTED.
551        // `is_writable_property` stays statically true because it describes capability.
552        if let Some(result) = self.reliability_inhibit.write_client_reliability(
553            self.out_of_service,
554            &mut self.reliability,
555            property,
556            &value,
557        ) {
558            return result;
559        }
560        if property == PropertyIdentifier::RELINQUISH_DEFAULT {
561            if let PropertyValue::Unsigned(u) = value {
562                let v = common::u64_to_u32(u)?;
563                return self.set_relinquish_default(v);
564            }
565            return Err(common::invalid_data_type_error());
566        }
567        Err(crate::common::unhandled_write_error(
568            self.property_metadata().as_ref(),
569            property,
570            array_index,
571        ))
572    }
573
574    fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
575        self.profile.capability()
576    }
577
578    fn advance_monotonic_time_internal(&mut self, now: std::time::Duration) -> bool {
579        self.profile.expire(now);
580        false
581    }
582
583    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
584        metadata::for_object(self)
585    }
586
587    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
588        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
589    }
590
591    fn is_createable(&self) -> bool {
592        true
593    }
594    fn creation_only_properties(&self) -> &'static [PropertyIdentifier] {
595        CREATION_ONLY
596    }
597    fn initialize_property(
598        &mut self,
599        property: PropertyIdentifier,
600        value: PropertyValue,
601    ) -> Result<(), Error> {
602        let held: Vec<u32> = self.held_states().collect();
603        initialize_states(
604            &mut self.number_of_states,
605            &mut self.state_text,
606            held,
607            property,
608            value,
609        )?;
610        let _ = self.recompute_reliability();
611        Ok(())
612    }
613    fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
614        if self.out_of_service || self.reliability_inhibit.enabled() {
615            return Err(common::write_access_denied_error());
616        }
617        if !common::is_reliability_value_valid(reliability) {
618            return Err(common::value_out_of_range_error());
619        }
620        self.reliability = reliability;
621        self.reliability_evaluator.clear_ownership();
622        Ok(())
623    }
624
625    fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
626        Ok(self.recompute_reliability())
627    }
628
629    fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
630        match self.access {
631            PresentValueAccess::Commandable => {
632                Err(common::optional_functionality_not_supported_error())
633            }
634            _ if self.out_of_service => Err(common::write_access_denied_error()),
635            PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
636                self.set_present_value_directly(value)?;
637                self.write_source.forget();
638                Ok(())
639            }
640        }
641    }
642
643    fn set_present_value_from_internal(
644        &mut self,
645        value: PropertyValue,
646        origin: &crate::command_source::CommandOrigin,
647    ) -> Result<(), Error> {
648        self.write_source.admit(origin)?;
649        self.set_present_value_internal(value)?;
650        self.write_source.record(origin);
651        Ok(())
652    }
653
654    fn reliability_evaluation_inhibited_internal(&self) -> bool {
655        self.reliability_inhibit.enabled()
656    }
657}
658
659#[cfg(test)]
660mod detection_enable_tests {
661    use super::*;
662
663    #[test]
664    fn msv_detection_enable_resets_and_gates_intrinsic_reporting() {
665        let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
666        assert_eq!(
667            msv.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
668                .unwrap(),
669            PropertyValue::Boolean(true)
670        );
671        msv.event_detector.alarm_values = vec![2];
672        msv.event_detector.time_delay = 2;
673        msv.write_property_from(
674            PropertyIdentifier::PRESENT_VALUE,
675            None,
676            PropertyValue::Unsigned(2),
677            Some(8),
678            &crate::command_source::test_origin(),
679        )
680        .unwrap();
681        assert_eq!(msv.evaluate_intrinsic_reporting(), None);
682        assert!(msv.event_detector.pending.is_some());
683
684        msv.event_detector.event_state = bacnet_types::enums::EventState::OFFNORMAL;
685        msv.event_detector.acked_transitions =
686            bacnet_types::bitstring::EventTransitionBits::empty();
687        msv.event_detector.fault_reliability = Some(bacnet_types::enums::Reliability::NO_SENSOR);
688        msv.write_property(
689            PropertyIdentifier::EVENT_DETECTION_ENABLE,
690            None,
691            PropertyValue::Boolean(false),
692            None,
693        )
694        .unwrap();
695
696        assert_eq!(
697            msv.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
698                .unwrap(),
699            PropertyValue::Boolean(false)
700        );
701        assert_eq!(
702            msv.read_property(PropertyIdentifier::EVENT_STATE, None)
703                .unwrap(),
704            PropertyValue::Enumerated(bacnet_types::enums::EventState::NORMAL.to_raw())
705        );
706        assert_eq!(
707            msv.read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
708                .unwrap(),
709            PropertyValue::BitString {
710                unused_bits: 5,
711                data: vec![0xe0],
712            }
713        );
714        assert!(msv.event_detector.pending.is_none());
715        assert!(msv.event_detector.fault_reliability.is_none());
716        assert_eq!(msv.evaluate_intrinsic_reporting(), None);
717        assert_eq!(msv.tick_intrinsic_reporting(), None);
718        assert!(
719            msv.event_detector.pending.is_none(),
720            "evaluate/tick re-armed a countdown while detection is disabled"
721        );
722        assert!(msv
723            .property_list()
724            .contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
725        assert!(msv.is_writable_property(PropertyIdentifier::EVENT_DETECTION_ENABLE));
726    }
727}
728
729#[cfg(test)]
730mod reliability_evaluator_tests {
731    use super::*;
732    use bacnet_types::enums::Reliability;
733
734    #[test]
735    fn configuration_error_dominates_bypassed_invalid_present_value() {
736        let mut msv = MultiStateValueObject::new(1, "MSV-dominance", 2).unwrap();
737        msv.present_value = 3;
738        msv.event_detector.alarm_values = vec![3];
739
740        msv.evaluate_reliability_internal().unwrap();
741        assert_eq!(
742            msv.reliability,
743            Reliability::CONFIGURATION_ERROR,
744            "invalid configuration must dominate invalid Present_Value"
745        );
746        msv.event_detector.alarm_values = vec![1];
747        msv.evaluate_reliability_internal().unwrap();
748        assert_eq!(msv.reliability, Reliability::MULTI_STATE_OUT_OF_RANGE);
749        msv.set_number_of_states(3).unwrap();
750        assert_eq!(
751            msv.reliability,
752            Reliability::NO_FAULT_DETECTED,
753            "count growth must synchronously recover the retained Present_Value"
754        );
755    }
756}