bacnet-objects 0.13.0

BACnet object model: traits, database, and standard object types
Documentation
//! Shared state machine for the elected Reliability_Evaluation_Inhibit property.

use bacnet_types::enums::{PropertyIdentifier, Reliability};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;

use crate::common::{
    invalid_data_type_error, is_reliability_value_valid, value_out_of_range_error,
    write_access_denied_error,
};

/// State shared by the nine intrinsic-reporting object types that elect the
/// optional Reliability_Evaluation_Inhibit property.
///
/// The client-override bit records ownership, not value inequality: an
/// accepted same-value Reliability write while Out_Of_Service is still the
/// alternate write recognized by the inhibit exception.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct ReliabilityInhibitState {
    enabled: bool,
    oos_client_reliability_override: bool,
}

/// Whether an Out_Of_Service write may trigger object-owned follow-up work.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OutOfServiceWrite {
    /// A Boolean write was applied, including an accepted same-value write.
    Applied,
    /// NULL relinquishment succeeded without changing state or ownership.
    Relinquished,
}

impl ReliabilityInhibitState {
    #[inline]
    pub(crate) fn enabled(self) -> bool {
        self.enabled
    }

    #[inline]
    pub(crate) fn read(self, property: PropertyIdentifier) -> Option<PropertyValue> {
        (property == PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT)
            .then_some(PropertyValue::Boolean(self.enabled))
    }

    /// Apply a Reliability_Evaluation_Inhibit write atomically.
    ///
    /// TRUE immediately normalizes Reliability unless the current OOS period
    /// owns a successful client Reliability write. FALSE only re-enables
    /// evaluation; it never restores a pre-inhibit value.
    #[inline]
    pub(crate) fn write_inhibit(
        &mut self,
        reliability: &mut Reliability,
        out_of_service: bool,
        property: PropertyIdentifier,
        value: &PropertyValue,
    ) -> Option<Result<(), Error>> {
        if property != PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT {
            return None;
        }
        let PropertyValue::Boolean(enabled) = value else {
            return Some(Err(invalid_data_type_error()));
        };

        self.enabled = *enabled;
        if self.enabled && !(out_of_service && self.oos_client_reliability_override) {
            *reliability = Reliability::NO_FAULT_DETECTED;
        }
        Some(Ok(()))
    }

    /// Apply OOS sequencing. A relinquishment must not trigger reevaluation
    /// or change saved/client Reliability ownership in the calling object.
    #[inline]
    pub(crate) fn write_out_of_service(
        &mut self,
        out_of_service: &mut bool,
        reliability: &mut Reliability,
        saved_reliability: &mut Option<Reliability>,
        property: PropertyIdentifier,
        value: &PropertyValue,
    ) -> Option<Result<OutOfServiceWrite, Error>> {
        if property != PropertyIdentifier::OUT_OF_SERVICE {
            return None;
        }
        if *value == PropertyValue::Null {
            return Some(Ok(OutOfServiceWrite::Relinquished));
        }
        let PropertyValue::Boolean(enabled) = value else {
            return Some(Err(invalid_data_type_error()));
        };

        if !*out_of_service && *enabled {
            *saved_reliability = Some(*reliability);
            self.oos_client_reliability_override = false;
            if self.enabled {
                *reliability = Reliability::NO_FAULT_DETECTED;
            }
        } else if *out_of_service && !*enabled {
            self.oos_client_reliability_override = false;
            let saved = saved_reliability.take();
            *reliability = if self.enabled {
                Reliability::NO_FAULT_DETECTED
            } else {
                saved.unwrap_or(Reliability::NO_FAULT_DETECTED)
            };
        }
        *out_of_service = *enabled;
        Some(Ok(OutOfServiceWrite::Applied))
    }

    /// Apply the OOS-only client Reliability route and record successful
    /// ownership only after type and value validation have completed.
    #[inline]
    pub(crate) fn write_client_reliability(
        &mut self,
        out_of_service: bool,
        reliability: &mut Reliability,
        property: PropertyIdentifier,
        value: &PropertyValue,
    ) -> Option<Result<(), Error>> {
        if property != PropertyIdentifier::RELIABILITY {
            return None;
        }
        if !out_of_service {
            return Some(Err(write_access_denied_error()));
        }
        let PropertyValue::Enumerated(raw) = value else {
            return Some(Err(invalid_data_type_error()));
        };
        let new_reliability = Reliability::from_raw(*raw);
        if !is_reliability_value_valid(new_reliability) {
            return Some(Err(value_out_of_range_error()));
        }

        *reliability = new_reliability;
        self.oos_client_reliability_override = true;
        Some(Ok(()))
    }
}