Skip to main content

bacnet_objects/binary/
output.rs

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