Skip to main content

bacnet_objects/binary/
input.rs

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