Skip to main content

bacnet_objects/multistate/
output.rs

1use super::*;
2use crate::event::CommandFailureDetector;
3use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
4use crate::property_metadata::PropertyMetadata;
5use std::sync::Arc;
6
7mod metadata;
8
9// ---------------------------------------------------------------------------
10// MultiStateOutput (type 14)
11// ---------------------------------------------------------------------------
12
13/// BACnet Multi-State Output object.
14///
15/// Commandable multi-state output with 16-level priority array.
16/// Present_Value is Unsigned, range 1..=number_of_states.
17pub struct MultiStateOutputObject {
18    profile: ProfileState,
19    oid: ObjectIdentifier,
20    name: String,
21    description: String,
22    present_value: u32,
23    feedback_value: u32,
24    number_of_states: u32,
25    out_of_service: bool,
26    status_flags: StatusFlags,
27    priority_array: [Option<u32>; 16],
28    relinquish_default: u32,
29    /// Reliability; NO_FAULT_DETECTED until a fault is evaluated or simulated.
30    reliability: Reliability,
31    reliability_before_out_of_service: Option<Reliability>,
32    reliability_inhibit: common::ReliabilityInhibitState,
33    reliability_evaluator: MultiStateReliabilityState,
34    event_detection_enable: bool,
35    state_text: Vec<String>,
36    /// COMMAND_FAILURE event detector.
37    event_detector: CommandFailureDetector,
38    pub(crate) event_history: EventHistory,
39    /// Implemented paired command-source tracking (Clause 19.5).
40    value_source: crate::command_source::ValueSourceTracking,
41}
42
43impl MultiStateOutputObject {
44    /// Create a new Multi-state Output object; `number_of_states` must be at least 1.
45    pub fn new(
46        instance: u32,
47        name: impl Into<String>,
48        number_of_states: u32,
49    ) -> Result<Self, Error> {
50        let oid = ObjectIdentifier::new(ObjectType::MULTI_STATE_OUTPUT, instance)?;
51        require_nonzero_states(number_of_states)?;
52        Ok(Self {
53            profile: ProfileState::default(),
54            oid,
55            name: name.into(),
56            description: String::new(),
57            present_value: 1,
58            feedback_value: 1,
59            number_of_states,
60            out_of_service: false,
61            status_flags: StatusFlags::empty(),
62            priority_array: [None; 16],
63            relinquish_default: 1,
64            reliability: Reliability::NO_FAULT_DETECTED,
65            reliability_before_out_of_service: None,
66            reliability_inhibit: common::ReliabilityInhibitState::default(),
67            reliability_evaluator: MultiStateReliabilityState::default(),
68            event_detection_enable: false,
69            state_text: (1..=number_of_states)
70                .map(|i| format!("State {i}"))
71                .collect(),
72            event_detector: CommandFailureDetector::default(),
73            event_history: EventHistory::default(),
74            value_source: crate::command_source::ValueSourceTracking::default(),
75        })
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 independently of commands and
94    /// Out_Of_Service; the text rows are network read-only. Invalid configuration
95    /// leaves the 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    /// Set the description string.
107    pub fn set_description(&mut self, desc: impl Into<String>) {
108        self.description = desc.into();
109    }
110
111    fn recalculate_present_value(&mut self) {
112        self.present_value =
113            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
114        let _ = self.recompute_reliability();
115    }
116
117    /// The values the object keeps that name a state, which a new count
118    /// may not strand: the commands in Priority_Array, Present_Value and
119    /// Relinquish_Default. Feedback_Value is left out: it is sensed, and
120    /// outside the states it is reported as CONFIGURATION_ERROR rather than
121    /// refused.
122    fn held_states(&self) -> impl Iterator<Item = u32> + '_ {
123        self.priority_array
124            .iter()
125            .flatten()
126            .copied()
127            .chain([self.present_value, self.relinquish_default])
128    }
129
130    fn configuration_invalid(&self) -> bool {
131        let is_invalid = |value: u32| !(1..=self.number_of_states).contains(&value);
132        self.priority_array
133            .iter()
134            .flatten()
135            .copied()
136            .any(is_invalid)
137            || is_invalid(self.relinquish_default)
138            || is_invalid(self.feedback_value)
139    }
140
141    fn recompute_reliability(&mut self) -> ReliabilityEvaluation {
142        if self.out_of_service || self.reliability_inhibit.enabled() {
143            return ReliabilityEvaluation::Unchanged;
144        }
145        let configuration_invalid = self.configuration_invalid();
146        self.reliability_evaluator.evaluate(
147            configuration_invalid,
148            self.present_value,
149            self.number_of_states,
150            &mut self.reliability,
151        )
152    }
153
154    /// Change the locally configured number of states.
155    ///
156    /// The BACnet `Number_Of_States` property remains read-only. A successful
157    /// local change resizes State_Text and immediately re-evaluates Reliability;
158    /// retained commands, defaults, feedback, and Present_Value are not repaired.
159    pub fn set_number_of_states(&mut self, number_of_states: u32) -> Result<(), Error> {
160        resize_state_text(
161            &mut self.number_of_states,
162            &mut self.state_text,
163            number_of_states,
164        )?;
165        let _ = self.recompute_reliability();
166        Ok(())
167    }
168
169    /// Set the Relinquish_Default (#270).
170    ///
171    /// Validated the same way a commanded Present_Value is (Unsigned
172    /// 1..=Number_Of_States); after the store, Present_Value is resolved anew
173    /// from the priority array so an empty array falls back to the new
174    /// default immediately.
175    ///
176    /// Number_Of_States shrink interplay: if the state count ever shrinks
177    /// below this value, the standard leaves adjustment of Priority_Array,
178    /// Relinquish_Default, Present_Value, and Feedback_Value to local policy
179    /// (Clause 12.19 / Table 12-22 Number_Of_States text). This implementation does
180    /// NOT auto-adjust: out-of-range stored values are a configuration
181    /// decision for the application to resolve; the object-owned evaluator
182    /// reports CONFIGURATION_ERROR while that retained condition remains.
183    pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
184        if value < 1 || value > self.number_of_states {
185            return Err(common::value_out_of_range_error());
186        }
187        self.relinquish_default = value;
188        self.recalculate_present_value();
189        Ok(())
190    }
191}
192
193impl BACnetObject for MultiStateOutputObject {
194    fn object_identifier(&self) -> ObjectIdentifier {
195        self.oid
196    }
197
198    fn object_name(&self) -> &str {
199        &self.name
200    }
201
202    fn supports_cov(&self) -> bool {
203        true
204    }
205
206    crate::event::impl_builtin_intrinsic_reporting!(
207        event_detector,
208        event_history,
209        [present_value, feedback_value],
210        reliability,
211        event_detection_enable,
212        CommandFailureDetector::ALGORITHM
213    );
214
215    fn acknowledge_alarm_correlated_internal(
216        &mut self,
217        event_state: EventState,
218        timestamp: &BACnetTimeStamp,
219    ) -> Result<(), Error> {
220        if !self.event_detection_enable {
221            return Err(Error::Protocol {
222                class: ErrorClass::OBJECT.to_raw() as u32,
223                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
224            });
225        }
226        self.event_history.acknowledge_correlated(
227            &mut self.event_detector.acked_transitions,
228            event_state,
229            timestamp,
230        )
231    }
232
233    fn read_property(
234        &self,
235        property: PropertyIdentifier,
236        array_index: Option<u32>,
237    ) -> Result<PropertyValue, Error> {
238        if let Some(result) = self
239            .value_source
240            .read(property, array_index, &self.priority_array)
241        {
242            return result;
243        }
244
245        if property == PropertyIdentifier::STATUS_FLAGS {
246            return Ok(common::compute_status_flags(
247                self.status_flags,
248                self.reliability,
249                self.out_of_service,
250                self.event_detector.event_state,
251            ));
252        }
253        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
254            return Ok(PropertyValue::Boolean(self.event_detection_enable));
255        }
256        if let Some(value) = self.reliability_inhibit.read(property) {
257            return Ok(value);
258        }
259        if let Some(result) = read_common_properties!(self, property, array_index) {
260            return result;
261        }
262        if let Some(result) = read_generic_event_properties!(self, property) {
263            return result;
264        }
265        if let Some(result) = self.event_history.read(property, array_index) {
266            return result;
267        }
268        if let Some(result) = self.profile.read(property, array_index) {
269            return result;
270        }
271        match property {
272            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
273                ObjectType::MULTI_STATE_OUTPUT.to_raw(),
274            )),
275            p if p == PropertyIdentifier::PRESENT_VALUE => {
276                Ok(PropertyValue::Unsigned(self.present_value as u64))
277            }
278            p if p == PropertyIdentifier::FEEDBACK_VALUE => {
279                Ok(PropertyValue::Unsigned(self.feedback_value as u64))
280            }
281            p if p == PropertyIdentifier::NUMBER_OF_STATES => {
282                Ok(PropertyValue::Unsigned(self.number_of_states as u64))
283            }
284            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
285                common::read_priority_array!(self, array_index, |v: u32| PropertyValue::Unsigned(
286                    v as u64
287                ))
288            }
289            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
290                Ok(PropertyValue::Unsigned(self.relinquish_default as u64))
291            }
292            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
293                Ok(common::current_command_priority(&self.priority_array))
294            }
295            p if p == PropertyIdentifier::STATE_TEXT => match array_index {
296                None => Ok(PropertyValue::List(
297                    self.state_text
298                        .iter()
299                        .map(|s| PropertyValue::CharacterString(s.clone()))
300                        .collect(),
301                )),
302                Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
303                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
304                    PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
305                ),
306                _ => Err(common::invalid_array_index_error()),
307            },
308            _ => Err(common::unknown_property_error()),
309        }
310    }
311
312    fn write_property_from(
313        &mut self,
314        property: PropertyIdentifier,
315        array_index: Option<u32>,
316        value: PropertyValue,
317        priority: Option<u8>,
318        origin: &crate::command_source::CommandOrigin,
319    ) -> Result<(), Error> {
320        if matches!(
321            property,
322            PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
323        ) && array_index.is_some()
324        {
325            return Err(common::property_is_not_an_array_error());
326        }
327        if property == PropertyIdentifier::VALUE_SOURCE {
328            return self.value_source.correct(value, priority, origin);
329        }
330        if property == PropertyIdentifier::PRESENT_VALUE {
331            let num_states = self.number_of_states;
332            return crate::command_source::write_sourced_priority!(
333                self,
334                value,
335                priority,
336                origin,
337                |v| {
338                    if let PropertyValue::Unsigned(u) = v {
339                        if u < 1 || u > num_states as u64 {
340                            Err(common::value_out_of_range_error())
341                        } else {
342                            Ok(u as u32)
343                        }
344                    } else {
345                        Err(common::invalid_data_type_error())
346                    }
347                }
348            );
349        }
350        self.write_property(property, array_index, value, priority)
351    }
352
353    fn write_property(
354        &mut self,
355        property: PropertyIdentifier,
356        array_index: Option<u32>,
357        value: PropertyValue,
358        _priority: Option<u8>,
359    ) -> Result<(), Error> {
360        if matches!(
361            property,
362            PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
363        ) {
364            return Err(common::write_access_denied_error());
365        }
366        if property == PropertyIdentifier::FEEDBACK_VALUE {
367            if let PropertyValue::Unsigned(u) = value {
368                // Checked for representability but deliberately NOT range-checked against
369                // Number_Of_States, unlike Present_Value. Clause 12.19 treats a
370                // Feedback_Value outside the state set as a condition to be *reported* —
371                // for the Number_Of_States-bounded properties apart from Present_Value,
372                // an out-of-range value pins Reliability at CONFIGURATION_ERROR until
373                // fixed — not as a value to refuse. Feedback_Value reflects a sensed quantity
374                // determined by local policy, so it can legitimately fall
375                // outside the configured range; refusing it would make CONFIGURATION_ERROR
376                // unreachable. The object-owned evaluator applies that reliability.
377                //
378                // The u32 conversion is still checked. A BACnet Unsigned decodes from up
379                // to 8 octets, so a bare `as u32` would wrap a large value back into the
380                // valid state range — silently turning a disagreeing feedback into an
381                // agreeing one and suppressing the COMMAND_FAILURE transition. That is a
382                // representation limit, not a configuration limit, so it is enforced here
383                // while the state-set range is not.
384                self.feedback_value = common::u64_to_u32(u)?;
385                let _ = self.recompute_reliability();
386                return Ok(());
387            }
388            return Err(common::invalid_data_type_error());
389        }
390        if property == PropertyIdentifier::STATE_TEXT {
391            // Written whole, State_Text sets Number_Of_States too (#1443).
392            let held: Vec<u32> = self.held_states().collect();
393            write_state_text(
394                &mut self.number_of_states,
395                &mut self.state_text,
396                held,
397                array_index,
398                value,
399            )?;
400            let _ = self.recompute_reliability();
401            return Ok(());
402        }
403        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
404            if let PropertyValue::Boolean(v) = value {
405                self.event_detection_enable = v;
406                if !v {
407                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
408                    self.event_detector.acked_transitions =
409                        bacnet_types::bitstring::EventTransitionBits::all();
410                    self.event_detector.pending = None;
411                    self.event_detector.fault_reliability = None;
412                    self.event_history.reset();
413                }
414                return Ok(());
415            }
416            return Err(common::invalid_data_type_error());
417        }
418        // Event_Message_Texts_Config and the Event_Algorithm_Inhibit pair (#1329).
419        if let Some(result) =
420            self.event_history
421                .write(property, array_index, &value, self.event_detection_enable)
422        {
423            return result;
424        }
425        if let Some(result) = write_generic_event_properties!(self, property, value) {
426            return result;
427        }
428        if let Some(result) = self.reliability_inhibit.write_inhibit(
429            &mut self.reliability,
430            self.out_of_service,
431            property,
432            &value,
433        ) {
434            result?;
435            let _ = self.recompute_reliability();
436            return Ok(());
437        }
438        if let Some(result) = self.reliability_inhibit.write_out_of_service(
439            &mut self.out_of_service,
440            &mut self.reliability,
441            &mut self.reliability_before_out_of_service,
442            property,
443            &value,
444        ) {
445            if result? == crate::reliability_inhibit::OutOfServiceWrite::Applied {
446                let _ = self.recompute_reliability();
447            }
448            return Ok(());
449        }
450        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
451            return result;
452        }
453        if let Some(result) = common::write_description(&mut self.description, property, &value) {
454            return result;
455        }
456        // Clause 12.19 requires simulation/test writes while Out_Of_Service is TRUE:
457        // Present_Value is writable, as is Reliability when that property exists
458        // and supports values beyond NO_FAULT_DETECTED.
459        // `is_writable_property` stays statically true because it describes capability.
460        if let Some(result) = self.reliability_inhibit.write_client_reliability(
461            self.out_of_service,
462            &mut self.reliability,
463            property,
464            &value,
465        ) {
466            return result;
467        }
468        if property == PropertyIdentifier::RELINQUISH_DEFAULT {
469            if let PropertyValue::Unsigned(u) = value {
470                let v = common::u64_to_u32(u)?;
471                return self.set_relinquish_default(v);
472            }
473            return Err(common::invalid_data_type_error());
474        }
475        if let Some(result) = self.profile.write(property, array_index, &value) {
476            return result;
477        }
478        Err(crate::common::unhandled_write_error(
479            self.property_metadata().as_ref(),
480            property,
481            array_index,
482        ))
483    }
484
485    fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
486        self.profile.capability()
487    }
488
489    fn advance_monotonic_time_internal(&mut self, now: std::time::Duration) -> bool {
490        self.profile.expire(now);
491        false
492    }
493
494    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
495        metadata::for_object(self)
496    }
497
498    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
499        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
500    }
501
502    fn is_createable(&self) -> bool {
503        true
504    }
505    fn creation_only_properties(&self) -> &'static [PropertyIdentifier] {
506        CREATION_ONLY
507    }
508    fn initialize_property(
509        &mut self,
510        property: PropertyIdentifier,
511        value: PropertyValue,
512    ) -> Result<(), Error> {
513        let held: Vec<u32> = self.held_states().collect();
514        initialize_states(
515            &mut self.number_of_states,
516            &mut self.state_text,
517            held,
518            property,
519            value,
520        )?;
521        let _ = self.recompute_reliability();
522        Ok(())
523    }
524    fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
525        if self.out_of_service || self.reliability_inhibit.enabled() {
526            return Err(common::write_access_denied_error());
527        }
528        if !common::is_reliability_value_valid(reliability) {
529            return Err(common::value_out_of_range_error());
530        }
531        self.reliability = reliability;
532        self.reliability_evaluator.clear_ownership();
533        Ok(())
534    }
535
536    fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
537        Ok(self.recompute_reliability())
538    }
539
540    fn reliability_evaluation_inhibited_internal(&self) -> bool {
541        self.reliability_inhibit.enabled()
542    }
543}
544
545#[cfg(test)]
546#[path = "output/tests.rs"]
547mod command_failure_tests;
548
549#[cfg(test)]
550mod reliability_evaluator_tests;