Skip to main content

bacnet_objects/multistate/
input.rs

1use super::*;
2use crate::property_metadata::PropertyMetadata;
3
4mod metadata;
5
6// ---------------------------------------------------------------------------
7// MultiStateInput (type 13)
8// ---------------------------------------------------------------------------
9
10/// BACnet Multi-State Input object.
11///
12/// Read-only multi-state point. Present_Value is writable only when out-of-service.
13/// Present_Value is Unsigned, range 1..=number_of_states.
14pub struct MultiStateInputObject {
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    /// Reliability; NO_FAULT_DETECTED until a fault is evaluated or simulated.
23    reliability: Reliability,
24    reliability_before_out_of_service: Option<Reliability>,
25    reliability_inhibit: common::ReliabilityInhibitState,
26    reliability_evaluator: MultiStateReliabilityState,
27    state_text: Vec<String>,
28    /// CHANGE_OF_STATE event detector.
29    event_detector: ChangeOfStateDetector,
30    /// Event_Detection_Enable (Clause 12.18). A FALSE value suspends
31    /// event-state-machine evaluation under Clause 13.2.2.1.
32    event_detection_enable: bool,
33    pub(crate) event_history: EventHistory,
34}
35
36impl MultiStateInputObject {
37    /// Create a new Multi-state Input object; `number_of_states` must be at least 1.
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_INPUT, 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            reliability: Reliability::NO_FAULT_DETECTED,
54            reliability_before_out_of_service: None,
55            reliability_inhibit: common::ReliabilityInhibitState::default(),
56            reliability_evaluator: MultiStateReliabilityState::default(),
57            state_text: (1..=number_of_states)
58                .map(|i| format!("State {i}"))
59                .collect(),
60            event_detector: ChangeOfStateDetector::default(),
61            event_detection_enable: true,
62            event_history: EventHistory::default(),
63        })
64    }
65
66    /// Set the alarm values (states that trigger OFFNORMAL).
67    ///
68    /// Unlike a network write, which refuses a state past Number_Of_States
69    /// (#1429), this takes any state.
70    pub fn set_alarm_values(&mut self, values: Vec<u32>) {
71        self.event_detector.alarm_values = values;
72    }
73
74    /// Mutate `Present_Value` on an unattached or otherwise application-owned object.
75    ///
76    /// This low-level helper bypasses running-server `Out_Of_Service` ownership,
77    /// range validation, intrinsic-event processing, and COV processing, while
78    /// retaining this object's reliability recomputation. Applications updating
79    /// a live object should use `BACnetServer::set_present_value_local`.
80    pub fn set_present_value(&mut self, value: u32) {
81        self.present_value = value;
82        let _ = self.recompute_reliability();
83    }
84
85    /// Validate and store a `Present_Value` write, without any access check.
86    ///
87    /// Shared by the network and internal routes, which differ only in the
88    /// `Out_Of_Service` condition each requires.
89    fn apply_present_value(&mut self, value: PropertyValue) -> Result<(), Error> {
90        let PropertyValue::Unsigned(v) = value else {
91            return Err(common::invalid_data_type_error());
92        };
93        if v < 1 || v > self.number_of_states as u64 {
94            return Err(common::value_out_of_range_error());
95        }
96        self.present_value = v as u32;
97        let _ = self.recompute_reliability();
98        Ok(())
99    }
100
101    /// Change the locally configured number of states.
102    ///
103    /// The BACnet `Number_Of_States` property remains read-only. A successful
104    /// local change retains the State_Text prefix, truncates its tail on
105    /// shrink, appends constructor-style labels on growth, and immediately
106    /// re-evaluates Reliability without repairing Present_Value.
107    pub fn set_number_of_states(&mut self, number_of_states: u32) -> Result<(), Error> {
108        resize_state_text(
109            &mut self.number_of_states,
110            &mut self.state_text,
111            number_of_states,
112        )?;
113        let _ = self.recompute_reliability();
114        Ok(())
115    }
116
117    /// Set the description string.
118    pub fn set_description(&mut self, desc: impl Into<String>) {
119        self.description = desc.into();
120    }
121
122    /// The values the object keeps that name a state, which a new count
123    /// may not strand: Present_Value and Alarm_Values.
124    fn held_states(&self) -> impl Iterator<Item = u32> + '_ {
125        std::iter::once(self.present_value).chain(self.event_detector.alarm_values.iter().copied())
126    }
127
128    fn recompute_reliability(&mut self) -> ReliabilityEvaluation {
129        if self.out_of_service || self.reliability_inhibit.enabled() {
130            return ReliabilityEvaluation::Unchanged;
131        }
132        self.reliability_evaluator.evaluate(
133            false,
134            self.present_value,
135            self.number_of_states,
136            &mut self.reliability,
137        )
138    }
139}
140
141impl BACnetObject for MultiStateInputObject {
142    fn object_identifier(&self) -> ObjectIdentifier {
143        self.oid
144    }
145
146    fn object_name(&self) -> &str {
147        &self.name
148    }
149
150    fn supports_cov(&self) -> bool {
151        true
152    }
153
154    crate::event::impl_builtin_intrinsic_reporting!(
155        event_detector,
156        event_history,
157        [present_value],
158        reliability,
159        event_detection_enable,
160        ChangeOfStateDetector::ALGORITHM
161    );
162
163    fn acknowledge_alarm_correlated_internal(
164        &mut self,
165        event_state: EventState,
166        timestamp: &BACnetTimeStamp,
167    ) -> Result<(), Error> {
168        if !self.event_detection_enable {
169            return Err(Error::Protocol {
170                class: ErrorClass::OBJECT.to_raw() as u32,
171                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
172            });
173        }
174        self.event_history.acknowledge_correlated(
175            &mut self.event_detector.acked_transitions,
176            event_state,
177            timestamp,
178        )
179    }
180
181    fn read_property(
182        &self,
183        property: PropertyIdentifier,
184        array_index: Option<u32>,
185    ) -> Result<PropertyValue, Error> {
186        if property == PropertyIdentifier::STATUS_FLAGS {
187            return Ok(common::compute_status_flags(
188                self.status_flags,
189                self.reliability,
190                self.out_of_service,
191                self.event_detector.event_state,
192            ));
193        }
194        if let Some(value) = self.reliability_inhibit.read(property) {
195            return Ok(value);
196        }
197        if let Some(result) = read_common_properties!(self, property, array_index) {
198            return result;
199        }
200        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
201            return Ok(PropertyValue::Boolean(self.event_detection_enable));
202        }
203        if let Some(result) = read_generic_event_properties!(self, property) {
204            return result;
205        }
206        if let Some(result) = self.event_history.read(property, array_index) {
207            return result;
208        }
209        match property {
210            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
211                ObjectType::MULTI_STATE_INPUT.to_raw(),
212            )),
213            p if p == PropertyIdentifier::PRESENT_VALUE => {
214                Ok(PropertyValue::Unsigned(self.present_value as u64))
215            }
216            p if p == PropertyIdentifier::NUMBER_OF_STATES => {
217                Ok(PropertyValue::Unsigned(self.number_of_states as u64))
218            }
219            p if p == PropertyIdentifier::STATE_TEXT => match array_index {
220                None => Ok(PropertyValue::List(
221                    self.state_text
222                        .iter()
223                        .map(|s| PropertyValue::CharacterString(s.clone()))
224                        .collect(),
225                )),
226                Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
227                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
228                    PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
229                ),
230                _ => Err(common::invalid_array_index_error()),
231            },
232            p if p == PropertyIdentifier::ALARM_VALUES => Ok(PropertyValue::List(
233                self.event_detector
234                    .alarm_values
235                    .iter()
236                    .map(|v| PropertyValue::Unsigned(*v as u64))
237                    .collect(),
238            )),
239            _ => Err(common::unknown_property_error()),
240        }
241    }
242
243    fn write_property(
244        &mut self,
245        property: PropertyIdentifier,
246        array_index: Option<u32>,
247        value: PropertyValue,
248        _priority: Option<u8>,
249    ) -> Result<(), Error> {
250        if property == PropertyIdentifier::PRESENT_VALUE {
251            if !self.out_of_service {
252                return Err(common::write_access_denied_error());
253            }
254            return self.apply_present_value(value);
255        }
256        if property == PropertyIdentifier::STATE_TEXT {
257            // Written whole, State_Text sets Number_Of_States too (#1443).
258            let held: Vec<u32> = self.held_states().collect();
259            write_state_text(
260                &mut self.number_of_states,
261                &mut self.state_text,
262                held,
263                array_index,
264                value,
265            )?;
266            let _ = self.recompute_reliability();
267            return Ok(());
268        }
269        if property == PropertyIdentifier::ALARM_VALUES {
270            let values = decode_alarm_values_write(array_index, value, self.number_of_states)?;
271            self.event_detector.alarm_values = values;
272            return Ok(());
273        }
274        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
275            if let PropertyValue::Boolean(v) = value {
276                self.event_detection_enable = v;
277                if !v {
278                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
279                    self.event_detector.acked_transitions =
280                        bacnet_types::bitstring::EventTransitionBits::all();
281                    self.event_detector.pending = None;
282                    self.event_detector.fault_reliability = None;
283                    self.event_history.reset();
284                }
285                return Ok(());
286            }
287            return Err(common::invalid_data_type_error());
288        }
289        // Event_Message_Texts_Config and the Event_Algorithm_Inhibit pair (#1329).
290        if let Some(result) =
291            self.event_history
292                .write(property, array_index, &value, self.event_detection_enable)
293        {
294            return result;
295        }
296        if let Some(result) = write_generic_event_properties!(self, property, value) {
297            return result;
298        }
299        if let Some(result) = self.reliability_inhibit.write_inhibit(
300            &mut self.reliability,
301            self.out_of_service,
302            property,
303            &value,
304        ) {
305            result?;
306            let _ = self.recompute_reliability();
307            return Ok(());
308        }
309        if let Some(result) = self.reliability_inhibit.write_out_of_service(
310            &mut self.out_of_service,
311            &mut self.reliability,
312            &mut self.reliability_before_out_of_service,
313            property,
314            &value,
315        ) {
316            if result? == crate::reliability_inhibit::OutOfServiceWrite::Applied {
317                let _ = self.recompute_reliability();
318            }
319            return Ok(());
320        }
321        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
322            return result;
323        }
324        if let Some(result) = common::write_description(&mut self.description, property, &value) {
325            return result;
326        }
327        // Clause 12.18 requires simulation/test writes while Out_Of_Service is TRUE:
328        // Present_Value is writable, as is Reliability when that property exists
329        // and supports values beyond NO_FAULT_DETECTED.
330        // `is_writable_property` stays statically true because it describes capability.
331        if let Some(result) = self.reliability_inhibit.write_client_reliability(
332            self.out_of_service,
333            &mut self.reliability,
334            property,
335            &value,
336        ) {
337            return result;
338        }
339        Err(crate::common::unhandled_write_error(
340            self.property_metadata().as_ref(),
341            property,
342            array_index,
343        ))
344    }
345
346    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
347        metadata::for_object(self)
348    }
349
350    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
351        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
352    }
353
354    fn is_createable(&self) -> bool {
355        true
356    }
357    fn creation_only_properties(&self) -> &'static [PropertyIdentifier] {
358        CREATION_ONLY
359    }
360    fn initialize_property(
361        &mut self,
362        property: PropertyIdentifier,
363        value: PropertyValue,
364    ) -> Result<(), Error> {
365        let held: Vec<u32> = self.held_states().collect();
366        initialize_states(
367            &mut self.number_of_states,
368            &mut self.state_text,
369            held,
370            property,
371            value,
372        )?;
373        let _ = self.recompute_reliability();
374        Ok(())
375    }
376    fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
377        if self.out_of_service || self.reliability_inhibit.enabled() {
378            return Err(common::write_access_denied_error());
379        }
380        if !common::is_reliability_value_valid(reliability) {
381            return Err(common::value_out_of_range_error());
382        }
383        self.reliability = reliability;
384        self.reliability_evaluator.clear_ownership();
385        Ok(())
386    }
387
388    fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
389        // Local safe-ownership policy: preserve the client's OOS simulation.
390        if self.out_of_service {
391            return Err(common::write_access_denied_error());
392        }
393        self.apply_present_value(value)
394    }
395
396    fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
397        Ok(self.recompute_reliability())
398    }
399
400    fn reliability_evaluation_inhibited_internal(&self) -> bool {
401        self.reliability_inhibit.enabled()
402    }
403}
404
405#[cfg(test)]
406mod detection_enable_tests {
407    use super::*;
408
409    #[test]
410    fn msi_detection_enable_resets_and_gates_intrinsic_reporting() {
411        let mut msi = MultiStateInputObject::new(1, "MSI-1", 3).unwrap();
412        assert_eq!(
413            msi.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
414                .unwrap(),
415            PropertyValue::Boolean(true)
416        );
417        msi.set_alarm_values(vec![2]);
418        msi.event_detector.time_delay = 2;
419        msi.set_present_value(2);
420        assert_eq!(msi.evaluate_intrinsic_reporting(), None);
421        assert!(msi.event_detector.pending.is_some());
422
423        msi.event_detector.event_state = bacnet_types::enums::EventState::OFFNORMAL;
424        msi.event_detector.acked_transitions =
425            bacnet_types::bitstring::EventTransitionBits::empty();
426        msi.event_detector.fault_reliability = Some(bacnet_types::enums::Reliability::NO_SENSOR);
427        msi.write_property(
428            PropertyIdentifier::EVENT_DETECTION_ENABLE,
429            None,
430            PropertyValue::Boolean(false),
431            None,
432        )
433        .unwrap();
434
435        assert_eq!(
436            msi.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
437                .unwrap(),
438            PropertyValue::Boolean(false)
439        );
440        assert_eq!(
441            msi.read_property(PropertyIdentifier::EVENT_STATE, None)
442                .unwrap(),
443            PropertyValue::Enumerated(bacnet_types::enums::EventState::NORMAL.to_raw())
444        );
445        assert_eq!(
446            msi.read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
447                .unwrap(),
448            PropertyValue::BitString {
449                unused_bits: 5,
450                data: vec![0xe0],
451            }
452        );
453        assert!(msi.event_detector.pending.is_none());
454        assert!(msi.event_detector.fault_reliability.is_none());
455        assert_eq!(msi.evaluate_intrinsic_reporting(), None);
456        assert_eq!(msi.tick_intrinsic_reporting(), None);
457        assert!(
458            msi.event_detector.pending.is_none(),
459            "evaluate/tick re-armed a countdown while detection is disabled"
460        );
461        assert!(msi
462            .property_list()
463            .contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
464        assert!(msi.is_writable_property(PropertyIdentifier::EVENT_DETECTION_ENABLE));
465    }
466}
467
468#[cfg(test)]
469mod reliability_safety_net_tests {
470    use super::*;
471    use crate::traits::ReliabilityEvaluation;
472    use bacnet_types::enums::Reliability;
473
474    #[test]
475    fn periodic_hook_repairs_one_stale_bypassed_state_then_is_unchanged() {
476        let mut msi = MultiStateInputObject::new(1, "MSI-stale", 2).unwrap();
477        msi.present_value = 3;
478
479        assert_eq!(
480            msi.evaluate_reliability_internal().unwrap(),
481            ReliabilityEvaluation::Changed {
482                old_reliability: Reliability::NO_FAULT_DETECTED,
483                new_reliability: Reliability::MULTI_STATE_OUT_OF_RANGE,
484            }
485        );
486        assert_eq!(
487            msi.evaluate_reliability_internal().unwrap(),
488            ReliabilityEvaluation::Unchanged
489        );
490    }
491}