Skip to main content

bacnet_objects/binary/
output.rs

1use super::*;
2use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
3use crate::prelude::*;
4use crate::property_metadata::PropertyMetadata;
5use alloc::sync::Arc;
6
7#[path = "output/metadata.rs"]
8mod metadata;
9
10// ---------------------------------------------------------------------------
11// BinaryOutput (type 4)
12// ---------------------------------------------------------------------------
13
14/// BACnet Binary Output object.
15///
16/// Commandable binary output with 16-level priority array.
17/// Uses Enumerated values: 0 = inactive, 1 = active.
18pub struct BinaryOutputObject {
19    profile: ProfileState,
20    oid: ObjectIdentifier,
21    name: String,
22    description: String,
23    present_value: u32,
24    feedback_value: u32,
25    out_of_service: bool,
26    status_flags: StatusFlags,
27    priority_array: [Option<u32>; 16],
28    relinquish_default: u32,
29    /// Polarity: 0 = normal, 1 = reverse.
30    polarity: u32,
31    /// Reliability; NO_FAULT_DETECTED until a fault is evaluated or simulated.
32    reliability: Reliability,
33    reliability_before_out_of_service: Option<Reliability>,
34    reliability_inhibit: common::ReliabilityInhibitState,
35    event_detection_enable: bool,
36    active_text: String,
37    inactive_text: String,
38    /// COMMAND_FAILURE event detector.
39    event_detector: CommandFailureDetector,
40    pub(crate) event_history: EventHistory,
41    /// Implemented paired command-source tracking (Clause 19.5).
42    value_source: crate::command_source::ValueSourceTracking,
43}
44
45impl BinaryOutputObject {
46    /// Create a new Binary Output object; fails if `instance` exceeds the object-identifier range.
47    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
48        let oid = ObjectIdentifier::new(ObjectType::BINARY_OUTPUT, instance)?;
49        Ok(Self {
50            profile: ProfileState::default(),
51            oid,
52            name: name.into(),
53            description: String::new(),
54            present_value: 0,
55            feedback_value: 0,
56            out_of_service: false,
57            status_flags: StatusFlags::empty(),
58            priority_array: [None; 16],
59            relinquish_default: 0,
60            polarity: 0,
61            reliability: Reliability::NO_FAULT_DETECTED,
62            reliability_before_out_of_service: None,
63            reliability_inhibit: common::ReliabilityInhibitState::default(),
64            event_detection_enable: false,
65            active_text: "Active".into(),
66            inactive_text: "Inactive".into(),
67            event_detector: CommandFailureDetector::default(),
68            event_history: EventHistory::default(),
69            value_source: crate::command_source::ValueSourceTracking::default(),
70        })
71    }
72
73    /// Build with application-owned Tags storage. Saved Tags override configured
74    /// Tags only while [`Self::set_profile`] provisions the row. Loaded data must
75    /// satisfy the shared Tags rules and opt-in 1 MiB snapshot limit.
76    pub fn with_tags_persistence(
77        instance: u32,
78        name: impl Into<String>,
79        persistence: Arc<dyn TagsPersistence>,
80    ) -> Result<Self, Error> {
81        let mut object = Self::new(instance, name)?;
82        object.profile = ProfileState::persistent(object.oid, persistence)?;
83        Ok(object)
84    }
85
86    /// Provision independent optional Tags, Profile_Location and Profile_Name
87    /// rows before registration. Tags is writable independently of commands and
88    /// Out_Of_Service; the text rows are network read-only. Invalid configuration
89    /// leaves the previous profile and its attached storage unchanged.
90    pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
91        self.profile.provision(profile)
92    }
93
94    /// Wait for queued save attempts to finish; writes report their own outcomes.
95    /// This is not a success receipt. Unstaged synchronous writes block their caller.
96    pub fn wait_for_tag_saves(&self) {
97        self.profile.wait_for_saves();
98    }
99
100    /// Set the description string.
101    pub fn set_description(&mut self, desc: impl Into<String>) {
102        self.description = desc.into();
103    }
104
105    fn recalculate_present_value(&mut self) {
106        self.present_value =
107            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
108    }
109
110    /// Set the Relinquish_Default (#270).
111    ///
112    /// Validated the same way a commanded Present_Value is (BinaryPV 0 or 1);
113    /// after the store, Present_Value is resolved anew from the priority
114    /// array so an empty array falls back to the new default immediately.
115    pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
116        if value > 1 {
117            return Err(common::value_out_of_range_error());
118        }
119        self.relinquish_default = value;
120        self.recalculate_present_value();
121        Ok(())
122    }
123}
124
125impl BACnetObject for BinaryOutputObject {
126    fn object_identifier(&self) -> ObjectIdentifier {
127        self.oid
128    }
129
130    fn object_name(&self) -> &str {
131        &self.name
132    }
133
134    fn supports_cov(&self) -> bool {
135        true
136    }
137
138    crate::event::impl_builtin_intrinsic_reporting!(
139        event_detector,
140        event_history,
141        [present_value, feedback_value],
142        reliability,
143        event_detection_enable,
144        CommandFailureDetector::ALGORITHM
145    );
146
147    fn acknowledge_alarm_correlated_internal(
148        &mut self,
149        event_state: EventState,
150        timestamp: &BACnetTimeStamp,
151    ) -> Result<(), Error> {
152        if !self.event_detection_enable {
153            return Err(Error::protocol(
154                ErrorClass::OBJECT,
155                ErrorCode::NO_ALARM_CONFIGURED,
156            ));
157        }
158        self.event_history.acknowledge_correlated(
159            &mut self.event_detector.acked_transitions,
160            event_state,
161            timestamp,
162        )
163    }
164
165    fn read_property(
166        &self,
167        property: PropertyIdentifier,
168        array_index: Option<u32>,
169    ) -> Result<PropertyValue, Error> {
170        if let Some(result) = self
171            .value_source
172            .read(property, array_index, &self.priority_array)
173        {
174            return result;
175        }
176
177        if property == PropertyIdentifier::STATUS_FLAGS {
178            return Ok(common::compute_status_flags(
179                self.status_flags,
180                self.reliability,
181                self.out_of_service,
182                self.event_detector.event_state,
183            ));
184        }
185        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
186            return Ok(PropertyValue::Boolean(self.event_detection_enable));
187        }
188        if let Some(value) = self.reliability_inhibit.read(property) {
189            return Ok(value);
190        }
191        if let Some(result) = read_common_properties!(self, property, array_index) {
192            return result;
193        }
194        if let Some(result) = read_generic_event_properties!(self, property) {
195            return result;
196        }
197        if let Some(result) = self.event_history.read(property, array_index) {
198            return result;
199        }
200        if let Some(result) = self.profile.read(property, array_index) {
201            return result;
202        }
203        match property {
204            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
205                ObjectType::BINARY_OUTPUT.to_raw(),
206            )),
207            p if p == PropertyIdentifier::PRESENT_VALUE => {
208                Ok(PropertyValue::Enumerated(self.present_value))
209            }
210            p if p == PropertyIdentifier::FEEDBACK_VALUE => {
211                Ok(PropertyValue::Enumerated(self.feedback_value))
212            }
213            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
214                common::read_priority_array!(self, array_index, PropertyValue::Enumerated)
215            }
216            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
217                Ok(PropertyValue::Enumerated(self.relinquish_default))
218            }
219            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
220                Ok(common::current_command_priority(&self.priority_array))
221            }
222            p if p == PropertyIdentifier::POLARITY => Ok(PropertyValue::Enumerated(self.polarity)),
223            p if p == PropertyIdentifier::ACTIVE_TEXT => {
224                Ok(PropertyValue::CharacterString(self.active_text.clone()))
225            }
226            p if p == PropertyIdentifier::INACTIVE_TEXT => {
227                Ok(PropertyValue::CharacterString(self.inactive_text.clone()))
228            }
229            _ => Err(common::unknown_property_error()),
230        }
231    }
232
233    fn write_property_from(
234        &mut self,
235        property: PropertyIdentifier,
236        array_index: Option<u32>,
237        value: PropertyValue,
238        priority: Option<u8>,
239        origin: &crate::command_source::CommandOrigin,
240    ) -> Result<(), Error> {
241        if matches!(
242            property,
243            PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
244        ) && array_index.is_some()
245        {
246            return Err(common::property_is_not_an_array_error());
247        }
248        if property == PropertyIdentifier::VALUE_SOURCE {
249            return self.value_source.correct(value, priority, origin);
250        }
251        if property == PropertyIdentifier::PRESENT_VALUE {
252            return crate::command_source::write_sourced_priority!(
253                self,
254                value,
255                priority,
256                origin,
257                |v| {
258                    if let PropertyValue::Enumerated(e) = v {
259                        if e > 1 {
260                            Err(common::value_out_of_range_error())
261                        } else {
262                            Ok(e)
263                        }
264                    } else {
265                        Err(common::invalid_data_type_error())
266                    }
267                }
268            );
269        }
270        self.write_property(property, array_index, value, priority)
271    }
272
273    fn write_property(
274        &mut self,
275        property: PropertyIdentifier,
276        array_index: Option<u32>,
277        value: PropertyValue,
278        _priority: Option<u8>,
279    ) -> Result<(), Error> {
280        if matches!(
281            property,
282            PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
283        ) {
284            return Err(common::write_access_denied_error());
285        }
286        if property == PropertyIdentifier::FEEDBACK_VALUE {
287            if let PropertyValue::Enumerated(e) = value {
288                if e > 1 {
289                    return Err(common::value_out_of_range_error());
290                }
291                self.feedback_value = e;
292                return Ok(());
293            }
294            return Err(common::invalid_data_type_error());
295        }
296        if property == PropertyIdentifier::ACTIVE_TEXT {
297            if let PropertyValue::CharacterString(s) = value {
298                self.active_text = s;
299                return Ok(());
300            }
301            return Err(common::invalid_data_type_error());
302        }
303        if property == PropertyIdentifier::INACTIVE_TEXT {
304            if let PropertyValue::CharacterString(s) = value {
305                self.inactive_text = s;
306                return Ok(());
307            }
308            return Err(common::invalid_data_type_error());
309        }
310        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
311            if let PropertyValue::Boolean(v) = value {
312                self.event_detection_enable = v;
313                if !v {
314                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
315                    self.event_detector.acked_transitions =
316                        bacnet_types::bitstring::EventTransitionBits::all();
317                    self.event_detector.pending = None;
318                    self.event_detector.fault_reliability = None;
319                    self.event_history.reset();
320                }
321                return Ok(());
322            }
323            return Err(common::invalid_data_type_error());
324        }
325        if property == PropertyIdentifier::RELINQUISH_DEFAULT {
326            if let PropertyValue::Enumerated(e) = value {
327                return self.set_relinquish_default(e);
328            }
329            return Err(common::invalid_data_type_error());
330        }
331        // Event_Message_Texts_Config and the Event_Algorithm_Inhibit pair (#1329).
332        if let Some(result) =
333            self.event_history
334                .write(property, array_index, &value, self.event_detection_enable)
335        {
336            return result;
337        }
338        if let Some(result) = write_generic_event_properties!(self, property, value) {
339            return result;
340        }
341        if let Some(result) = self.reliability_inhibit.write_inhibit(
342            &mut self.reliability,
343            self.out_of_service,
344            property,
345            &value,
346        ) {
347            return result;
348        }
349        if let Some(result) = self.reliability_inhibit.write_out_of_service(
350            &mut self.out_of_service,
351            &mut self.reliability,
352            &mut self.reliability_before_out_of_service,
353            property,
354            &value,
355        ) {
356            return result.map(|_| ());
357        }
358        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
359            return result;
360        }
361        if let Some(result) = common::write_description(&mut self.description, property, &value) {
362            return result;
363        }
364        // Clause 12.7 requires simulation/test writes while Out_Of_Service is TRUE:
365        // Present_Value is writable, as is Reliability when that property exists
366        // and supports values beyond NO_FAULT_DETECTED.
367        // `is_writable_property` stays statically true because it describes capability.
368        if let Some(result) = self.reliability_inhibit.write_client_reliability(
369            self.out_of_service,
370            &mut self.reliability,
371            property,
372            &value,
373        ) {
374            return result;
375        }
376        if let Some(result) = self.profile.write(property, array_index, &value) {
377            return result;
378        }
379        Err(crate::common::unhandled_write_error(
380            self.property_metadata().as_ref(),
381            property,
382            array_index,
383        ))
384    }
385
386    fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
387        self.profile.capability()
388    }
389
390    fn advance_monotonic_time_internal(&mut self, now: core::time::Duration) -> bool {
391        self.profile.expire(now);
392        false
393    }
394
395    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
396        metadata::for_object(self)
397    }
398
399    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
400        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
401    }
402
403    fn is_createable(&self) -> bool {
404        true
405    }
406
407    fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
408        if self.out_of_service || self.reliability_inhibit.enabled() {
409            return Err(common::write_access_denied_error());
410        }
411        if !common::is_reliability_value_valid(reliability) {
412            return Err(common::value_out_of_range_error());
413        }
414        self.reliability = reliability;
415        Ok(())
416    }
417
418    fn reliability_evaluation_inhibited_internal(&self) -> bool {
419        self.reliability_inhibit.enabled()
420    }
421}
422
423#[cfg(test)]
424#[path = "tests/command_failure.rs"]
425mod command_failure_tests;