Skip to main content

bacnet_objects/binary/
input.rs

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