Skip to main content

bacnet_objects/multistate/
value.rs

1use super::*;
2
3// ---------------------------------------------------------------------------
4// MultiStateValue (type 19)
5// ---------------------------------------------------------------------------
6
7/// BACnet Multi-State Value object.
8///
9/// Commandable multi-state value with 16-level priority array.
10/// Present_Value is Unsigned, range 1..=number_of_states.
11pub struct MultiStateValueObject {
12    oid: ObjectIdentifier,
13    name: String,
14    description: String,
15    present_value: u32,
16    number_of_states: u32,
17    out_of_service: bool,
18    status_flags: StatusFlags,
19    priority_array: [Option<u32>; 16],
20    relinquish_default: u32,
21    /// Reliability: 0 = NO_FAULT_DETECTED.
22    reliability: u32,
23    reliability_before_out_of_service: Option<u32>,
24    reliability_inhibit: common::ReliabilityInhibitState,
25    reliability_evaluator: MultiStateReliabilityState,
26    state_text: Vec<String>,
27    /// CHANGE_OF_STATE event detector.
28    event_detector: ChangeOfStateDetector,
29    /// Event_Detection_Enable (Clause 12.20). Clause 13.2.2.1: "If the
30    /// Event_Detection_Enable property is FALSE, then this state machine is not evaluated."
31    event_detection_enable: bool,
32    pub(crate) event_history: EventHistory,
33    /// Value source tracking (optional per spec — exposed via VALUE_SOURCE property).
34    value_source: common::ValueSourceTracking,
35}
36
37impl MultiStateValueObject {
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_VALUE, 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            number_of_states,
51            out_of_service: false,
52            status_flags: StatusFlags::empty(),
53            priority_array: [None; 16],
54            relinquish_default: 1,
55            reliability: 0,
56            reliability_before_out_of_service: None,
57            reliability_inhibit: common::ReliabilityInhibitState::default(),
58            reliability_evaluator: MultiStateReliabilityState::default(),
59            state_text: (1..=number_of_states)
60                .map(|i| format!("State {i}"))
61                .collect(),
62            event_detector: ChangeOfStateDetector::default(),
63            event_detection_enable: true,
64            event_history: EventHistory::default(),
65            value_source: common::ValueSourceTracking::default(),
66        })
67    }
68
69    /// Set the description string.
70    pub fn set_description(&mut self, desc: impl Into<String>) {
71        self.description = desc.into();
72    }
73
74    fn recalculate_present_value(&mut self) {
75        self.present_value =
76            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
77        let _ = self.recompute_reliability();
78    }
79
80    fn configuration_invalid(&self) -> bool {
81        let is_invalid = |value: u32| !(1..=self.number_of_states).contains(&value);
82        self.priority_array
83            .iter()
84            .flatten()
85            .copied()
86            .any(is_invalid)
87            || is_invalid(self.relinquish_default)
88            || self
89                .event_detector
90                .alarm_values
91                .iter()
92                .copied()
93                .any(is_invalid)
94    }
95
96    fn recompute_reliability(&mut self) -> ReliabilityEvaluation {
97        if self.out_of_service || self.reliability_inhibit.enabled() {
98            return ReliabilityEvaluation::Unchanged;
99        }
100        let configuration_invalid = self.configuration_invalid();
101        self.reliability_evaluator.evaluate(
102            configuration_invalid,
103            self.present_value,
104            self.number_of_states,
105            &mut self.reliability,
106        )
107    }
108
109    /// Change the locally configured number of states.
110    ///
111    /// The BACnet `Number_Of_States` property remains read-only. A successful
112    /// local change resizes State_Text and immediately re-evaluates Reliability;
113    /// retained commands, defaults, alarms, and Present_Value are not repaired.
114    pub fn set_number_of_states(&mut self, number_of_states: u32) -> Result<(), Error> {
115        resize_state_text(
116            &mut self.number_of_states,
117            &mut self.state_text,
118            number_of_states,
119        )?;
120        let _ = self.recompute_reliability();
121        Ok(())
122    }
123
124    /// Set the alarm values and synchronously re-evaluate configuration Reliability.
125    pub fn set_alarm_values(&mut self, values: Vec<u32>) {
126        self.event_detector.alarm_values = values;
127        let _ = self.recompute_reliability();
128    }
129
130    /// Set the Relinquish_Default (#270).
131    ///
132    /// Validated the same way a commanded Present_Value is (Unsigned
133    /// 1..=Number_Of_States); after the store, Present_Value is resolved anew
134    /// from the priority array so an empty array falls back to the new
135    /// default immediately.
136    ///
137    /// Number_Of_States shrink interplay: if the state count ever shrinks
138    /// below this value, retained Priority_Array, Relinquish_Default,
139    /// Present_Value, and Alarm_Values are not auto-adjusted (Clause 12.20 /
140    /// Table 12-23). An out-of-range retained value is a configuration decision
141    /// for the application to resolve; the object-owned evaluator reports
142    /// CONFIGURATION_ERROR while that condition remains.
143    pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
144        if value < 1 || value > self.number_of_states {
145            return Err(common::value_out_of_range_error());
146        }
147        self.relinquish_default = value;
148        self.recalculate_present_value();
149        Ok(())
150    }
151}
152
153impl BACnetObject for MultiStateValueObject {
154    fn object_identifier(&self) -> ObjectIdentifier {
155        self.oid
156    }
157
158    fn object_name(&self) -> &str {
159        &self.name
160    }
161
162    fn supports_cov(&self) -> bool {
163        true
164    }
165
166    crate::event::impl_builtin_intrinsic_reporting!(
167        event_detector,
168        event_history,
169        [present_value],
170        reliability,
171        event_detection_enable,
172        ChangeOfStateDetector::ALGORITHM
173    );
174    impl_intrinsic_write_rollback!(
175        event_detector,
176        event_detection_enable,
177        event_history,
178        reliability_inhibit,
179        reliability,
180        out_of_service,
181        reliability_before_out_of_service;
182        reliability_evaluator,
183        priority_array,
184        relinquish_default,
185        present_value
186    );
187
188    fn acknowledge_alarm_correlated_internal(
189        &mut self,
190        event_state: EventState,
191        timestamp: &BACnetTimeStamp,
192    ) -> Result<(), Error> {
193        if !self.event_detection_enable {
194            return Err(Error::Protocol {
195                class: ErrorClass::OBJECT.to_raw() as u32,
196                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
197            });
198        }
199        self.event_history.acknowledge_correlated(
200            &mut self.event_detector.acked_transitions,
201            event_state,
202            timestamp,
203        )
204    }
205
206    fn read_property(
207        &self,
208        property: PropertyIdentifier,
209        array_index: Option<u32>,
210    ) -> Result<PropertyValue, Error> {
211        if property == PropertyIdentifier::STATUS_FLAGS {
212            return Ok(common::compute_status_flags(
213                self.status_flags,
214                self.reliability,
215                self.out_of_service,
216                self.event_detector.event_state.to_raw(),
217            ));
218        }
219        if let Some(value) = self.reliability_inhibit.read(property) {
220            return Ok(value);
221        }
222        if let Some(result) = read_common_properties!(self, property, array_index) {
223            return result;
224        }
225        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
226            return Ok(PropertyValue::Boolean(self.event_detection_enable));
227        }
228        if let Some(result) = read_generic_event_properties!(self, property) {
229            return result;
230        }
231        if let Some(result) = self.event_history.read(property, array_index) {
232            return result;
233        }
234        match property {
235            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
236                ObjectType::MULTI_STATE_VALUE.to_raw(),
237            )),
238            p if p == PropertyIdentifier::PRESENT_VALUE => {
239                Ok(PropertyValue::Unsigned(self.present_value as u64))
240            }
241            p if p == PropertyIdentifier::NUMBER_OF_STATES => {
242                Ok(PropertyValue::Unsigned(self.number_of_states as u64))
243            }
244            p if p == PropertyIdentifier::VALUE_SOURCE => {
245                Ok(self.value_source.value_source.clone())
246            }
247            p if p == PropertyIdentifier::LAST_COMMAND_TIME => Ok(PropertyValue::Unsigned(
248                match self.value_source.last_command_time {
249                    BACnetTimeStamp::SequenceNumber(n) => u64::from(n),
250                    _ => 0,
251                },
252            )),
253            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
254                common::read_priority_array!(self, array_index, |v: u32| PropertyValue::Unsigned(
255                    v as u64
256                ))
257            }
258            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
259                Ok(PropertyValue::Unsigned(self.relinquish_default as u64))
260            }
261            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
262                Ok(common::current_command_priority(&self.priority_array))
263            }
264            p if p == PropertyIdentifier::STATE_TEXT => match array_index {
265                None => Ok(PropertyValue::List(
266                    self.state_text
267                        .iter()
268                        .map(|s| PropertyValue::CharacterString(s.clone()))
269                        .collect(),
270                )),
271                Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
272                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
273                    PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
274                ),
275                _ => Err(common::invalid_array_index_error()),
276            },
277            p if p == PropertyIdentifier::ALARM_VALUES => Ok(PropertyValue::List(
278                self.event_detector
279                    .alarm_values
280                    .iter()
281                    .map(|v| PropertyValue::Unsigned(*v as u64))
282                    .collect(),
283            )),
284            _ => Err(common::unknown_property_error()),
285        }
286    }
287
288    fn write_property(
289        &mut self,
290        property: PropertyIdentifier,
291        array_index: Option<u32>,
292        value: PropertyValue,
293        priority: Option<u8>,
294    ) -> Result<(), Error> {
295        {
296            let num_states = self.number_of_states;
297            common::write_priority_array_direct!(self, property, array_index, value, |v| {
298                if let PropertyValue::Unsigned(u) = v {
299                    if u < 1 || u > num_states as u64 {
300                        Err(common::value_out_of_range_error())
301                    } else {
302                        Ok(u as u32)
303                    }
304                } else {
305                    Err(common::invalid_data_type_error())
306                }
307            });
308        }
309        if property == PropertyIdentifier::PRESENT_VALUE {
310            let num_states = self.number_of_states;
311            return common::write_priority_array!(self, value, priority, |v| {
312                if let PropertyValue::Unsigned(u) = v {
313                    if u < 1 || u > num_states as u64 {
314                        Err(common::value_out_of_range_error())
315                    } else {
316                        Ok(u as u32)
317                    }
318                } else {
319                    Err(common::invalid_data_type_error())
320                }
321            });
322        }
323        if property == PropertyIdentifier::STATE_TEXT {
324            match array_index {
325                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => {
326                    if let PropertyValue::CharacterString(s) = value {
327                        self.state_text[(idx - 1) as usize] = s;
328                        return Ok(());
329                    }
330                    return Err(common::invalid_data_type_error());
331                }
332                None => return Err(common::write_access_denied_error()),
333                _ => return Err(common::invalid_array_index_error()),
334            }
335        }
336        if property == PropertyIdentifier::ALARM_VALUES {
337            let values = decode_alarm_values_write(array_index, value)?;
338            self.set_alarm_values(values);
339            return Ok(());
340        }
341        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
342            if let PropertyValue::Boolean(v) = value {
343                self.event_detection_enable = v;
344                if !v {
345                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
346                    self.event_detector.acked_transitions = 0b111;
347                    self.event_detector.pending = None;
348                    self.event_detector.fault_reliability = None;
349                    self.event_history.reset();
350                }
351                return Ok(());
352            }
353            return Err(common::invalid_data_type_error());
354        }
355        if let Some(result) = write_generic_event_properties!(self, property, value) {
356            return result;
357        }
358        if let Some(result) = self.reliability_inhibit.write_inhibit(
359            &mut self.reliability,
360            self.out_of_service,
361            property,
362            &value,
363        ) {
364            result?;
365            let _ = self.recompute_reliability();
366            return Ok(());
367        }
368        if let Some(result) = self.reliability_inhibit.write_out_of_service(
369            &mut self.out_of_service,
370            &mut self.reliability,
371            &mut self.reliability_before_out_of_service,
372            property,
373            &value,
374        ) {
375            result?;
376            let _ = self.recompute_reliability();
377            return Ok(());
378        }
379        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
380            return result;
381        }
382        if let Some(result) = common::write_description(&mut self.description, property, &value) {
383            return result;
384        }
385        // Clause 12.20, while Out_Of_Service is TRUE: "the Present_Value property and
386        // the Reliability property, if present and capable of taking on values other
387        // than NO_FAULT_DETECTED, shall be writable to allow simulating specific
388        // conditions or for testing purposes".
389        // `is_writable_property` stays statically true because it describes capability.
390        if let Some(result) = self.reliability_inhibit.write_client_reliability(
391            self.out_of_service,
392            &mut self.reliability,
393            property,
394            &value,
395        ) {
396            return result;
397        }
398        if property == PropertyIdentifier::RELINQUISH_DEFAULT {
399            if let PropertyValue::Unsigned(u) = value {
400                let v = common::u64_to_u32(u)?;
401                return self.set_relinquish_default(v);
402            }
403            return Err(common::invalid_data_type_error());
404        }
405        Err(common::write_access_denied_error())
406    }
407
408    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
409        static PROPS: &[PropertyIdentifier] = &[
410            PropertyIdentifier::OBJECT_IDENTIFIER,
411            PropertyIdentifier::OBJECT_NAME,
412            PropertyIdentifier::DESCRIPTION,
413            PropertyIdentifier::OBJECT_TYPE,
414            PropertyIdentifier::PRESENT_VALUE,
415            PropertyIdentifier::STATUS_FLAGS,
416            PropertyIdentifier::EVENT_STATE,
417            PropertyIdentifier::EVENT_DETECTION_ENABLE,
418            PropertyIdentifier::EVENT_ENABLE,
419            PropertyIdentifier::TIME_DELAY,
420            PropertyIdentifier::TIME_DELAY_NORMAL,
421            PropertyIdentifier::NOTIFY_TYPE,
422            PropertyIdentifier::NOTIFICATION_CLASS,
423            PropertyIdentifier::ACKED_TRANSITIONS,
424            PropertyIdentifier::EVENT_TIME_STAMPS,
425            PropertyIdentifier::EVENT_MESSAGE_TEXTS,
426            PropertyIdentifier::OUT_OF_SERVICE,
427            PropertyIdentifier::NUMBER_OF_STATES,
428            PropertyIdentifier::PRIORITY_ARRAY,
429            PropertyIdentifier::RELINQUISH_DEFAULT,
430            PropertyIdentifier::CURRENT_COMMAND_PRIORITY,
431            PropertyIdentifier::RELIABILITY,
432            PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT,
433            PropertyIdentifier::STATE_TEXT,
434            PropertyIdentifier::ALARM_VALUES,
435        ];
436        Cow::Borrowed(PROPS)
437    }
438
439    fn is_createable(&self) -> bool {
440        true
441    }
442    fn set_reliability_internal(&mut self, reliability: u32) -> Result<(), Error> {
443        if self.out_of_service || self.reliability_inhibit.enabled() {
444            return Err(common::write_access_denied_error());
445        }
446        if !common::is_reliability_value_valid(reliability) {
447            return Err(common::value_out_of_range_error());
448        }
449        self.reliability = reliability;
450        self.reliability_evaluator.clear_ownership();
451        Ok(())
452    }
453
454    fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
455        Ok(self.recompute_reliability())
456    }
457
458    fn reliability_evaluation_inhibited_internal(&self) -> bool {
459        self.reliability_inhibit.enabled()
460    }
461
462    fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
463        // Mirrors the MultiStateValue `write_property` arms. The generic event
464        // set became writable with #229: Clause 12.20 requires the supported
465        // Event_Enable value set to include (T, T, T), and these detectors
466        // default to (F, F, F) with, previously, no commissioning path at all.
467        common::is_multistate_commandable_writable(property)
468            || common::is_generic_event_property_writable(property)
469            || property == PropertyIdentifier::RELIABILITY
470            || property == PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT
471            || property == PropertyIdentifier::EVENT_DETECTION_ENABLE
472            || property == PropertyIdentifier::ALARM_VALUES
473    }
474}
475
476#[cfg(test)]
477mod detection_enable_tests {
478    use super::*;
479
480    #[test]
481    fn msv_detection_enable_resets_and_gates_intrinsic_reporting() {
482        let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
483        assert_eq!(
484            msv.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
485                .unwrap(),
486            PropertyValue::Boolean(true)
487        );
488        msv.event_detector.alarm_values = vec![2];
489        msv.event_detector.time_delay = 2;
490        msv.write_property(
491            PropertyIdentifier::PRESENT_VALUE,
492            None,
493            PropertyValue::Unsigned(2),
494            Some(8),
495        )
496        .unwrap();
497        assert_eq!(msv.evaluate_intrinsic_reporting(), None);
498        assert!(msv.event_detector.pending.is_some());
499
500        msv.event_detector.event_state = bacnet_types::enums::EventState::OFFNORMAL;
501        msv.event_detector.acked_transitions = 0;
502        msv.event_detector.fault_reliability = Some(1);
503        msv.write_property(
504            PropertyIdentifier::EVENT_DETECTION_ENABLE,
505            None,
506            PropertyValue::Boolean(false),
507            None,
508        )
509        .unwrap();
510
511        assert_eq!(
512            msv.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
513                .unwrap(),
514            PropertyValue::Boolean(false)
515        );
516        assert_eq!(
517            msv.read_property(PropertyIdentifier::EVENT_STATE, None)
518                .unwrap(),
519            PropertyValue::Enumerated(bacnet_types::enums::EventState::NORMAL.to_raw())
520        );
521        assert_eq!(
522            msv.read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
523                .unwrap(),
524            PropertyValue::BitString {
525                unused_bits: 5,
526                data: vec![0xe0],
527            }
528        );
529        assert!(msv.event_detector.pending.is_none());
530        assert!(msv.event_detector.fault_reliability.is_none());
531        assert_eq!(msv.evaluate_intrinsic_reporting(), None);
532        assert_eq!(msv.tick_intrinsic_reporting(), None);
533        assert!(
534            msv.event_detector.pending.is_none(),
535            "evaluate/tick re-armed a countdown while detection is disabled"
536        );
537        assert!(msv
538            .property_list()
539            .contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
540        assert!(msv.is_writable_property(PropertyIdentifier::EVENT_DETECTION_ENABLE));
541    }
542}
543
544#[cfg(test)]
545mod reliability_evaluator_tests {
546    use super::*;
547    use bacnet_types::enums::Reliability;
548
549    #[test]
550    fn configuration_error_dominates_bypassed_invalid_present_value() {
551        let mut msv = MultiStateValueObject::new(1, "MSV-dominance", 2).unwrap();
552        msv.present_value = 3;
553        msv.event_detector.alarm_values = vec![3];
554
555        msv.evaluate_reliability_internal().unwrap();
556        assert_eq!(
557            msv.reliability,
558            Reliability::CONFIGURATION_ERROR.to_raw(),
559            "invalid configuration must dominate invalid Present_Value"
560        );
561        msv.event_detector.alarm_values = vec![1];
562        msv.evaluate_reliability_internal().unwrap();
563        assert_eq!(
564            msv.reliability,
565            Reliability::MULTI_STATE_OUT_OF_RANGE.to_raw()
566        );
567        msv.set_number_of_states(3).unwrap();
568        assert_eq!(
569            msv.reliability,
570            Reliability::NO_FAULT_DETECTED.to_raw(),
571            "count growth must synchronously recover the retained Present_Value"
572        );
573    }
574}