Skip to main content

bacnet_objects/multistate/
input.rs

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