Skip to main content

bacnet_objects/binary/
value.rs

1use super::*;
2use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
3use crate::prelude::*;
4use crate::present_value_access::PresentValueAccess;
5use crate::property_metadata::PropertyMetadata;
6use alloc::sync::Arc;
7
8#[path = "value/metadata.rs"]
9mod metadata;
10
11// ---------------------------------------------------------------------------
12// BinaryValue (type 5)
13// ---------------------------------------------------------------------------
14
15/// BACnet Binary Value object.
16///
17/// Uses Enumerated values: 0 = inactive, 1 = active.
18pub struct BinaryValueObject {
19    profile: ProfileState,
20    audit_policy: crate::audit::ObjectAuditPolicy,
21    oid: ObjectIdentifier,
22    name: String,
23    description: String,
24    present_value: u32, // 0 = inactive, 1 = active
25    out_of_service: bool,
26    status_flags: StatusFlags,
27    access: PresentValueAccess,
28    priority_array: [Option<u32>; 16],
29    relinquish_default: u32,
30    /// Reliability; NO_FAULT_DETECTED until a fault is evaluated or simulated.
31    reliability: Reliability,
32    reliability_before_out_of_service: Option<Reliability>,
33    reliability_inhibit: common::ReliabilityInhibitState,
34    active_text: String,
35    inactive_text: String,
36    /// CHANGE_OF_STATE event detector.
37    event_detector: ChangeOfStateDetector,
38    /// Event_Detection_Enable (Clause 12.8). A FALSE value suspends
39    /// event-state-machine evaluation under Clause 13.2.2.1.
40    event_detection_enable: bool,
41    pub(crate) event_history: EventHistory,
42    /// Implemented paired command-source tracking (Clause 19.5).
43    value_source: crate::command_source::ValueSourceTracking,
44    /// The last writer of a noncommandable Present_Value, once tracked (#1552).
45    write_source: crate::command_source::SingleValueSource,
46}
47
48impl BinaryValueObject {
49    /// Provision independently optional Audit properties before registration.
50    /// Raw object configuration bypasses server notification ownership; use
51    /// BACnetServer::write_local or WP/WPM for live property mutations.
52    pub fn set_audit_policy(&mut self, policy: crate::audit::ObjectAuditPolicy) {
53        self.audit_policy = policy;
54    }
55
56    /// Create a new Binary Value object with a commandable Present_Value.
57    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
58        Self::with_access(instance, name, PresentValueAccess::Commandable)
59    }
60
61    /// Create a new Binary Value object whose Present_Value is written as `access` says.
62    pub fn with_access(
63        instance: u32,
64        name: impl Into<String>,
65        access: PresentValueAccess,
66    ) -> Result<Self, Error> {
67        let oid = ObjectIdentifier::new(ObjectType::BINARY_VALUE, instance)?;
68        Ok(Self {
69            profile: ProfileState::default(),
70            audit_policy: crate::audit::ObjectAuditPolicy::default(),
71            oid,
72            name: name.into(),
73            description: String::new(),
74            present_value: 0, // inactive
75            out_of_service: false,
76            status_flags: StatusFlags::empty(),
77            access,
78            priority_array: [None; 16],
79            relinquish_default: 0,
80            reliability: Reliability::NO_FAULT_DETECTED,
81            reliability_before_out_of_service: None,
82            reliability_inhibit: common::ReliabilityInhibitState::default(),
83            active_text: "Active".into(),
84            inactive_text: "Inactive".into(),
85            event_detector: ChangeOfStateDetector {
86                alarm_values: vec![1],
87                ..Default::default()
88            },
89            event_detection_enable: true,
90            event_history: EventHistory::default(),
91            value_source: crate::command_source::ValueSourceTracking::default(),
92            write_source: crate::command_source::SingleValueSource::default(),
93        })
94    }
95
96    /// Build with application-owned Tags storage and the requested Present_Value access.
97    /// Saved Tags override configured Tags only while [`Self::set_profile`]
98    /// provisions the row. Loaded data must satisfy the shared Tags rules and
99    /// the opt-in 1 MiB snapshot limit.
100    pub fn with_tags_persistence(
101        instance: u32,
102        name: impl Into<String>,
103        access: PresentValueAccess,
104        persistence: Arc<dyn TagsPersistence>,
105    ) -> Result<Self, Error> {
106        let mut object = Self::with_access(instance, name, access)?;
107        object.profile = ProfileState::persistent(object.oid, persistence)?;
108        Ok(object)
109    }
110
111    /// Provision independent optional Tags, Profile_Location and Profile_Name
112    /// rows before registration, in every Present_Value access mode. Tags is
113    /// writable; the text rows are network read-only. Invalid configuration
114    /// leaves the previous profile and its attached storage unchanged.
115    pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
116        self.profile.provision(profile)
117    }
118
119    /// Wait for queued save attempts to finish; writes report their own outcomes.
120    /// This is not a success receipt. Unstaged synchronous writes block their caller.
121    pub fn wait_for_tag_saves(&self) {
122        self.profile.wait_for_saves();
123    }
124
125    /// Track the source of a noncommandable Present_Value (Clause 19.5,
126    /// #1552), as [`AnalogValueObject::set_value_source_tracking`] does.
127    ///
128    /// [`AnalogValueObject::set_value_source_tracking`]: crate::analog::AnalogValueObject::set_value_source_tracking
129    pub fn set_value_source_tracking(&mut self, enabled: bool) {
130        self.write_source.set_enabled(enabled);
131    }
132
133    /// Set the description string.
134    pub fn set_description(&mut self, desc: impl Into<String>) {
135        self.description = desc.into();
136    }
137
138    fn recalculate_present_value(&mut self) {
139        self.present_value =
140            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
141    }
142
143    /// Set the Relinquish_Default (#270).
144    ///
145    /// Validated the same way a commanded Present_Value is (BinaryPV 0 or 1);
146    /// after the store, Present_Value is resolved anew from the priority
147    /// array so an empty array falls back to the new default immediately.
148    pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
149        if self.access != PresentValueAccess::Commandable {
150            return Err(common::unknown_property_error());
151        }
152        if value > 1 {
153            return Err(common::value_out_of_range_error());
154        }
155        self.relinquish_default = value;
156        self.recalculate_present_value();
157        Ok(())
158    }
159
160    /// A noncommandable Present_Value write, network-equivalent.
161    fn write_direct_present_value(
162        &mut self,
163        array_index: Option<u32>,
164        value: PropertyValue,
165    ) -> Result<(), Error> {
166        if array_index.is_some() {
167            return Err(common::property_is_not_an_array_error());
168        }
169        if self.access == PresentValueAccess::ReadOnly && !self.out_of_service {
170            return Err(common::write_access_denied_error());
171        }
172        // Clause 19.2: an otherwise permitted noncommandable NULL is a no-op.
173        if value == PropertyValue::Null {
174            return Ok(());
175        }
176        self.present_value = Self::checked_present_value(value)?;
177        Ok(())
178    }
179
180    fn checked_present_value(value: PropertyValue) -> Result<u32, Error> {
181        let PropertyValue::Enumerated(e) = value else {
182            return Err(common::invalid_data_type_error());
183        };
184        if e > 1 {
185            return Err(common::value_out_of_range_error());
186        }
187        Ok(e)
188    }
189}
190
191impl BACnetObject for BinaryValueObject {
192    fn audit_policy_authority_internal(
193        &mut self,
194    ) -> Option<crate::audit::AuditPolicyAuthority<'_>> {
195        Some(crate::audit::AuditPolicyAuthority::new(
196            &mut self.audit_policy,
197        ))
198    }
199
200    fn audit_object_policy_internal(&self) -> crate::audit::ObjectAuditPolicy {
201        self.audit_policy
202    }
203
204    fn object_identifier(&self) -> ObjectIdentifier {
205        self.oid
206    }
207
208    fn object_name(&self) -> &str {
209        &self.name
210    }
211
212    fn supports_cov(&self) -> bool {
213        true
214    }
215
216    crate::event::impl_builtin_intrinsic_reporting!(
217        event_detector,
218        event_history,
219        [present_value],
220        reliability,
221        event_detection_enable,
222        ChangeOfStateDetector::ALGORITHM
223    );
224
225    fn acknowledge_alarm_correlated_internal(
226        &mut self,
227        event_state: EventState,
228        timestamp: &BACnetTimeStamp,
229    ) -> Result<(), Error> {
230        if !self.event_detection_enable {
231            return Err(Error::protocol(
232                ErrorClass::OBJECT,
233                ErrorCode::NO_ALARM_CONFIGURED,
234            ));
235        }
236        self.event_history.acknowledge_correlated(
237            &mut self.event_detector.acked_transitions,
238            event_state,
239            timestamp,
240        )
241    }
242
243    fn read_property(
244        &self,
245        property: PropertyIdentifier,
246        array_index: Option<u32>,
247    ) -> Result<PropertyValue, Error> {
248        if metadata::excludes(self, property) {
249            return Err(common::unknown_property_error());
250        }
251        let source = match self.access {
252            PresentValueAccess::Commandable => {
253                self.value_source
254                    .read(property, array_index, &self.priority_array)
255            }
256            _ => self.write_source.read(property, array_index),
257        };
258        if let Some(result) = source {
259            return result;
260        }
261        if let Some(result) = self.profile.read(property, array_index) {
262            return result;
263        }
264
265        if let Some(result) = self.audit_policy.read(property, array_index) {
266            return result;
267        }
268        if property == PropertyIdentifier::STATUS_FLAGS {
269            return Ok(common::compute_status_flags(
270                self.status_flags,
271                self.reliability,
272                self.out_of_service,
273                self.event_detector.event_state,
274            ));
275        }
276        if let Some(value) = self.reliability_inhibit.read(property) {
277            return Ok(value);
278        }
279        if let Some(result) = read_common_properties!(self, property, array_index) {
280            return result;
281        }
282        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
283            return Ok(PropertyValue::Boolean(self.event_detection_enable));
284        }
285        if let Some(result) = read_generic_event_properties!(self, property) {
286            return result;
287        }
288        if let Some(result) = self.event_history.read(property, array_index) {
289            return result;
290        }
291        match property {
292            p if p == PropertyIdentifier::OBJECT_TYPE => {
293                Ok(PropertyValue::Enumerated(ObjectType::BINARY_VALUE.to_raw()))
294            }
295            p if p == PropertyIdentifier::PRESENT_VALUE => {
296                Ok(PropertyValue::Enumerated(self.present_value))
297            }
298            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
299                common::read_priority_array!(self, array_index, PropertyValue::Enumerated)
300            }
301            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
302                Ok(PropertyValue::Enumerated(self.relinquish_default))
303            }
304            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
305                Ok(common::current_command_priority(&self.priority_array))
306            }
307            p if p == PropertyIdentifier::ACTIVE_TEXT => {
308                Ok(PropertyValue::CharacterString(self.active_text.clone()))
309            }
310            p if p == PropertyIdentifier::INACTIVE_TEXT => {
311                Ok(PropertyValue::CharacterString(self.inactive_text.clone()))
312            }
313            p if p == PropertyIdentifier::ALARM_VALUE => {
314                // Construction and writes keep exactly one value; ACTIVE is the
315                // defensive construction default if a future reset empties it.
316                Ok(PropertyValue::Enumerated(
317                    self.event_detector
318                        .alarm_values
319                        .first()
320                        .copied()
321                        .unwrap_or(1),
322                ))
323            }
324            _ => Err(common::unknown_property_error()),
325        }
326    }
327
328    fn write_property_from(
329        &mut self,
330        property: PropertyIdentifier,
331        array_index: Option<u32>,
332        value: PropertyValue,
333        priority: Option<u8>,
334        origin: &crate::command_source::CommandOrigin,
335    ) -> Result<(), Error> {
336        if matches!(
337            property,
338            PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
339        ) && array_index.is_some()
340        {
341            return Err(common::property_is_not_an_array_error());
342        }
343        if property == PropertyIdentifier::VALUE_SOURCE {
344            return match self.access {
345                PresentValueAccess::Commandable => {
346                    self.value_source.correct(value, priority, origin)
347                }
348                _ => self.write_source.correct(array_index, value, origin),
349            };
350        }
351        if property == PropertyIdentifier::PRESENT_VALUE {
352            return match self.access {
353                PresentValueAccess::Commandable => {
354                    crate::command_source::write_sourced_priority!(
355                        self,
356                        value,
357                        priority,
358                        origin,
359                        Self::checked_present_value
360                    )
361                }
362                PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
363                    self.write_source.admit(origin)?;
364                    // A NULL is a no-op (Clause 19.2), so it has no writer.
365                    let null = value == PropertyValue::Null;
366                    self.write_direct_present_value(array_index, value)?;
367                    if !null {
368                        self.write_source.record(origin);
369                    }
370                    Ok(())
371                }
372            };
373        }
374        self.write_property(property, array_index, value, priority)
375    }
376
377    fn write_property(
378        &mut self,
379        property: PropertyIdentifier,
380        array_index: Option<u32>,
381        value: PropertyValue,
382        priority: Option<u8>,
383    ) -> Result<(), Error> {
384        if metadata::excludes(self, property) {
385            return Err(common::unknown_property_error());
386        }
387        if let Some(result) = self.profile.write(property, array_index, &value) {
388            return result;
389        }
390        if let Some(result) = self
391            .audit_policy
392            .write(property, array_index, &value, priority)
393        {
394            return result;
395        }
396
397        if property == PropertyIdentifier::PRESENT_VALUE {
398            if self.access == PresentValueAccess::Commandable {
399                return Err(common::write_access_denied_error());
400            }
401            // A tracked source needs the writer (`write_property_from`).
402            self.write_source.unsourced()?;
403            return self.write_direct_present_value(array_index, value);
404        }
405        if property == PropertyIdentifier::VALUE_SOURCE {
406            return Err(common::write_access_denied_error());
407        }
408        if property == PropertyIdentifier::ACTIVE_TEXT {
409            if let PropertyValue::CharacterString(s) = value {
410                self.active_text = s;
411                return Ok(());
412            }
413            return Err(common::invalid_data_type_error());
414        }
415        if property == PropertyIdentifier::INACTIVE_TEXT {
416            if let PropertyValue::CharacterString(s) = value {
417                self.inactive_text = s;
418                return Ok(());
419            }
420            return Err(common::invalid_data_type_error());
421        }
422        if property == PropertyIdentifier::ALARM_VALUE {
423            if let PropertyValue::Enumerated(v) = value {
424                if v > 1 {
425                    return Err(common::value_out_of_range_error());
426                }
427                self.event_detector.alarm_values = vec![v];
428                return Ok(());
429            }
430            return Err(common::invalid_data_type_error());
431        }
432        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
433            if let PropertyValue::Boolean(v) = value {
434                self.event_detection_enable = v;
435                if !v {
436                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
437                    self.event_detector.acked_transitions =
438                        bacnet_types::bitstring::EventTransitionBits::all();
439                    self.event_detector.pending = None;
440                    self.event_detector.fault_reliability = None;
441                    self.event_history.reset();
442                }
443                return Ok(());
444            }
445            return Err(common::invalid_data_type_error());
446        }
447        if property == PropertyIdentifier::RELINQUISH_DEFAULT {
448            if let PropertyValue::Enumerated(e) = value {
449                return self.set_relinquish_default(e);
450            }
451            return Err(common::invalid_data_type_error());
452        }
453        // Event_Message_Texts_Config and the Event_Algorithm_Inhibit pair (#1329).
454        if let Some(result) =
455            self.event_history
456                .write(property, array_index, &value, self.event_detection_enable)
457        {
458            return result;
459        }
460        if let Some(result) = write_generic_event_properties!(self, property, value) {
461            return result;
462        }
463        if let Some(result) = self.reliability_inhibit.write_inhibit(
464            &mut self.reliability,
465            self.out_of_service,
466            property,
467            &value,
468        ) {
469            return result;
470        }
471        if let Some(result) = self.reliability_inhibit.write_out_of_service(
472            &mut self.out_of_service,
473            &mut self.reliability,
474            &mut self.reliability_before_out_of_service,
475            property,
476            &value,
477        ) {
478            return result.map(|_| ());
479        }
480        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
481            return result;
482        }
483        if let Some(result) = common::write_description(&mut self.description, property, &value) {
484            return result;
485        }
486        // Clause 12.8 requires simulation/test writes while Out_Of_Service is TRUE:
487        // Present_Value is writable, as is Reliability when that property exists
488        // and supports values beyond NO_FAULT_DETECTED.
489        // `is_writable_property` stays statically true because it describes capability.
490        if let Some(result) = self.reliability_inhibit.write_client_reliability(
491            self.out_of_service,
492            &mut self.reliability,
493            property,
494            &value,
495        ) {
496            return result;
497        }
498        Err(crate::common::unhandled_write_error(
499            self.property_metadata().as_ref(),
500            property,
501            array_index,
502        ))
503    }
504
505    fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
506        self.profile.capability()
507    }
508
509    fn advance_monotonic_time_internal(&mut self, now: core::time::Duration) -> bool {
510        self.profile.expire(now);
511        false
512    }
513
514    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
515        metadata::for_object(self)
516    }
517
518    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
519        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
520    }
521
522    fn is_createable(&self) -> bool {
523        true
524    }
525
526    fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
527        if self.out_of_service || self.reliability_inhibit.enabled() {
528            return Err(common::write_access_denied_error());
529        }
530        if !common::is_reliability_value_valid(reliability) {
531            return Err(common::value_out_of_range_error());
532        }
533        self.reliability = reliability;
534        Ok(())
535    }
536
537    fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
538        match self.access {
539            PresentValueAccess::Commandable => {
540                Err(common::optional_functionality_not_supported_error())
541            }
542            _ if self.out_of_service => Err(common::write_access_denied_error()),
543            PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
544                self.present_value = Self::checked_present_value(value)?;
545                self.write_source.forget();
546                Ok(())
547            }
548        }
549    }
550
551    fn set_present_value_from_internal(
552        &mut self,
553        value: PropertyValue,
554        origin: &crate::command_source::CommandOrigin,
555    ) -> Result<(), Error> {
556        self.write_source.admit(origin)?;
557        self.set_present_value_internal(value)?;
558        self.write_source.record(origin);
559        Ok(())
560    }
561
562    fn reliability_evaluation_inhibited_internal(&self) -> bool {
563        self.reliability_inhibit.enabled()
564    }
565}
566
567#[cfg(test)]
568#[path = "tests/value.rs"]
569mod value_tests;