Skip to main content

bacnet_objects/analog/
output.rs

1use super::*;
2use crate::common::{
3    read_analog_event_properties, read_generic_event_properties, write_analog_event_properties,
4    write_generic_event_properties,
5};
6use crate::property_metadata::PropertyMetadata;
7
8mod metadata;
9
10// ---------------------------------------------------------------------------
11// AnalogOutput (type 1)
12// ---------------------------------------------------------------------------
13
14/// BACnet Analog Output object.
15pub struct AnalogOutputObject {
16    oid: ObjectIdentifier,
17    name: String,
18    description: String,
19    present_value: f32,
20    units: u32,
21    out_of_service: bool,
22    status_flags: StatusFlags,
23    priority_array: [Option<f32>; 16],
24    relinquish_default: f32,
25    /// COV_Increment: minimum change threshold for COV notifications.
26    /// Default 0.0 means notify on any write (including no-change).
27    /// Set to a positive value for delta-based filtering.
28    cov_increment: f32,
29    event_detector: OutOfRangeDetector,
30    /// Event_Detection_Enable (Clause 12.3). A FALSE value suspends
31    /// event-state-machine evaluation under Clause 13.2.2.1.
32    event_detection_enable: bool,
33    reliability: Reliability,
34    reliability_before_out_of_service: Option<Reliability>,
35    reliability_inhibit: common::ReliabilityInhibitState,
36    min_pres_value: Option<f32>,
37    max_pres_value: Option<f32>,
38    pub(crate) event_history: EventHistory,
39    /// Value source tracking.
40    value_source: crate::command_source::ValueSourceTracking,
41}
42
43impl AnalogOutputObject {
44    /// Create a new Analog Output object.
45    pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
46        let oid = ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, instance)?;
47        Ok(Self {
48            oid,
49            name: name.into(),
50            description: String::new(),
51            present_value: 0.0,
52            units,
53            out_of_service: false,
54            status_flags: StatusFlags::empty(),
55            priority_array: [None; 16],
56            relinquish_default: 0.0,
57            cov_increment: 0.0,
58            event_detector: OutOfRangeDetector::default(),
59            event_detection_enable: true,
60            reliability: Reliability::NO_FAULT_DETECTED,
61            reliability_before_out_of_service: None,
62            reliability_inhibit: common::ReliabilityInhibitState::default(),
63            min_pres_value: None,
64            max_pres_value: None,
65            event_history: EventHistory::default(),
66            value_source: crate::command_source::ValueSourceTracking::default(),
67        })
68    }
69
70    /// Set the description string.
71    pub fn set_description(&mut self, desc: impl Into<String>) {
72        self.description = desc.into();
73    }
74
75    /// Set minimum engineering-bound metadata; this is not a reliability fault limit.
76    pub fn set_min_pres_value(&mut self, value: f32) {
77        self.min_pres_value = Some(value);
78    }
79
80    /// Set maximum engineering-bound metadata; this is not a reliability fault limit.
81    pub fn set_max_pres_value(&mut self, value: f32) {
82        self.max_pres_value = Some(value);
83    }
84
85    /// Recalculate present-value from the priority array.
86    fn recalculate_present_value(&mut self) {
87        self.present_value =
88            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
89    }
90
91    /// Set the Relinquish_Default (#270).
92    ///
93    /// Validated the same way a commanded Present_Value is (finite Real);
94    /// after the store, Present_Value is resolved anew from the priority
95    /// array so an empty array falls back to the new default immediately.
96    pub fn set_relinquish_default(&mut self, value: f32) -> Result<(), Error> {
97        common::reject_non_finite(value)?;
98        self.relinquish_default = value;
99        self.recalculate_present_value();
100        Ok(())
101    }
102}
103
104impl BACnetObject for AnalogOutputObject {
105    fn object_identifier(&self) -> ObjectIdentifier {
106        self.oid
107    }
108
109    fn object_name(&self) -> &str {
110        &self.name
111    }
112
113    fn read_property(
114        &self,
115        property: PropertyIdentifier,
116        array_index: Option<u32>,
117    ) -> Result<PropertyValue, Error> {
118        if let Some(result) = self
119            .value_source
120            .read(property, array_index, &self.priority_array)
121        {
122            return result;
123        }
124
125        if property == PropertyIdentifier::STATUS_FLAGS {
126            return Ok(common::compute_status_flags(
127                self.status_flags,
128                self.reliability,
129                self.out_of_service,
130                self.event_detector.event_state,
131            ));
132        }
133        if let Some(value) = self.reliability_inhibit.read(property) {
134            return Ok(value);
135        }
136        if let Some(result) = read_common_properties!(self, property, array_index) {
137            return result;
138        }
139        if let Some(result) = read_analog_event_properties!(self, property) {
140            return result;
141        }
142        if let Some(result) = self.event_history.read(property, array_index) {
143            return result;
144        }
145        if let Some(result) = read_generic_event_properties!(self, property) {
146            return result;
147        }
148        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
149            return Ok(PropertyValue::Boolean(self.event_detection_enable));
150        }
151        match property {
152            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
153                ObjectType::ANALOG_OUTPUT.to_raw(),
154            )),
155            p if p == PropertyIdentifier::PRESENT_VALUE => {
156                Ok(PropertyValue::Real(self.present_value))
157            }
158            p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
159            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
160                common::read_priority_array!(self, array_index, PropertyValue::Real)
161            }
162            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
163                Ok(PropertyValue::Real(self.relinquish_default))
164            }
165            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
166                Ok(common::current_command_priority(&self.priority_array))
167            }
168            p if p == PropertyIdentifier::COV_INCREMENT => {
169                Ok(PropertyValue::Real(self.cov_increment))
170            }
171            p if p == PropertyIdentifier::MIN_PRES_VALUE => match self.min_pres_value {
172                Some(v) => Ok(PropertyValue::Real(v)),
173                None => Err(common::unknown_property_error()),
174            },
175            p if p == PropertyIdentifier::MAX_PRES_VALUE => match self.max_pres_value {
176                Some(v) => Ok(PropertyValue::Real(v)),
177                None => Err(common::unknown_property_error()),
178            },
179            _ => Err(common::unknown_property_error()),
180        }
181    }
182
183    fn write_property_from(
184        &mut self,
185        property: PropertyIdentifier,
186        array_index: Option<u32>,
187        value: PropertyValue,
188        priority: Option<u8>,
189        origin: &crate::command_source::CommandOrigin,
190    ) -> Result<(), Error> {
191        if matches!(
192            property,
193            PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
194        ) && array_index.is_some()
195        {
196            return Err(common::property_is_not_an_array_error());
197        }
198        if property == PropertyIdentifier::VALUE_SOURCE {
199            return self.value_source.correct(value, priority, origin);
200        }
201        if property == PropertyIdentifier::PRESENT_VALUE {
202            return crate::command_source::write_sourced_priority!(
203                self,
204                value,
205                priority,
206                origin,
207                |v| {
208                    if let PropertyValue::Real(f) = v {
209                        if !f.is_finite() {
210                            return Err(common::value_out_of_range_error());
211                        }
212                        Ok(f)
213                    } else {
214                        Err(common::invalid_data_type_error())
215                    }
216                }
217            );
218        }
219        self.write_property(property, array_index, value, priority)
220    }
221
222    fn write_property(
223        &mut self,
224        property: PropertyIdentifier,
225        array_index: Option<u32>,
226        value: PropertyValue,
227        _priority: Option<u8>,
228    ) -> Result<(), Error> {
229        if matches!(
230            property,
231            PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
232        ) {
233            return Err(common::write_access_denied_error());
234        }
235        if let Some(result) = self.reliability_inhibit.write_inhibit(
236            &mut self.reliability,
237            self.out_of_service,
238            property,
239            &value,
240        ) {
241            return result;
242        }
243        if let Some(result) = self.reliability_inhibit.write_out_of_service(
244            &mut self.out_of_service,
245            &mut self.reliability,
246            &mut self.reliability_before_out_of_service,
247            property,
248            &value,
249        ) {
250            return result.map(|_| ());
251        }
252        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
253            return result;
254        }
255        if let Some(result) = common::write_description(&mut self.description, property, &value) {
256            return result;
257        }
258        // Clause 12.3 requires simulation/test writes while Out_Of_Service is TRUE:
259        // Present_Value is writable, as is Reliability when that property exists
260        // and supports values beyond NO_FAULT_DETECTED.
261        // `is_writable_property` stays statically true because it describes capability.
262        if let Some(result) = self.reliability_inhibit.write_client_reliability(
263            self.out_of_service,
264            &mut self.reliability,
265            property,
266            &value,
267        ) {
268            return result;
269        }
270        if property == PropertyIdentifier::RELINQUISH_DEFAULT {
271            if let PropertyValue::Real(v) = value {
272                return self.set_relinquish_default(v);
273            }
274            return Err(common::invalid_data_type_error());
275        }
276        if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
277        {
278            return result;
279        }
280        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
281            if let PropertyValue::Boolean(v) = value {
282                self.event_detection_enable = v;
283                if !v {
284                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
285                    self.event_detector.acked_transitions =
286                        bacnet_types::bitstring::EventTransitionBits::all();
287                    self.event_detector.pending = None;
288                    self.event_detector.fault_reliability = None;
289                    self.event_history.reset();
290                }
291                return Ok(());
292            }
293            return Err(common::invalid_data_type_error());
294        }
295        if let Some(result) = write_analog_event_properties!(self, property, value) {
296            return result;
297        }
298        // Event_Message_Texts_Config and the Event_Algorithm_Inhibit pair (#1329).
299        if let Some(result) =
300            self.event_history
301                .write(property, array_index, &value, self.event_detection_enable)
302        {
303            return result;
304        }
305        if let Some(result) = write_generic_event_properties!(self, property, value) {
306            return result;
307        }
308        Err(crate::common::unhandled_write_error(
309            self.property_metadata().as_ref(),
310            property,
311            array_index,
312        ))
313    }
314
315    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
316        metadata::for_object(self)
317    }
318
319    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
320        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
321    }
322
323    fn supports_cov(&self) -> bool {
324        true
325    }
326
327    fn cov_increment(&self) -> Option<f64> {
328        Some(f64::from(self.cov_increment))
329    }
330
331    crate::event::impl_builtin_intrinsic_reporting!(
332        event_detector,
333        event_history,
334        [present_value],
335        reliability,
336        event_detection_enable,
337        OutOfRangeDetector::ALGORITHM
338    );
339
340    fn acknowledge_alarm(
341        &mut self,
342        transition_bit: bacnet_types::bitstring::EventTransitionBits,
343    ) -> Result<(), bacnet_types::error::Error> {
344        self.event_detector.acked_transitions |=
345            transition_bit & bacnet_types::bitstring::EventTransitionBits::all();
346        Ok(())
347    }
348
349    fn acknowledge_alarm_correlated_internal(
350        &mut self,
351        event_state: EventState,
352        timestamp: &BACnetTimeStamp,
353    ) -> Result<(), Error> {
354        if !self.event_detection_enable {
355            return Err(Error::Protocol {
356                class: ErrorClass::OBJECT.to_raw() as u32,
357                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
358            });
359        }
360        self.event_history.acknowledge_correlated(
361            &mut self.event_detector.acked_transitions,
362            event_state,
363            timestamp,
364        )
365    }
366
367    fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
368        if self.out_of_service || self.reliability_inhibit.enabled() {
369            return Err(common::write_access_denied_error());
370        }
371        if !common::is_reliability_value_valid(reliability) {
372            return Err(common::value_out_of_range_error());
373        }
374        self.reliability = reliability;
375        Ok(())
376    }
377
378    fn reliability_evaluation_inhibited_internal(&self) -> bool {
379        self.reliability_inhibit.enabled()
380    }
381
382    fn is_createable(&self) -> bool {
383        true
384    }
385
386    fn creation_only_properties(&self) -> &'static [PropertyIdentifier] {
387        super::CREATION_ONLY
388    }
389
390    fn initialize_property(
391        &mut self,
392        property: PropertyIdentifier,
393        value: PropertyValue,
394    ) -> Result<(), Error> {
395        super::initialize_units(&mut self.units, property, value)
396    }
397}
398
399#[cfg(test)]
400mod detection_enable_reset_tests {
401    use super::*;
402
403    /// Regression guard for issue #123: once transitions populate timestamps and messages,
404    /// disabling event detection must still restore their Clause 13.2.2.1 initial conditions.
405    #[test]
406    fn ao_disabling_detection_resets_event_history() {
407        let mut ao = AnalogOutputObject::new(1, "AO-1", 62).unwrap();
408        assert_eq!(
409            ao.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
410                .unwrap(),
411            PropertyValue::Boolean(true)
412        );
413        ao.event_detector.event_state = bacnet_types::enums::EventState::HIGH_LIMIT;
414        ao.event_detector.acked_transitions = bacnet_types::bitstring::EventTransitionBits::empty();
415        ao.event_detector.pending = Some(crate::event::PendingTransition {
416            state: bacnet_types::enums::EventState::HIGH_LIMIT,
417            remaining: 2,
418        });
419        ao.event_detector.fault_reliability = Some(bacnet_types::enums::Reliability::NO_SENSOR);
420        ao.event_history.time_stamps = [
421            BACnetTimeStamp::SequenceNumber(1),
422            BACnetTimeStamp::SequenceNumber(2),
423            BACnetTimeStamp::SequenceNumber(3),
424        ];
425        ao.event_history.message_texts = ["offnormal".into(), "fault".into(), "normal".into()];
426
427        ao.write_property(
428            PropertyIdentifier::EVENT_DETECTION_ENABLE,
429            None,
430            PropertyValue::Boolean(false),
431            None,
432        )
433        .unwrap();
434
435        assert_eq!(
436            ao.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
437                .unwrap(),
438            PropertyValue::Boolean(false)
439        );
440        assert_eq!(
441            ao.event_detector.event_state,
442            bacnet_types::enums::EventState::NORMAL
443        );
444        assert_eq!(
445            ao.event_detector.acked_transitions,
446            bacnet_types::bitstring::EventTransitionBits::all()
447        );
448        assert!(ao.event_detector.pending.is_none());
449        assert!(ao.event_detector.fault_reliability.is_none());
450        assert_eq!(
451            ao.event_history.time_stamps,
452            [
453                BACnetTimeStamp::SequenceNumber(0),
454                BACnetTimeStamp::SequenceNumber(0),
455                BACnetTimeStamp::SequenceNumber(0),
456            ]
457        );
458        assert_eq!(
459            ao.event_history.message_texts,
460            [String::new(), String::new(), String::new()]
461        );
462    }
463}