Skip to main content

bacnet_objects/binary/
output.rs

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