Skip to main content

bacnet_objects/analog/
value.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// AnalogValue (type 2)
9// ---------------------------------------------------------------------------
10
11/// BACnet Analog Value object.
12pub struct AnalogValueObject {
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    /// 16-level priority array. `None` = no command at that level.
21    priority_array: [Option<f32>; 16],
22    relinquish_default: f32,
23    /// COV_Increment: minimum change threshold for COV notifications.
24    /// Default 0.0 means notify on any write (including no-change).
25    /// Set to a positive value for delta-based filtering.
26    cov_increment: f32,
27    event_detector: OutOfRangeDetector,
28    /// Event_Detection_Enable (Clause 12.4). Clause 13.2.2.1: "If the
29    /// Event_Detection_Enable property is FALSE, then this state machine is not evaluated."
30    event_detection_enable: bool,
31    /// Reliability: 0 = NO_FAULT_DETECTED.
32    reliability: u32,
33    reliability_before_out_of_service: Option<u32>,
34    reliability_inhibit: common::ReliabilityInhibitState,
35    fault_out_of_range: FaultOutOfRangeState,
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: common::ValueSourceTracking,
41}
42
43impl AnalogValueObject {
44    /// Create a new Analog Value object.
45    pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
46        let oid = ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 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: 0,
61            reliability_before_out_of_service: None,
62            reliability_inhibit: common::ReliabilityInhibitState::default(),
63            fault_out_of_range: FaultOutOfRangeState::default(),
64            min_pres_value: None,
65            max_pres_value: None,
66            event_history: EventHistory::default(),
67            value_source: common::ValueSourceTracking::default(),
68        })
69    }
70
71    /// Set the present value directly (bypasses priority array; use when out-of-service
72    /// or for initialisation before the priority-array mechanism takes over).
73    pub fn set_present_value(&mut self, value: f32) {
74        debug_assert!(
75            value.is_finite(),
76            "set_present_value called with non-finite value"
77        );
78        self.present_value = value;
79    }
80
81    /// Set the description string.
82    pub fn set_description(&mut self, desc: impl Into<String>) {
83        self.description = desc.into();
84    }
85
86    /// Set minimum engineering-bound metadata; this is not a reliability fault limit.
87    pub fn set_min_pres_value(&mut self, value: f32) {
88        self.min_pres_value = Some(value);
89    }
90
91    /// Set maximum engineering-bound metadata; this is not a reliability fault limit.
92    pub fn set_max_pres_value(&mut self, value: f32) {
93        self.max_pres_value = Some(value);
94    }
95
96    /// Configure the optional object-owned FAULT_OUT_OF_RANGE algorithm.
97    ///
98    /// Both limits become readable together. Equal limits are valid; non-finite
99    /// limits and a low limit greater than the high limit are rejected without
100    /// changing the prior configuration.
101    pub fn configure_fault_out_of_range(&mut self, low: f32, high: f32) -> Result<(), Error> {
102        self.fault_out_of_range.configure(low, high)
103    }
104
105    /// Recalculate present-value from the priority array.
106    fn recalculate_present_value(&mut self) {
107        self.present_value =
108            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
109    }
110
111    /// Set the Relinquish_Default (#270).
112    ///
113    /// Validated the same way a commanded Present_Value is (finite Real);
114    /// after the store, Present_Value is resolved anew from the priority
115    /// array so an empty array falls back to the new default immediately.
116    pub fn set_relinquish_default(&mut self, value: f32) -> Result<(), Error> {
117        common::reject_non_finite(value)?;
118        self.relinquish_default = value;
119        self.recalculate_present_value();
120        Ok(())
121    }
122}
123
124impl BACnetObject for AnalogValueObject {
125    fn object_identifier(&self) -> ObjectIdentifier {
126        self.oid
127    }
128
129    fn object_name(&self) -> &str {
130        &self.name
131    }
132
133    fn read_property(
134        &self,
135        property: PropertyIdentifier,
136        array_index: Option<u32>,
137    ) -> Result<PropertyValue, Error> {
138        if property == PropertyIdentifier::STATUS_FLAGS {
139            return Ok(common::compute_status_flags(
140                self.status_flags,
141                self.reliability,
142                self.out_of_service,
143                self.event_detector.event_state.to_raw(),
144            ));
145        }
146        if let Some(value) = self.reliability_inhibit.read(property) {
147            return Ok(value);
148        }
149        if let Some(result) = read_common_properties!(self, property, array_index) {
150            return result;
151        }
152        if let Some(result) = read_analog_event_properties!(self, property) {
153            return result;
154        }
155        if let Some(result) = self.event_history.read(property, array_index) {
156            return result;
157        }
158        if let Some(result) = read_generic_event_properties!(self, property) {
159            return result;
160        }
161        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
162            return Ok(PropertyValue::Boolean(self.event_detection_enable));
163        }
164        if let Some(value) = self.fault_out_of_range.read_limit(property) {
165            return Ok(value);
166        }
167        match property {
168            p if p == PropertyIdentifier::OBJECT_TYPE => {
169                Ok(PropertyValue::Enumerated(ObjectType::ANALOG_VALUE.to_raw()))
170            }
171            p if p == PropertyIdentifier::PRESENT_VALUE => {
172                Ok(PropertyValue::Real(self.present_value))
173            }
174            p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
175            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
176                common::read_priority_array!(self, array_index, PropertyValue::Real)
177            }
178            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
179                Ok(PropertyValue::Real(self.relinquish_default))
180            }
181            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
182                Ok(common::current_command_priority(&self.priority_array))
183            }
184            p if p == PropertyIdentifier::VALUE_SOURCE => {
185                Ok(self.value_source.value_source.clone())
186            }
187            p if p == PropertyIdentifier::LAST_COMMAND_TIME => Ok(PropertyValue::Unsigned(
188                match self.value_source.last_command_time {
189                    BACnetTimeStamp::SequenceNumber(n) => u64::from(n),
190                    _ => 0,
191                },
192            )),
193            p if p == PropertyIdentifier::COV_INCREMENT => {
194                Ok(PropertyValue::Real(self.cov_increment))
195            }
196            p if p == PropertyIdentifier::MIN_PRES_VALUE => match self.min_pres_value {
197                Some(v) => Ok(PropertyValue::Real(v)),
198                None => Err(common::unknown_property_error()),
199            },
200            p if p == PropertyIdentifier::MAX_PRES_VALUE => match self.max_pres_value {
201                Some(v) => Ok(PropertyValue::Real(v)),
202                None => Err(common::unknown_property_error()),
203            },
204            _ => Err(common::unknown_property_error()),
205        }
206    }
207
208    fn write_property(
209        &mut self,
210        property: PropertyIdentifier,
211        array_index: Option<u32>,
212        value: PropertyValue,
213        priority: Option<u8>,
214    ) -> Result<(), Error> {
215        common::write_priority_array_direct!(self, property, array_index, value, |v| {
216            if let PropertyValue::Real(f) = v {
217                if !f.is_finite() {
218                    return Err(common::value_out_of_range_error());
219                }
220                Ok(f)
221            } else {
222                Err(common::invalid_data_type_error())
223            }
224        });
225        if property == PropertyIdentifier::PRESENT_VALUE {
226            return common::write_priority_array!(self, value, priority, |v| {
227                if let PropertyValue::Real(f) = v {
228                    if !f.is_finite() {
229                        return Err(common::value_out_of_range_error());
230                    }
231                    Ok(f)
232                } else {
233                    Err(common::invalid_data_type_error())
234                }
235            });
236        }
237        if let Some(result) = self.reliability_inhibit.write_inhibit(
238            &mut self.reliability,
239            self.out_of_service,
240            property,
241            &value,
242        ) {
243            return result;
244        }
245        if let Some(result) = self.reliability_inhibit.write_out_of_service(
246            &mut self.out_of_service,
247            &mut self.reliability,
248            &mut self.reliability_before_out_of_service,
249            property,
250            &value,
251        ) {
252            return result;
253        }
254        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
255            return result;
256        }
257        if let Some(result) = common::write_description(&mut self.description, property, &value) {
258            return result;
259        }
260        // Clause 12.4, while Out_Of_Service is TRUE: "the Present_Value property and
261        // the Reliability property, if present and capable of taking on values other
262        // than NO_FAULT_DETECTED, shall be writable to allow simulating specific
263        // conditions or for testing purposes".
264        // `is_writable_property` stays statically true because it describes capability.
265        if let Some(result) = self.reliability_inhibit.write_client_reliability(
266            self.out_of_service,
267            &mut self.reliability,
268            property,
269            &value,
270        ) {
271            return result;
272        }
273        if property == PropertyIdentifier::RELINQUISH_DEFAULT {
274            if let PropertyValue::Real(v) = value {
275                return self.set_relinquish_default(v);
276            }
277            return Err(common::invalid_data_type_error());
278        }
279        if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
280        {
281            return result;
282        }
283        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
284            if let PropertyValue::Boolean(v) = value {
285                self.event_detection_enable = v;
286                if !v {
287                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
288                    self.event_detector.acked_transitions = 0b111;
289                    self.event_detector.pending = None;
290                    self.event_detector.fault_reliability = None;
291                    self.event_history.reset();
292                }
293                return Ok(());
294            }
295            return Err(common::invalid_data_type_error());
296        }
297        if let Some(result) = write_analog_event_properties!(self, property, value) {
298            return result;
299        }
300        if let Some(result) = write_generic_event_properties!(self, property, value) {
301            return result;
302        }
303        Err(common::write_access_denied_error())
304    }
305
306    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
307        static PROPS: &[PropertyIdentifier] = &[
308            PropertyIdentifier::OBJECT_IDENTIFIER,
309            PropertyIdentifier::OBJECT_NAME,
310            PropertyIdentifier::DESCRIPTION,
311            PropertyIdentifier::OBJECT_TYPE,
312            PropertyIdentifier::PRESENT_VALUE,
313            PropertyIdentifier::STATUS_FLAGS,
314            PropertyIdentifier::EVENT_STATE,
315            PropertyIdentifier::EVENT_DETECTION_ENABLE,
316            PropertyIdentifier::OUT_OF_SERVICE,
317            PropertyIdentifier::UNITS,
318            PropertyIdentifier::PRIORITY_ARRAY,
319            PropertyIdentifier::RELINQUISH_DEFAULT,
320            PropertyIdentifier::CURRENT_COMMAND_PRIORITY,
321            PropertyIdentifier::COV_INCREMENT,
322            PropertyIdentifier::HIGH_LIMIT,
323            PropertyIdentifier::LOW_LIMIT,
324            PropertyIdentifier::DEADBAND,
325            PropertyIdentifier::LIMIT_ENABLE,
326            PropertyIdentifier::EVENT_ENABLE,
327            PropertyIdentifier::NOTIFY_TYPE,
328            PropertyIdentifier::NOTIFICATION_CLASS,
329            PropertyIdentifier::TIME_DELAY,
330            PropertyIdentifier::TIME_DELAY_NORMAL,
331            PropertyIdentifier::RELIABILITY,
332            PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT,
333            PropertyIdentifier::ACKED_TRANSITIONS,
334            PropertyIdentifier::EVENT_TIME_STAMPS,
335            PropertyIdentifier::EVENT_MESSAGE_TEXTS,
336        ];
337        self.fault_out_of_range.property_list(PROPS)
338    }
339
340    fn supports_cov(&self) -> bool {
341        true
342    }
343
344    fn cov_increment(&self) -> Option<f32> {
345        Some(self.cov_increment)
346    }
347
348    crate::event::impl_builtin_intrinsic_reporting!(
349        event_detector,
350        event_history,
351        [present_value],
352        reliability,
353        event_detection_enable,
354        OutOfRangeDetector::ALGORITHM
355    );
356    impl_intrinsic_write_rollback!(
357        event_detector,
358        event_detection_enable,
359        event_history,
360        reliability_inhibit,
361        reliability,
362        out_of_service,
363        reliability_before_out_of_service,
364        fault_out_of_range
365    );
366
367    fn acknowledge_alarm(&mut self, transition_bit: u8) -> Result<(), bacnet_types::error::Error> {
368        self.event_detector.acked_transitions |= transition_bit & 0x07;
369        Ok(())
370    }
371
372    fn acknowledge_alarm_correlated_internal(
373        &mut self,
374        event_state: EventState,
375        timestamp: &BACnetTimeStamp,
376    ) -> Result<(), Error> {
377        if !self.event_detection_enable {
378            return Err(Error::Protocol {
379                class: ErrorClass::OBJECT.to_raw() as u32,
380                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
381            });
382        }
383        self.event_history.acknowledge_correlated(
384            &mut self.event_detector.acked_transitions,
385            event_state,
386            timestamp,
387        )
388    }
389
390    fn set_reliability_internal(&mut self, reliability: u32) -> Result<(), Error> {
391        if self.out_of_service || self.reliability_inhibit.enabled() {
392            return Err(common::write_access_denied_error());
393        }
394        if !common::is_reliability_value_valid(reliability) {
395            return Err(common::value_out_of_range_error());
396        }
397        self.reliability = reliability;
398        self.fault_out_of_range.clear_ownership();
399        Ok(())
400    }
401
402    fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
403        if self.out_of_service || self.reliability_inhibit.enabled() {
404            return Ok(ReliabilityEvaluation::Unchanged);
405        }
406        self.fault_out_of_range
407            .evaluate(self.present_value, &mut self.reliability)
408    }
409
410    fn reliability_evaluation_inhibited_internal(&self) -> bool {
411        self.reliability_inhibit.enabled()
412    }
413
414    /// AnalogValue is NOT createable: `handle_create_object` has no branch for
415    /// it, so PICS must not advertise createability the runtime rejects.
416    fn is_createable(&self) -> bool {
417        false
418    }
419
420    fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
421        // Mirrors the AnalogValue `write_property` arms. Same set as
422        // AnalogOutput (commandable + common + event properties).
423        common::is_commandable_property_writable(property)
424            || common::is_common_writable(property)
425            || property == PropertyIdentifier::RELIABILITY
426            || property == PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT
427            || property == PropertyIdentifier::COV_INCREMENT
428            || common::is_event_property_writable(property)
429            || property == PropertyIdentifier::EVENT_DETECTION_ENABLE
430    }
431}
432
433#[cfg(test)]
434mod detection_enable_reset_tests {
435    use super::*;
436
437    /// Regression guard for issue #123: once transitions populate timestamps and messages,
438    /// disabling event detection must still restore their Clause 13.2.2.1 initial conditions.
439    #[test]
440    fn av_disabling_detection_resets_event_history() {
441        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
442        assert_eq!(
443            av.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
444                .unwrap(),
445            PropertyValue::Boolean(true)
446        );
447        av.event_detector.event_state = bacnet_types::enums::EventState::HIGH_LIMIT;
448        av.event_detector.acked_transitions = 0;
449        av.event_detector.pending = Some(crate::event::PendingTransition {
450            state: bacnet_types::enums::EventState::HIGH_LIMIT,
451            remaining: 2,
452        });
453        av.event_detector.fault_reliability = Some(1);
454        av.event_history.time_stamps = [
455            BACnetTimeStamp::SequenceNumber(1),
456            BACnetTimeStamp::SequenceNumber(2),
457            BACnetTimeStamp::SequenceNumber(3),
458        ];
459        av.event_history.message_texts = ["offnormal".into(), "fault".into(), "normal".into()];
460
461        av.write_property(
462            PropertyIdentifier::EVENT_DETECTION_ENABLE,
463            None,
464            PropertyValue::Boolean(false),
465            None,
466        )
467        .unwrap();
468
469        assert_eq!(
470            av.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
471                .unwrap(),
472            PropertyValue::Boolean(false)
473        );
474        assert_eq!(
475            av.event_detector.event_state,
476            bacnet_types::enums::EventState::NORMAL
477        );
478        assert_eq!(av.event_detector.acked_transitions, 0b111);
479        assert!(av.event_detector.pending.is_none());
480        assert!(av.event_detector.fault_reliability.is_none());
481        assert_eq!(
482            av.event_history.time_stamps,
483            [
484                BACnetTimeStamp::SequenceNumber(0),
485                BACnetTimeStamp::SequenceNumber(0),
486                BACnetTimeStamp::SequenceNumber(0),
487            ]
488        );
489        assert_eq!(
490            av.event_history.message_texts,
491            [String::new(), String::new(), String::new()]
492        );
493    }
494}
495
496#[cfg(test)]
497mod fault_out_of_range_non_finite_tests {
498    use super::*;
499    use bacnet_types::enums::{ErrorClass, ErrorCode, Reliability};
500
501    fn assert_value_out_of_range(result: Result<ReliabilityEvaluation, Error>) {
502        assert!(matches!(
503            result,
504            Err(Error::Protocol { class, code })
505                if class == ErrorClass::PROPERTY.to_raw() as u32
506                    && code == ErrorCode::VALUE_OUT_OF_RANGE.to_raw() as u32
507        ));
508    }
509
510    #[test]
511    fn av_non_finite_monitored_values_preserve_reliability_status_and_ownership() {
512        for non_finite in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
513            let mut normal = AnalogValueObject::new(1, "AV-normal", 62).unwrap();
514            normal.configure_fault_out_of_range(10.0, 20.0).unwrap();
515            let status_before = normal
516                .read_property(PropertyIdentifier::STATUS_FLAGS, None)
517                .unwrap();
518            normal.present_value = non_finite;
519
520            assert_value_out_of_range(normal.evaluate_reliability_internal());
521            assert_eq!(normal.present_value.to_bits(), non_finite.to_bits());
522            assert_eq!(normal.reliability, Reliability::NO_FAULT_DETECTED.to_raw());
523            assert_eq!(
524                normal
525                    .read_property(PropertyIdentifier::STATUS_FLAGS, None)
526                    .unwrap(),
527                status_before
528            );
529            assert!(normal.fault_out_of_range.owned_fault.is_none());
530
531            normal.present_value = 9.0;
532            assert_eq!(
533                normal.evaluate_reliability_internal().unwrap(),
534                ReliabilityEvaluation::Changed {
535                    old_reliability: Reliability::NO_FAULT_DETECTED.to_raw(),
536                    new_reliability: Reliability::UNDER_RANGE.to_raw(),
537                }
538            );
539
540            let mut owned = AnalogValueObject::new(2, "AV-owned", 62).unwrap();
541            owned.configure_fault_out_of_range(10.0, 20.0).unwrap();
542            owned.present_value = 9.0;
543            owned.evaluate_reliability_internal().unwrap();
544            let status_before = owned
545                .read_property(PropertyIdentifier::STATUS_FLAGS, None)
546                .unwrap();
547            owned.present_value = non_finite;
548
549            assert_value_out_of_range(owned.evaluate_reliability_internal());
550            assert_eq!(owned.present_value.to_bits(), non_finite.to_bits());
551            assert_eq!(owned.reliability, Reliability::UNDER_RANGE.to_raw());
552            assert_eq!(
553                owned
554                    .read_property(PropertyIdentifier::STATUS_FLAGS, None)
555                    .unwrap(),
556                status_before
557            );
558            assert!(matches!(
559                owned.fault_out_of_range.owned_fault,
560                Some(OwnedRangeFault::UnderRange)
561            ));
562
563            owned.present_value = 21.0;
564            assert_eq!(
565                owned.evaluate_reliability_internal().unwrap(),
566                ReliabilityEvaluation::Changed {
567                    old_reliability: Reliability::UNDER_RANGE.to_raw(),
568                    new_reliability: Reliability::OVER_RANGE.to_raw(),
569                }
570            );
571        }
572    }
573}