Skip to main content

bacnet_objects/analog/
mod.rs

1//! Analog Input (type 0), Analog Output (type 1), and Analog Value (type 2) objects.
2//!
3//! Per ASHRAE 135-2020 Clauses 12.2 (AI), 12.3 (AO), and 12.4 (AV).
4
5use alloc::borrow::Cow;
6use bacnet_types::enums::{
7    EngineeringUnits, ErrorClass, ErrorCode, EventState, ObjectType, PropertyIdentifier,
8    Reliability,
9};
10use bacnet_types::error::Error;
11use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier, PropertyValue, StatusFlags};
12
13use crate::common::{self, read_common_properties};
14use crate::event::{OutOfRangeDetector, history::EventHistory};
15use crate::traits::{BACnetObject, ReliabilityEvaluation};
16
17#[derive(Clone, Copy)]
18struct FaultOutOfRangeLimits {
19    low: f32,
20    high: f32,
21}
22
23#[derive(Clone, Copy, PartialEq, Eq)]
24pub(crate) enum OwnedRangeFault {
25    UnderRange,
26    OverRange,
27}
28
29impl OwnedRangeFault {
30    fn reliability(self) -> Reliability {
31        match self {
32            Self::UnderRange => Reliability::UNDER_RANGE,
33            Self::OverRange => Reliability::OVER_RANGE,
34        }
35    }
36}
37
38#[derive(Default)]
39struct FaultOutOfRangeState {
40    // Deliberately separate from the intrinsic OutOfRangeDetector: reliability
41    // has strict boundaries, no deadband, and first-stage ownership precedence.
42    limits: Option<FaultOutOfRangeLimits>,
43    owned_fault: Option<OwnedRangeFault>,
44}
45
46impl FaultOutOfRangeState {
47    fn configure(&mut self, low: f32, high: f32) -> Result<(), Error> {
48        if !low.is_finite() || !high.is_finite() || low > high {
49            return Err(common::value_out_of_range_error());
50        }
51        self.limits = Some(FaultOutOfRangeLimits { low, high });
52        Ok(())
53    }
54
55    fn read_limit(&self, property: PropertyIdentifier) -> Option<PropertyValue> {
56        let limits = self.limits?;
57        match property {
58            PropertyIdentifier::FAULT_LOW_LIMIT => Some(PropertyValue::Real(limits.low)),
59            PropertyIdentifier::FAULT_HIGH_LIMIT => Some(PropertyValue::Real(limits.high)),
60            _ => None,
61        }
62    }
63
64    fn clear_ownership(&mut self) {
65        self.owned_fault = None;
66    }
67
68    fn evaluate(
69        &mut self,
70        monitored_value: f32,
71        reliability: &mut Reliability,
72    ) -> Result<ReliabilityEvaluation, Error> {
73        let Some(limits) = self.limits else {
74            return Ok(ReliabilityEvaluation::Unchanged);
75        };
76        if !monitored_value.is_finite() {
77            return Err(common::value_out_of_range_error());
78        }
79        let observed_fault = if monitored_value < limits.low {
80            Some(OwnedRangeFault::UnderRange)
81        } else if monitored_value > limits.high {
82            Some(OwnedRangeFault::OverRange)
83        } else {
84            None
85        };
86
87        let (new_reliability, new_owner) = if self.owned_fault.is_some() {
88            (
89                observed_fault
90                    .map(OwnedRangeFault::reliability)
91                    .unwrap_or(Reliability::NO_FAULT_DETECTED),
92                observed_fault,
93            )
94        } else if *reliability == Reliability::NO_FAULT_DETECTED {
95            let Some(fault) = observed_fault else {
96                return Ok(ReliabilityEvaluation::Unchanged);
97            };
98            (fault.reliability(), Some(fault))
99        } else {
100            return Ok(ReliabilityEvaluation::Unchanged);
101        };
102
103        if new_reliability == *reliability {
104            return Ok(ReliabilityEvaluation::Unchanged);
105        }
106        let old_reliability = *reliability;
107        *reliability = new_reliability;
108        self.owned_fault = new_owner;
109        Ok(ReliabilityEvaluation::Changed {
110            old_reliability,
111            new_reliability,
112        })
113    }
114}
115
116/// What a createable analog object takes only from a CreateObject initial
117/// value (#1429): Units, which stays read-only to WriteProperty.
118const CREATION_ONLY: &[PropertyIdentifier] = &[PropertyIdentifier::UNITS];
119
120/// Set `units` from a Units initial value. An Enumerated above 65535 is
121/// outside BACnetEngineeringUnits (Clause 21) and is VALUE_OUT_OF_RANGE;
122/// any other property is WRITE_ACCESS_DENIED, as the trait default answers.
123fn initialize_units(
124    units: &mut EngineeringUnits,
125    property: PropertyIdentifier,
126    value: PropertyValue,
127) -> Result<(), Error> {
128    if property != PropertyIdentifier::UNITS {
129        return Err(common::write_access_denied_error());
130    }
131    let PropertyValue::Enumerated(raw) = value else {
132        return Err(common::invalid_data_type_error());
133    };
134    if raw > 65_535 {
135        return Err(common::value_out_of_range_error());
136    }
137    *units = EngineeringUnits::from_raw(raw);
138    Ok(())
139}
140
141mod input;
142mod output;
143mod value;
144pub use input::*;
145pub use output::*;
146pub use value::*;
147
148// ---------------------------------------------------------------------------
149// Tests
150// ---------------------------------------------------------------------------
151
152#[cfg(test)]
153mod tests;