Skip to main content

bacnet_objects/binary/
input.rs

1use super::*;
2use crate::property_metadata::{
3    PropertyConformance, PropertyMetadata, PropertyPresenceCondition, PropertyWriteCapability,
4};
5
6const BINARY_INPUT_PROPERTY_METADATA: &[PropertyMetadata] = &[
7    PropertyMetadata::new(
8        PropertyIdentifier::OBJECT_IDENTIFIER,
9        PropertyConformance::RequiredRead,
10        None,
11        PropertyWriteCapability::ReadOnly,
12    ),
13    PropertyMetadata::new(
14        PropertyIdentifier::OBJECT_NAME,
15        PropertyConformance::RequiredRead,
16        None,
17        PropertyWriteCapability::Always,
18    ),
19    PropertyMetadata::new(
20        PropertyIdentifier::DESCRIPTION,
21        PropertyConformance::Optional,
22        None,
23        PropertyWriteCapability::Always,
24    ),
25    PropertyMetadata::new(
26        PropertyIdentifier::OBJECT_TYPE,
27        PropertyConformance::RequiredRead,
28        None,
29        PropertyWriteCapability::ReadOnly,
30    ),
31    PropertyMetadata::new(
32        PropertyIdentifier::PRESENT_VALUE,
33        PropertyConformance::RequiredRead,
34        None,
35        PropertyWriteCapability::WhenOutOfService,
36    ),
37    PropertyMetadata::new(
38        PropertyIdentifier::STATUS_FLAGS,
39        PropertyConformance::RequiredRead,
40        None,
41        PropertyWriteCapability::ReadOnly,
42    ),
43    PropertyMetadata::new(
44        PropertyIdentifier::EVENT_STATE,
45        PropertyConformance::RequiredRead,
46        None,
47        PropertyWriteCapability::ReadOnly,
48    ),
49    PropertyMetadata::new(
50        PropertyIdentifier::EVENT_DETECTION_ENABLE,
51        PropertyConformance::Optional,
52        Some(PropertyPresenceCondition::IntrinsicReporting),
53        PropertyWriteCapability::Always,
54    ),
55    PropertyMetadata::new(
56        PropertyIdentifier::EVENT_ENABLE,
57        PropertyConformance::Optional,
58        Some(PropertyPresenceCondition::IntrinsicReporting),
59        PropertyWriteCapability::Always,
60    ),
61    PropertyMetadata::new(
62        PropertyIdentifier::TIME_DELAY,
63        PropertyConformance::Optional,
64        Some(PropertyPresenceCondition::IntrinsicReporting),
65        PropertyWriteCapability::Always,
66    ),
67    PropertyMetadata::new(
68        PropertyIdentifier::TIME_DELAY_NORMAL,
69        PropertyConformance::Optional,
70        Some(PropertyPresenceCondition::IntrinsicReporting),
71        PropertyWriteCapability::Always,
72    ),
73    PropertyMetadata::new(
74        PropertyIdentifier::NOTIFY_TYPE,
75        PropertyConformance::Optional,
76        Some(PropertyPresenceCondition::IntrinsicReporting),
77        PropertyWriteCapability::Always,
78    ),
79    PropertyMetadata::new(
80        PropertyIdentifier::NOTIFICATION_CLASS,
81        PropertyConformance::Optional,
82        Some(PropertyPresenceCondition::IntrinsicReporting),
83        PropertyWriteCapability::Always,
84    ),
85    PropertyMetadata::new(
86        PropertyIdentifier::ACKED_TRANSITIONS,
87        PropertyConformance::Optional,
88        Some(PropertyPresenceCondition::IntrinsicReporting),
89        PropertyWriteCapability::ReadOnly,
90    ),
91    PropertyMetadata::new(
92        PropertyIdentifier::EVENT_TIME_STAMPS,
93        PropertyConformance::Optional,
94        Some(PropertyPresenceCondition::IntrinsicReporting),
95        PropertyWriteCapability::ReadOnly,
96    ),
97    PropertyMetadata::new(
98        PropertyIdentifier::EVENT_MESSAGE_TEXTS,
99        PropertyConformance::Optional,
100        Some(PropertyPresenceCondition::IntrinsicReporting),
101        PropertyWriteCapability::ReadOnly,
102    ),
103    PropertyMetadata::new(
104        PropertyIdentifier::OUT_OF_SERVICE,
105        PropertyConformance::RequiredRead,
106        None,
107        PropertyWriteCapability::Always,
108    ),
109    PropertyMetadata::new(
110        PropertyIdentifier::POLARITY,
111        PropertyConformance::RequiredRead,
112        None,
113        PropertyWriteCapability::ReadOnly,
114    ),
115    PropertyMetadata::new(
116        PropertyIdentifier::RELIABILITY,
117        PropertyConformance::Optional,
118        None,
119        PropertyWriteCapability::WhenOutOfService,
120    ),
121    PropertyMetadata::new(
122        PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT,
123        PropertyConformance::Optional,
124        None,
125        PropertyWriteCapability::Always,
126    ),
127    PropertyMetadata::new(
128        PropertyIdentifier::ACTIVE_TEXT,
129        PropertyConformance::Optional,
130        Some(PropertyPresenceCondition::PairedText),
131        PropertyWriteCapability::Always,
132    ),
133    PropertyMetadata::new(
134        PropertyIdentifier::INACTIVE_TEXT,
135        PropertyConformance::Optional,
136        Some(PropertyPresenceCondition::PairedText),
137        PropertyWriteCapability::Always,
138    ),
139    PropertyMetadata::new(
140        PropertyIdentifier::ALARM_VALUE,
141        PropertyConformance::Optional,
142        Some(PropertyPresenceCondition::IntrinsicReporting),
143        PropertyWriteCapability::Always,
144    ),
145    PropertyMetadata::new(
146        PropertyIdentifier::PROPERTY_LIST,
147        PropertyConformance::RequiredRead,
148        None,
149        PropertyWriteCapability::ReadOnly,
150    ),
151];
152
153// ---------------------------------------------------------------------------
154// BinaryInput (type 3)
155// ---------------------------------------------------------------------------
156
157/// BACnet Binary Input object.
158///
159/// Read-only binary point. Present_Value is writable only when out-of-service.
160/// Uses Enumerated values: 0 = inactive, 1 = active.
161pub struct BinaryInputObject {
162    oid: ObjectIdentifier,
163    name: String,
164    description: String,
165    present_value: u32,
166    out_of_service: bool,
167    status_flags: StatusFlags,
168    /// Polarity: 0 = normal, 1 = reverse.
169    polarity: u32,
170    /// Reliability: 0 = NO_FAULT_DETECTED.
171    reliability: u32,
172    reliability_before_out_of_service: Option<u32>,
173    reliability_inhibit: common::ReliabilityInhibitState,
174    active_text: String,
175    inactive_text: String,
176    /// CHANGE_OF_STATE event detector.
177    event_detector: ChangeOfStateDetector,
178    /// Event_Detection_Enable (Clause 12.6). Clause 13.2.2.1: "If the
179    /// Event_Detection_Enable property is FALSE, then this state machine is not evaluated."
180    event_detection_enable: bool,
181    pub(crate) event_history: EventHistory,
182}
183
184impl BinaryInputObject {
185    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
186        let oid = ObjectIdentifier::new(ObjectType::BINARY_INPUT, instance)?;
187        Ok(Self {
188            oid,
189            name: name.into(),
190            description: String::new(),
191            present_value: 0,
192            out_of_service: false,
193            status_flags: StatusFlags::empty(),
194            polarity: 0,
195            reliability: 0,
196            reliability_before_out_of_service: None,
197            reliability_inhibit: common::ReliabilityInhibitState::default(),
198            active_text: "Active".into(),
199            inactive_text: "Inactive".into(),
200            event_detector: ChangeOfStateDetector {
201                alarm_values: vec![1],
202                ..Default::default()
203            },
204            event_detection_enable: true,
205            event_history: EventHistory::default(),
206        })
207    }
208
209    /// Mutate logical `Present_Value` on an unattached or application-owned object.
210    ///
211    /// The value is BACnet INACTIVE (`0`) or ACTIVE (`1`) **after Polarity**,
212    /// not a raw electrical or physical state. This low-level helper bypasses
213    /// running-server `Out_Of_Service` ownership, validation, intrinsic-event
214    /// processing, and COV processing. Applications updating a live object
215    /// should use `BACnetServer::set_present_value_local`.
216    pub fn set_present_value(&mut self, value: u32) {
217        self.present_value = value;
218    }
219
220    /// Validate and store a `Present_Value` write, without any access check.
221    ///
222    /// Shared by the network and internal routes, which differ only in the
223    /// `Out_Of_Service` condition each requires.
224    fn apply_present_value(&mut self, value: PropertyValue) -> Result<(), Error> {
225        let PropertyValue::Enumerated(v) = value else {
226            return Err(common::invalid_data_type_error());
227        };
228        if v > 1 {
229            return Err(common::value_out_of_range_error());
230        }
231        self.present_value = v;
232        Ok(())
233    }
234
235    /// Set the description string.
236    pub fn set_description(&mut self, desc: impl Into<String>) {
237        self.description = desc.into();
238    }
239}
240
241impl BACnetObject for BinaryInputObject {
242    fn object_identifier(&self) -> ObjectIdentifier {
243        self.oid
244    }
245
246    fn object_name(&self) -> &str {
247        &self.name
248    }
249
250    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
251        Cow::Borrowed(BINARY_INPUT_PROPERTY_METADATA)
252    }
253
254    fn supports_cov(&self) -> bool {
255        true
256    }
257
258    crate::event::impl_builtin_intrinsic_reporting!(
259        event_detector,
260        event_history,
261        [present_value],
262        reliability,
263        event_detection_enable,
264        ChangeOfStateDetector::ALGORITHM
265    );
266    impl_intrinsic_write_rollback!(
267        event_detector,
268        event_detection_enable,
269        event_history,
270        reliability_inhibit,
271        reliability,
272        out_of_service,
273        reliability_before_out_of_service
274    );
275
276    fn acknowledge_alarm_correlated_internal(
277        &mut self,
278        event_state: EventState,
279        timestamp: &BACnetTimeStamp,
280    ) -> Result<(), Error> {
281        if !self.event_detection_enable {
282            return Err(Error::Protocol {
283                class: ErrorClass::OBJECT.to_raw() as u32,
284                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
285            });
286        }
287        self.event_history.acknowledge_correlated(
288            &mut self.event_detector.acked_transitions,
289            event_state,
290            timestamp,
291        )
292    }
293
294    fn read_property(
295        &self,
296        property: PropertyIdentifier,
297        array_index: Option<u32>,
298    ) -> Result<PropertyValue, Error> {
299        if property == PropertyIdentifier::STATUS_FLAGS {
300            return Ok(common::compute_status_flags(
301                self.status_flags,
302                self.reliability,
303                self.out_of_service,
304                self.event_detector.event_state.to_raw(),
305            ));
306        }
307        if let Some(value) = self.reliability_inhibit.read(property) {
308            return Ok(value);
309        }
310        if let Some(result) = read_common_properties!(self, property, array_index) {
311            return result;
312        }
313        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
314            return Ok(PropertyValue::Boolean(self.event_detection_enable));
315        }
316        if let Some(result) = read_generic_event_properties!(self, property) {
317            return result;
318        }
319        if let Some(result) = self.event_history.read(property, array_index) {
320            return result;
321        }
322        match property {
323            p if p == PropertyIdentifier::OBJECT_TYPE => {
324                Ok(PropertyValue::Enumerated(ObjectType::BINARY_INPUT.to_raw()))
325            }
326            p if p == PropertyIdentifier::PRESENT_VALUE => {
327                Ok(PropertyValue::Enumerated(self.present_value))
328            }
329            p if p == PropertyIdentifier::POLARITY => Ok(PropertyValue::Enumerated(self.polarity)),
330            p if p == PropertyIdentifier::ACTIVE_TEXT => {
331                Ok(PropertyValue::CharacterString(self.active_text.clone()))
332            }
333            p if p == PropertyIdentifier::INACTIVE_TEXT => {
334                Ok(PropertyValue::CharacterString(self.inactive_text.clone()))
335            }
336            p if p == PropertyIdentifier::ALARM_VALUE => {
337                // Construction and writes keep exactly one value; ACTIVE is the
338                // defensive construction default if a future reset empties it.
339                Ok(PropertyValue::Enumerated(
340                    self.event_detector
341                        .alarm_values
342                        .first()
343                        .copied()
344                        .unwrap_or(1),
345                ))
346            }
347            _ => Err(common::unknown_property_error()),
348        }
349    }
350
351    fn write_property(
352        &mut self,
353        property: PropertyIdentifier,
354        _array_index: Option<u32>,
355        value: PropertyValue,
356        _priority: Option<u8>,
357    ) -> Result<(), Error> {
358        if property == PropertyIdentifier::PRESENT_VALUE {
359            if !self.out_of_service {
360                return Err(common::write_access_denied_error());
361            }
362            return self.apply_present_value(value);
363        }
364        if property == PropertyIdentifier::ACTIVE_TEXT {
365            if let PropertyValue::CharacterString(s) = value {
366                self.active_text = s;
367                return Ok(());
368            }
369            return Err(common::invalid_data_type_error());
370        }
371        if property == PropertyIdentifier::INACTIVE_TEXT {
372            if let PropertyValue::CharacterString(s) = value {
373                self.inactive_text = s;
374                return Ok(());
375            }
376            return Err(common::invalid_data_type_error());
377        }
378        if property == PropertyIdentifier::ALARM_VALUE {
379            if let PropertyValue::Enumerated(v) = value {
380                if v > 1 {
381                    return Err(common::value_out_of_range_error());
382                }
383                self.event_detector.alarm_values = vec![v];
384                return Ok(());
385            }
386            return Err(common::invalid_data_type_error());
387        }
388        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
389            if let PropertyValue::Boolean(v) = value {
390                self.event_detection_enable = v;
391                if !v {
392                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
393                    self.event_detector.acked_transitions = 0b111;
394                    self.event_detector.pending = None;
395                    self.event_detector.fault_reliability = None;
396                    self.event_history.reset();
397                }
398                return Ok(());
399            }
400            return Err(common::invalid_data_type_error());
401        }
402        if let Some(result) = write_generic_event_properties!(self, property, value) {
403            return result;
404        }
405        if let Some(result) = self.reliability_inhibit.write_inhibit(
406            &mut self.reliability,
407            self.out_of_service,
408            property,
409            &value,
410        ) {
411            return result;
412        }
413        if let Some(result) = self.reliability_inhibit.write_out_of_service(
414            &mut self.out_of_service,
415            &mut self.reliability,
416            &mut self.reliability_before_out_of_service,
417            property,
418            &value,
419        ) {
420            return result;
421        }
422        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
423            return result;
424        }
425        if let Some(result) = common::write_description(&mut self.description, property, &value) {
426            return result;
427        }
428        if let Some(result) = self.reliability_inhibit.write_client_reliability(
429            self.out_of_service,
430            &mut self.reliability,
431            property,
432            &value,
433        ) {
434            return result;
435        }
436        Err(common::write_access_denied_error())
437    }
438
439    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
440        let metadata = self.property_metadata();
441        crate::property_metadata::property_list_from_metadata(metadata.as_ref())
442    }
443
444    fn is_createable(&self) -> bool {
445        true
446    }
447
448    fn set_reliability_internal(&mut self, reliability: u32) -> Result<(), Error> {
449        if self.out_of_service || self.reliability_inhibit.enabled() {
450            return Err(common::write_access_denied_error());
451        }
452        if !common::is_reliability_value_valid(reliability) {
453            return Err(common::value_out_of_range_error());
454        }
455        self.reliability = reliability;
456        Ok(())
457    }
458
459    fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
460        // Local safe-ownership policy: preserve the client's OOS simulation.
461        if self.out_of_service {
462            return Err(common::write_access_denied_error());
463        }
464        self.apply_present_value(value)
465    }
466
467    fn reliability_evaluation_inhibited_internal(&self) -> bool {
468        self.reliability_inhibit.enabled()
469    }
470}
471
472#[cfg(test)]
473#[path = "tests/input_output.rs"]
474mod input_output_tests;