Skip to main content

bacnet_objects/multistate/
value.rs

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