Skip to main content

bacnet_objects/binary/
value.rs

1use super::*;
2
3// ---------------------------------------------------------------------------
4// BinaryValue (type 5)
5// ---------------------------------------------------------------------------
6
7/// BACnet Binary Value object.
8///
9/// Commandable binary value with 16-level priority array.
10/// Uses Enumerated values: 0 = inactive, 1 = active.
11pub struct BinaryValueObject {
12    oid: ObjectIdentifier,
13    name: String,
14    description: String,
15    present_value: u32, // 0 = inactive, 1 = active
16    out_of_service: bool,
17    status_flags: StatusFlags,
18    priority_array: [Option<u32>; 16],
19    relinquish_default: u32,
20    /// Reliability: 0 = NO_FAULT_DETECTED.
21    reliability: u32,
22    reliability_before_out_of_service: Option<u32>,
23    reliability_inhibit: common::ReliabilityInhibitState,
24    active_text: String,
25    inactive_text: String,
26    /// CHANGE_OF_STATE event detector.
27    event_detector: ChangeOfStateDetector,
28    /// Event_Detection_Enable (Clause 12.8). Clause 13.2.2.1: "If the
29    /// Event_Detection_Enable property is FALSE, then this state machine is not evaluated."
30    event_detection_enable: bool,
31    pub(crate) event_history: EventHistory,
32    /// Value source tracking (optional per spec — exposed via VALUE_SOURCE property).
33    value_source: common::ValueSourceTracking,
34}
35
36impl BinaryValueObject {
37    /// Create a new Binary Value object.
38    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
39        let oid = ObjectIdentifier::new(ObjectType::BINARY_VALUE, instance)?;
40        Ok(Self {
41            oid,
42            name: name.into(),
43            description: String::new(),
44            present_value: 0, // inactive
45            out_of_service: false,
46            status_flags: StatusFlags::empty(),
47            priority_array: [None; 16],
48            relinquish_default: 0,
49            reliability: 0,
50            reliability_before_out_of_service: None,
51            reliability_inhibit: common::ReliabilityInhibitState::default(),
52            active_text: "Active".into(),
53            inactive_text: "Inactive".into(),
54            event_detector: ChangeOfStateDetector {
55                alarm_values: vec![1],
56                ..Default::default()
57            },
58            event_detection_enable: true,
59            event_history: EventHistory::default(),
60            value_source: common::ValueSourceTracking::default(),
61        })
62    }
63
64    /// Set the description string.
65    pub fn set_description(&mut self, desc: impl Into<String>) {
66        self.description = desc.into();
67    }
68
69    fn recalculate_present_value(&mut self) {
70        self.present_value =
71            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
72    }
73
74    /// Set the Relinquish_Default (#270).
75    ///
76    /// Validated the same way a commanded Present_Value is (BinaryPV 0 or 1);
77    /// after the store, Present_Value is resolved anew from the priority
78    /// array so an empty array falls back to the new default immediately.
79    pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
80        if value > 1 {
81            return Err(common::value_out_of_range_error());
82        }
83        self.relinquish_default = value;
84        self.recalculate_present_value();
85        Ok(())
86    }
87}
88
89impl BACnetObject for BinaryValueObject {
90    fn object_identifier(&self) -> ObjectIdentifier {
91        self.oid
92    }
93
94    fn object_name(&self) -> &str {
95        &self.name
96    }
97
98    fn supports_cov(&self) -> bool {
99        true
100    }
101
102    crate::event::impl_builtin_intrinsic_reporting!(
103        event_detector,
104        event_history,
105        [present_value],
106        reliability,
107        event_detection_enable,
108        ChangeOfStateDetector::ALGORITHM
109    );
110    impl_intrinsic_write_rollback!(
111        event_detector,
112        event_detection_enable,
113        event_history,
114        reliability_inhibit,
115        reliability,
116        out_of_service,
117        reliability_before_out_of_service
118    );
119
120    fn acknowledge_alarm_correlated_internal(
121        &mut self,
122        event_state: EventState,
123        timestamp: &BACnetTimeStamp,
124    ) -> Result<(), Error> {
125        if !self.event_detection_enable {
126            return Err(Error::Protocol {
127                class: ErrorClass::OBJECT.to_raw() as u32,
128                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
129            });
130        }
131        self.event_history.acknowledge_correlated(
132            &mut self.event_detector.acked_transitions,
133            event_state,
134            timestamp,
135        )
136    }
137
138    fn read_property(
139        &self,
140        property: PropertyIdentifier,
141        array_index: Option<u32>,
142    ) -> Result<PropertyValue, Error> {
143        if property == PropertyIdentifier::STATUS_FLAGS {
144            return Ok(common::compute_status_flags(
145                self.status_flags,
146                self.reliability,
147                self.out_of_service,
148                self.event_detector.event_state.to_raw(),
149            ));
150        }
151        if let Some(value) = self.reliability_inhibit.read(property) {
152            return Ok(value);
153        }
154        if let Some(result) = read_common_properties!(self, property, array_index) {
155            return result;
156        }
157        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
158            return Ok(PropertyValue::Boolean(self.event_detection_enable));
159        }
160        if let Some(result) = read_generic_event_properties!(self, property) {
161            return result;
162        }
163        if let Some(result) = self.event_history.read(property, array_index) {
164            return result;
165        }
166        match property {
167            p if p == PropertyIdentifier::OBJECT_TYPE => {
168                Ok(PropertyValue::Enumerated(ObjectType::BINARY_VALUE.to_raw()))
169            }
170            p if p == PropertyIdentifier::PRESENT_VALUE => {
171                Ok(PropertyValue::Enumerated(self.present_value))
172            }
173            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
174                common::read_priority_array!(self, array_index, PropertyValue::Enumerated)
175            }
176            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
177                Ok(PropertyValue::Enumerated(self.relinquish_default))
178            }
179            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
180                Ok(common::current_command_priority(&self.priority_array))
181            }
182            p if p == PropertyIdentifier::VALUE_SOURCE => {
183                Ok(self.value_source.value_source.clone())
184            }
185            p if p == PropertyIdentifier::LAST_COMMAND_TIME => Ok(PropertyValue::Unsigned(
186                match self.value_source.last_command_time {
187                    BACnetTimeStamp::SequenceNumber(n) => u64::from(n),
188                    _ => 0,
189                },
190            )),
191            p if p == PropertyIdentifier::ACTIVE_TEXT => {
192                Ok(PropertyValue::CharacterString(self.active_text.clone()))
193            }
194            p if p == PropertyIdentifier::INACTIVE_TEXT => {
195                Ok(PropertyValue::CharacterString(self.inactive_text.clone()))
196            }
197            p if p == PropertyIdentifier::ALARM_VALUE => {
198                // Construction and writes keep exactly one value; ACTIVE is the
199                // defensive construction default if a future reset empties it.
200                Ok(PropertyValue::Enumerated(
201                    self.event_detector
202                        .alarm_values
203                        .first()
204                        .copied()
205                        .unwrap_or(1),
206                ))
207            }
208            _ => Err(common::unknown_property_error()),
209        }
210    }
211
212    fn write_property(
213        &mut self,
214        property: PropertyIdentifier,
215        array_index: Option<u32>,
216        value: PropertyValue,
217        priority: Option<u8>,
218    ) -> Result<(), Error> {
219        common::write_priority_array_direct!(self, property, array_index, value, |v| {
220            if let PropertyValue::Enumerated(e) = v {
221                if e > 1 {
222                    Err(common::value_out_of_range_error())
223                } else {
224                    Ok(e)
225                }
226            } else {
227                Err(common::invalid_data_type_error())
228            }
229        });
230        if property == PropertyIdentifier::PRESENT_VALUE {
231            return common::write_priority_array!(self, value, priority, |v| {
232                if let PropertyValue::Enumerated(e) = v {
233                    if e > 1 {
234                        Err(common::value_out_of_range_error())
235                    } else {
236                        Ok(e)
237                    }
238                } else {
239                    Err(common::invalid_data_type_error())
240                }
241            });
242        }
243        if property == PropertyIdentifier::ACTIVE_TEXT {
244            if let PropertyValue::CharacterString(s) = value {
245                self.active_text = s;
246                return Ok(());
247            }
248            return Err(common::invalid_data_type_error());
249        }
250        if property == PropertyIdentifier::INACTIVE_TEXT {
251            if let PropertyValue::CharacterString(s) = value {
252                self.inactive_text = s;
253                return Ok(());
254            }
255            return Err(common::invalid_data_type_error());
256        }
257        if property == PropertyIdentifier::ALARM_VALUE {
258            if let PropertyValue::Enumerated(v) = value {
259                if v > 1 {
260                    return Err(common::value_out_of_range_error());
261                }
262                self.event_detector.alarm_values = vec![v];
263                return Ok(());
264            }
265            return Err(common::invalid_data_type_error());
266        }
267        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
268            if let PropertyValue::Boolean(v) = value {
269                self.event_detection_enable = v;
270                if !v {
271                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
272                    self.event_detector.acked_transitions = 0b111;
273                    self.event_detector.pending = None;
274                    self.event_detector.fault_reliability = None;
275                    self.event_history.reset();
276                }
277                return Ok(());
278            }
279            return Err(common::invalid_data_type_error());
280        }
281        if property == PropertyIdentifier::RELINQUISH_DEFAULT {
282            if let PropertyValue::Enumerated(e) = value {
283                return self.set_relinquish_default(e);
284            }
285            return Err(common::invalid_data_type_error());
286        }
287        if let Some(result) = write_generic_event_properties!(self, property, value) {
288            return result;
289        }
290        if let Some(result) = self.reliability_inhibit.write_inhibit(
291            &mut self.reliability,
292            self.out_of_service,
293            property,
294            &value,
295        ) {
296            return result;
297        }
298        if let Some(result) = self.reliability_inhibit.write_out_of_service(
299            &mut self.out_of_service,
300            &mut self.reliability,
301            &mut self.reliability_before_out_of_service,
302            property,
303            &value,
304        ) {
305            return result;
306        }
307        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
308            return result;
309        }
310        if let Some(result) = common::write_description(&mut self.description, property, &value) {
311            return result;
312        }
313        // Clause 12.8, while Out_Of_Service is TRUE: "the Present_Value property and
314        // the Reliability property, if present and capable of taking on values other
315        // than NO_FAULT_DETECTED, shall be writable to allow simulating specific
316        // conditions or for testing purposes".
317        // `is_writable_property` stays statically true because it describes capability.
318        if let Some(result) = self.reliability_inhibit.write_client_reliability(
319            self.out_of_service,
320            &mut self.reliability,
321            property,
322            &value,
323        ) {
324            return result;
325        }
326        Err(common::write_access_denied_error())
327    }
328
329    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
330        static PROPS: &[PropertyIdentifier] = &[
331            PropertyIdentifier::OBJECT_IDENTIFIER,
332            PropertyIdentifier::OBJECT_NAME,
333            PropertyIdentifier::DESCRIPTION,
334            PropertyIdentifier::OBJECT_TYPE,
335            PropertyIdentifier::PRESENT_VALUE,
336            PropertyIdentifier::STATUS_FLAGS,
337            PropertyIdentifier::EVENT_STATE,
338            PropertyIdentifier::EVENT_DETECTION_ENABLE,
339            PropertyIdentifier::EVENT_ENABLE,
340            PropertyIdentifier::TIME_DELAY,
341            PropertyIdentifier::TIME_DELAY_NORMAL,
342            PropertyIdentifier::NOTIFY_TYPE,
343            PropertyIdentifier::NOTIFICATION_CLASS,
344            PropertyIdentifier::ACKED_TRANSITIONS,
345            PropertyIdentifier::EVENT_TIME_STAMPS,
346            PropertyIdentifier::EVENT_MESSAGE_TEXTS,
347            PropertyIdentifier::OUT_OF_SERVICE,
348            PropertyIdentifier::PRIORITY_ARRAY,
349            PropertyIdentifier::RELINQUISH_DEFAULT,
350            PropertyIdentifier::CURRENT_COMMAND_PRIORITY,
351            PropertyIdentifier::RELIABILITY,
352            PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT,
353            PropertyIdentifier::ACTIVE_TEXT,
354            PropertyIdentifier::INACTIVE_TEXT,
355            PropertyIdentifier::ALARM_VALUE,
356        ];
357        Cow::Borrowed(PROPS)
358    }
359
360    fn is_createable(&self) -> bool {
361        true
362    }
363
364    fn set_reliability_internal(&mut self, reliability: u32) -> Result<(), Error> {
365        if self.out_of_service || self.reliability_inhibit.enabled() {
366            return Err(common::write_access_denied_error());
367        }
368        if !common::is_reliability_value_valid(reliability) {
369            return Err(common::value_out_of_range_error());
370        }
371        self.reliability = reliability;
372        Ok(())
373    }
374
375    fn reliability_evaluation_inhibited_internal(&self) -> bool {
376        self.reliability_inhibit.enabled()
377    }
378
379    fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
380        // Mirrors the BinaryValue `write_property` arms. Same set as
381        // BinaryOutput (commandable + common + text + generic event
382        // properties). The event set became writable with #229: Clause 12.8
383        // requires the supported Event_Enable value set to include (T, T, T),
384        // and these detectors default to (F, F, F) with, previously, no
385        // commissioning path at all.
386        common::is_commandable_property_writable(property)
387            || common::is_common_writable(property)
388            || common::is_generic_event_property_writable(property)
389            || property == PropertyIdentifier::ACTIVE_TEXT
390            || property == PropertyIdentifier::INACTIVE_TEXT
391            || property == PropertyIdentifier::ALARM_VALUE
392            || property == PropertyIdentifier::RELIABILITY
393            || property == PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT
394            || property == PropertyIdentifier::EVENT_DETECTION_ENABLE
395    }
396}
397
398#[cfg(test)]
399#[path = "tests/value.rs"]
400mod value_tests;