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