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