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