Skip to main content

bacnet_objects/binary/
value.rs

1use super::*;
2use crate::present_value_access::PresentValueAccess;
3use crate::property_metadata::PropertyMetadata;
4
5#[path = "value/metadata.rs"]
6mod metadata;
7
8// ---------------------------------------------------------------------------
9// BinaryValue (type 5)
10// ---------------------------------------------------------------------------
11
12/// BACnet Binary Value object.
13///
14/// Uses Enumerated values: 0 = inactive, 1 = active.
15pub struct BinaryValueObject {
16    audit_policy: crate::audit::ObjectAuditPolicy,
17    oid: ObjectIdentifier,
18    name: String,
19    description: String,
20    present_value: u32, // 0 = inactive, 1 = active
21    out_of_service: bool,
22    status_flags: StatusFlags,
23    access: PresentValueAccess,
24    priority_array: [Option<u32>; 16],
25    relinquish_default: u32,
26    /// Reliability; NO_FAULT_DETECTED until a fault is evaluated or simulated.
27    reliability: Reliability,
28    reliability_before_out_of_service: Option<Reliability>,
29    reliability_inhibit: common::ReliabilityInhibitState,
30    active_text: String,
31    inactive_text: String,
32    /// CHANGE_OF_STATE event detector.
33    event_detector: ChangeOfStateDetector,
34    /// Event_Detection_Enable (Clause 12.8). A FALSE value suspends
35    /// event-state-machine evaluation under Clause 13.2.2.1.
36    event_detection_enable: bool,
37    pub(crate) event_history: EventHistory,
38    /// Implemented paired command-source tracking (Clause 19.5).
39    value_source: crate::command_source::ValueSourceTracking,
40}
41
42impl BinaryValueObject {
43    /// Provision independently optional Audit properties before registration.
44    /// Raw object configuration bypasses server notification ownership; use
45    /// BACnetServer::write_local or WP/WPM for live property mutations.
46    pub fn set_audit_policy(&mut self, policy: crate::audit::ObjectAuditPolicy) {
47        self.audit_policy = policy;
48    }
49
50    /// Create a new Binary Value object with a commandable Present_Value.
51    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
52        Self::with_access(instance, name, PresentValueAccess::Commandable)
53    }
54
55    /// Create a new Binary Value object whose Present_Value is written as `access` says.
56    pub fn with_access(
57        instance: u32,
58        name: impl Into<String>,
59        access: PresentValueAccess,
60    ) -> Result<Self, Error> {
61        let oid = ObjectIdentifier::new(ObjectType::BINARY_VALUE, instance)?;
62        Ok(Self {
63            audit_policy: crate::audit::ObjectAuditPolicy::default(),
64            oid,
65            name: name.into(),
66            description: String::new(),
67            present_value: 0, // inactive
68            out_of_service: false,
69            status_flags: StatusFlags::empty(),
70            access,
71            priority_array: [None; 16],
72            relinquish_default: 0,
73            reliability: Reliability::NO_FAULT_DETECTED,
74            reliability_before_out_of_service: None,
75            reliability_inhibit: common::ReliabilityInhibitState::default(),
76            active_text: "Active".into(),
77            inactive_text: "Inactive".into(),
78            event_detector: ChangeOfStateDetector {
79                alarm_values: vec![1],
80                ..Default::default()
81            },
82            event_detection_enable: true,
83            event_history: EventHistory::default(),
84            value_source: crate::command_source::ValueSourceTracking::default(),
85        })
86    }
87
88    /// Set the description string.
89    pub fn set_description(&mut self, desc: impl Into<String>) {
90        self.description = desc.into();
91    }
92
93    fn recalculate_present_value(&mut self) {
94        self.present_value =
95            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
96    }
97
98    /// Set the Relinquish_Default (#270).
99    ///
100    /// Validated the same way a commanded Present_Value is (BinaryPV 0 or 1);
101    /// after the store, Present_Value is resolved anew from the priority
102    /// array so an empty array falls back to the new default immediately.
103    pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
104        if self.access != PresentValueAccess::Commandable {
105            return Err(common::unknown_property_error());
106        }
107        if value > 1 {
108            return Err(common::value_out_of_range_error());
109        }
110        self.relinquish_default = value;
111        self.recalculate_present_value();
112        Ok(())
113    }
114
115    fn checked_present_value(value: PropertyValue) -> Result<u32, Error> {
116        let PropertyValue::Enumerated(e) = value else {
117            return Err(common::invalid_data_type_error());
118        };
119        if e > 1 {
120            return Err(common::value_out_of_range_error());
121        }
122        Ok(e)
123    }
124}
125
126impl BACnetObject for BinaryValueObject {
127    fn audit_policy_authority_internal(
128        &mut self,
129    ) -> Option<crate::audit::AuditPolicyAuthority<'_>> {
130        Some(crate::audit::AuditPolicyAuthority::new(
131            &mut self.audit_policy,
132        ))
133    }
134
135    fn audit_object_policy_internal(&self) -> crate::audit::ObjectAuditPolicy {
136        self.audit_policy
137    }
138
139    fn object_identifier(&self) -> ObjectIdentifier {
140        self.oid
141    }
142
143    fn object_name(&self) -> &str {
144        &self.name
145    }
146
147    fn supports_cov(&self) -> bool {
148        true
149    }
150
151    crate::event::impl_builtin_intrinsic_reporting!(
152        event_detector,
153        event_history,
154        [present_value],
155        reliability,
156        event_detection_enable,
157        ChangeOfStateDetector::ALGORITHM
158    );
159
160    fn acknowledge_alarm_correlated_internal(
161        &mut self,
162        event_state: EventState,
163        timestamp: &BACnetTimeStamp,
164    ) -> Result<(), Error> {
165        if !self.event_detection_enable {
166            return Err(Error::Protocol {
167                class: ErrorClass::OBJECT.to_raw() as u32,
168                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
169            });
170        }
171        self.event_history.acknowledge_correlated(
172            &mut self.event_detector.acked_transitions,
173            event_state,
174            timestamp,
175        )
176    }
177
178    fn read_property(
179        &self,
180        property: PropertyIdentifier,
181        array_index: Option<u32>,
182    ) -> Result<PropertyValue, Error> {
183        if metadata::excludes(self, property) {
184            return Err(common::unknown_property_error());
185        }
186        if let Some(result) = self
187            .value_source
188            .read(property, array_index, &self.priority_array)
189        {
190            return result;
191        }
192
193        if let Some(result) = self.audit_policy.read(property, array_index) {
194            return result;
195        }
196        if property == PropertyIdentifier::STATUS_FLAGS {
197            return Ok(common::compute_status_flags(
198                self.status_flags,
199                self.reliability,
200                self.out_of_service,
201                self.event_detector.event_state,
202            ));
203        }
204        if let Some(value) = self.reliability_inhibit.read(property) {
205            return Ok(value);
206        }
207        if let Some(result) = read_common_properties!(self, property, array_index) {
208            return result;
209        }
210        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
211            return Ok(PropertyValue::Boolean(self.event_detection_enable));
212        }
213        if let Some(result) = read_generic_event_properties!(self, property) {
214            return result;
215        }
216        if let Some(result) = self.event_history.read(property, array_index) {
217            return result;
218        }
219        match property {
220            p if p == PropertyIdentifier::OBJECT_TYPE => {
221                Ok(PropertyValue::Enumerated(ObjectType::BINARY_VALUE.to_raw()))
222            }
223            p if p == PropertyIdentifier::PRESENT_VALUE => {
224                Ok(PropertyValue::Enumerated(self.present_value))
225            }
226            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
227                common::read_priority_array!(self, array_index, PropertyValue::Enumerated)
228            }
229            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
230                Ok(PropertyValue::Enumerated(self.relinquish_default))
231            }
232            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
233                Ok(common::current_command_priority(&self.priority_array))
234            }
235            p if p == PropertyIdentifier::ACTIVE_TEXT => {
236                Ok(PropertyValue::CharacterString(self.active_text.clone()))
237            }
238            p if p == PropertyIdentifier::INACTIVE_TEXT => {
239                Ok(PropertyValue::CharacterString(self.inactive_text.clone()))
240            }
241            p if p == PropertyIdentifier::ALARM_VALUE => {
242                // Construction and writes keep exactly one value; ACTIVE is the
243                // defensive construction default if a future reset empties it.
244                Ok(PropertyValue::Enumerated(
245                    self.event_detector
246                        .alarm_values
247                        .first()
248                        .copied()
249                        .unwrap_or(1),
250                ))
251            }
252            _ => Err(common::unknown_property_error()),
253        }
254    }
255
256    fn write_property_from(
257        &mut self,
258        property: PropertyIdentifier,
259        array_index: Option<u32>,
260        value: PropertyValue,
261        priority: Option<u8>,
262        origin: &crate::command_source::CommandOrigin,
263    ) -> Result<(), Error> {
264        if matches!(
265            property,
266            PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
267        ) && array_index.is_some()
268        {
269            return Err(common::property_is_not_an_array_error());
270        }
271        if property == PropertyIdentifier::VALUE_SOURCE
272            && self.access == PresentValueAccess::Commandable
273        {
274            return self.value_source.correct(value, priority, origin);
275        }
276        if property == PropertyIdentifier::PRESENT_VALUE {
277            return match self.access {
278                PresentValueAccess::Commandable => {
279                    crate::command_source::write_sourced_priority!(
280                        self,
281                        value,
282                        priority,
283                        origin,
284                        Self::checked_present_value
285                    )
286                }
287                PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
288                    self.write_property(property, array_index, value, priority)
289                }
290            };
291        }
292        self.write_property(property, array_index, value, priority)
293    }
294
295    fn write_property(
296        &mut self,
297        property: PropertyIdentifier,
298        array_index: Option<u32>,
299        value: PropertyValue,
300        priority: Option<u8>,
301    ) -> Result<(), Error> {
302        if metadata::excludes(self, property) {
303            return Err(common::unknown_property_error());
304        }
305        if let Some(result) = self
306            .audit_policy
307            .write(property, array_index, &value, priority)
308        {
309            return result;
310        }
311
312        if property == PropertyIdentifier::PRESENT_VALUE {
313            if self.access == PresentValueAccess::Commandable {
314                return Err(common::write_access_denied_error());
315            }
316            if array_index.is_some() {
317                return Err(common::property_is_not_an_array_error());
318            }
319            if self.access == PresentValueAccess::ReadOnly && !self.out_of_service {
320                return Err(common::write_access_denied_error());
321            }
322            // Clause 19.2: an otherwise permitted noncommandable NULL is a no-op.
323            if value == PropertyValue::Null {
324                return Ok(());
325            }
326            self.present_value = Self::checked_present_value(value)?;
327            return Ok(());
328        }
329        if property == PropertyIdentifier::VALUE_SOURCE {
330            return Err(common::write_access_denied_error());
331        }
332        if property == PropertyIdentifier::ACTIVE_TEXT {
333            if let PropertyValue::CharacterString(s) = value {
334                self.active_text = s;
335                return Ok(());
336            }
337            return Err(common::invalid_data_type_error());
338        }
339        if property == PropertyIdentifier::INACTIVE_TEXT {
340            if let PropertyValue::CharacterString(s) = value {
341                self.inactive_text = s;
342                return Ok(());
343            }
344            return Err(common::invalid_data_type_error());
345        }
346        if property == PropertyIdentifier::ALARM_VALUE {
347            if let PropertyValue::Enumerated(v) = value {
348                if v > 1 {
349                    return Err(common::value_out_of_range_error());
350                }
351                self.event_detector.alarm_values = vec![v];
352                return Ok(());
353            }
354            return Err(common::invalid_data_type_error());
355        }
356        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
357            if let PropertyValue::Boolean(v) = value {
358                self.event_detection_enable = v;
359                if !v {
360                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
361                    self.event_detector.acked_transitions =
362                        bacnet_types::bitstring::EventTransitionBits::all();
363                    self.event_detector.pending = None;
364                    self.event_detector.fault_reliability = None;
365                    self.event_history.reset();
366                }
367                return Ok(());
368            }
369            return Err(common::invalid_data_type_error());
370        }
371        if property == PropertyIdentifier::RELINQUISH_DEFAULT {
372            if let PropertyValue::Enumerated(e) = value {
373                return self.set_relinquish_default(e);
374            }
375            return Err(common::invalid_data_type_error());
376        }
377        // Event_Message_Texts_Config and the Event_Algorithm_Inhibit pair (#1329).
378        if let Some(result) =
379            self.event_history
380                .write(property, array_index, &value, self.event_detection_enable)
381        {
382            return result;
383        }
384        if let Some(result) = write_generic_event_properties!(self, property, value) {
385            return result;
386        }
387        if let Some(result) = self.reliability_inhibit.write_inhibit(
388            &mut self.reliability,
389            self.out_of_service,
390            property,
391            &value,
392        ) {
393            return result;
394        }
395        if let Some(result) = self.reliability_inhibit.write_out_of_service(
396            &mut self.out_of_service,
397            &mut self.reliability,
398            &mut self.reliability_before_out_of_service,
399            property,
400            &value,
401        ) {
402            return result.map(|_| ());
403        }
404        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
405            return result;
406        }
407        if let Some(result) = common::write_description(&mut self.description, property, &value) {
408            return result;
409        }
410        // Clause 12.8 requires simulation/test writes while Out_Of_Service is TRUE:
411        // Present_Value is writable, as is Reliability when that property exists
412        // and supports values beyond NO_FAULT_DETECTED.
413        // `is_writable_property` stays statically true because it describes capability.
414        if let Some(result) = self.reliability_inhibit.write_client_reliability(
415            self.out_of_service,
416            &mut self.reliability,
417            property,
418            &value,
419        ) {
420            return result;
421        }
422        Err(crate::common::unhandled_write_error(
423            self.property_metadata().as_ref(),
424            property,
425            array_index,
426        ))
427    }
428
429    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
430        metadata::for_object(self)
431    }
432
433    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
434        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
435    }
436
437    fn is_createable(&self) -> bool {
438        true
439    }
440
441    fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
442        if self.out_of_service || self.reliability_inhibit.enabled() {
443            return Err(common::write_access_denied_error());
444        }
445        if !common::is_reliability_value_valid(reliability) {
446            return Err(common::value_out_of_range_error());
447        }
448        self.reliability = reliability;
449        Ok(())
450    }
451
452    fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
453        match self.access {
454            PresentValueAccess::Commandable => {
455                Err(common::optional_functionality_not_supported_error())
456            }
457            _ if self.out_of_service => Err(common::write_access_denied_error()),
458            PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
459                self.present_value = Self::checked_present_value(value)?;
460                Ok(())
461            }
462        }
463    }
464
465    fn reliability_evaluation_inhibited_internal(&self) -> bool {
466        self.reliability_inhibit.enabled()
467    }
468}
469
470#[cfg(test)]
471#[path = "tests/value.rs"]
472mod value_tests;