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};
6use crate::present_value_access::PresentValueAccess;
7use crate::property_metadata::PropertyMetadata;
8
9mod metadata;
10#[cfg(test)]
11mod nonfinite_tests;
12
13// ---------------------------------------------------------------------------
14// AnalogValue (type 2)
15// ---------------------------------------------------------------------------
16
17/// BACnet Analog Value object.
18pub struct AnalogValueObject {
19    audit_policy: crate::audit::ObjectAuditPolicy,
20    oid: ObjectIdentifier,
21    name: String,
22    description: String,
23    present_value: f32,
24    units: u32,
25    out_of_service: bool,
26    status_flags: StatusFlags,
27    access: PresentValueAccess,
28    /// 16-level priority array. `None` = no command at that level.
29    priority_array: [Option<f32>; 16],
30    relinquish_default: f32,
31    /// COV_Increment: minimum change threshold for COV notifications.
32    /// Default 0.0 means notify on any write (including no-change).
33    /// Set to a positive value for delta-based filtering.
34    cov_increment: f32,
35    event_detector: OutOfRangeDetector,
36    /// Event_Detection_Enable (Clause 12.4). A FALSE value suspends
37    /// event-state-machine evaluation under Clause 13.2.2.1.
38    event_detection_enable: bool,
39    /// Reliability; NO_FAULT_DETECTED until a fault is evaluated or simulated.
40    reliability: Reliability,
41    reliability_before_out_of_service: Option<Reliability>,
42    reliability_inhibit: common::ReliabilityInhibitState,
43    fault_out_of_range: FaultOutOfRangeState,
44    min_pres_value: Option<f32>,
45    max_pres_value: Option<f32>,
46    pub(crate) event_history: EventHistory,
47    /// Value source tracking.
48    value_source: crate::command_source::ValueSourceTracking,
49}
50
51impl AnalogValueObject {
52    /// Provision independently optional Audit properties before registration.
53    /// Raw object configuration bypasses server notification ownership; use
54    /// BACnetServer::write_local or WP/WPM for live property mutations.
55    pub fn set_audit_policy(&mut self, policy: crate::audit::ObjectAuditPolicy) {
56        self.audit_policy = policy;
57    }
58
59    /// Create a new Analog Value object with a commandable Present_Value.
60    pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
61        Self::with_access(instance, name, units, PresentValueAccess::Commandable)
62    }
63
64    /// Create a new Analog Value object whose Present_Value is written as `access` says.
65    pub fn with_access(
66        instance: u32,
67        name: impl Into<String>,
68        units: u32,
69        access: PresentValueAccess,
70    ) -> Result<Self, Error> {
71        let oid = ObjectIdentifier::new(ObjectType::ANALOG_VALUE, instance)?;
72        Ok(Self {
73            audit_policy: crate::audit::ObjectAuditPolicy::default(),
74            oid,
75            name: name.into(),
76            description: String::new(),
77            present_value: 0.0,
78            units,
79            out_of_service: false,
80            status_flags: StatusFlags::empty(),
81            access,
82            priority_array: [None; 16],
83            relinquish_default: 0.0,
84            cov_increment: 0.0,
85            event_detector: OutOfRangeDetector::default(),
86            event_detection_enable: true,
87            reliability: Reliability::NO_FAULT_DETECTED,
88            reliability_before_out_of_service: None,
89            reliability_inhibit: common::ReliabilityInhibitState::default(),
90            fault_out_of_range: FaultOutOfRangeState::default(),
91            min_pres_value: None,
92            max_pres_value: None,
93            event_history: EventHistory::default(),
94            value_source: crate::command_source::ValueSourceTracking::default(),
95        })
96    }
97
98    /// Set the description string.
99    pub fn set_description(&mut self, desc: impl Into<String>) {
100        self.description = desc.into();
101    }
102
103    /// Set minimum engineering-bound metadata; this is not a reliability fault limit.
104    pub fn set_min_pres_value(&mut self, value: f32) {
105        self.min_pres_value = Some(value);
106    }
107
108    /// Set maximum engineering-bound metadata; this is not a reliability fault limit.
109    pub fn set_max_pres_value(&mut self, value: f32) {
110        self.max_pres_value = Some(value);
111    }
112
113    /// Configure the optional object-owned FAULT_OUT_OF_RANGE algorithm.
114    ///
115    /// Both limits become readable together. Equal limits are valid; non-finite
116    /// limits and a low limit greater than the high limit are rejected without
117    /// changing the prior configuration.
118    pub fn configure_fault_out_of_range(&mut self, low: f32, high: f32) -> Result<(), Error> {
119        self.fault_out_of_range.configure(low, high)
120    }
121
122    /// Recalculate present-value from the priority array.
123    fn recalculate_present_value(&mut self) {
124        self.present_value =
125            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
126    }
127
128    /// Set the Relinquish_Default (#270).
129    ///
130    /// Validated the same way a commanded Present_Value is (finite Real);
131    /// after the store, Present_Value is resolved anew from the priority
132    /// array so an empty array falls back to the new default immediately.
133    pub fn set_relinquish_default(&mut self, value: f32) -> Result<(), Error> {
134        if self.access != PresentValueAccess::Commandable {
135            return Err(common::unknown_property_error());
136        }
137        common::reject_non_finite(value)?;
138        self.relinquish_default = value;
139        self.recalculate_present_value();
140        Ok(())
141    }
142
143    fn checked_present_value(value: PropertyValue) -> Result<f32, Error> {
144        let PropertyValue::Real(v) = value else {
145            return Err(common::invalid_data_type_error());
146        };
147        if !v.is_finite() {
148            return Err(common::value_out_of_range_error());
149        }
150        Ok(v)
151    }
152}
153
154impl BACnetObject for AnalogValueObject {
155    fn audit_policy_authority_internal(
156        &mut self,
157    ) -> Option<crate::audit::AuditPolicyAuthority<'_>> {
158        Some(crate::audit::AuditPolicyAuthority::new(
159            &mut self.audit_policy,
160        ))
161    }
162
163    fn audit_object_policy_internal(&self) -> crate::audit::ObjectAuditPolicy {
164        self.audit_policy
165    }
166
167    fn object_identifier(&self) -> ObjectIdentifier {
168        self.oid
169    }
170
171    fn object_name(&self) -> &str {
172        &self.name
173    }
174
175    fn read_property(
176        &self,
177        property: PropertyIdentifier,
178        array_index: Option<u32>,
179    ) -> Result<PropertyValue, Error> {
180        if metadata::excludes(self, property) {
181            return Err(common::unknown_property_error());
182        }
183        if let Some(result) = self
184            .value_source
185            .read(property, array_index, &self.priority_array)
186        {
187            return result;
188        }
189
190        if let Some(result) = self.audit_policy.read(property, array_index) {
191            return result;
192        }
193        if property == PropertyIdentifier::STATUS_FLAGS {
194            return Ok(common::compute_status_flags(
195                self.status_flags,
196                self.reliability,
197                self.out_of_service,
198                self.event_detector.event_state,
199            ));
200        }
201        if let Some(value) = self.reliability_inhibit.read(property) {
202            return Ok(value);
203        }
204        if let Some(result) = read_common_properties!(self, property, array_index) {
205            return result;
206        }
207        if let Some(result) = read_analog_event_properties!(self, property) {
208            return result;
209        }
210        if let Some(result) = self.event_history.read(property, array_index) {
211            return result;
212        }
213        if let Some(result) = read_generic_event_properties!(self, property) {
214            return result;
215        }
216        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
217            return Ok(PropertyValue::Boolean(self.event_detection_enable));
218        }
219        if let Some(value) = self.fault_out_of_range.read_limit(property) {
220            return Ok(value);
221        }
222        match property {
223            p if p == PropertyIdentifier::OBJECT_TYPE => {
224                Ok(PropertyValue::Enumerated(ObjectType::ANALOG_VALUE.to_raw()))
225            }
226            p if p == PropertyIdentifier::PRESENT_VALUE => {
227                Ok(PropertyValue::Real(self.present_value))
228            }
229            p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
230            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
231                common::read_priority_array!(self, array_index, PropertyValue::Real)
232            }
233            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
234                Ok(PropertyValue::Real(self.relinquish_default))
235            }
236            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
237                Ok(common::current_command_priority(&self.priority_array))
238            }
239            p if p == PropertyIdentifier::COV_INCREMENT => {
240                Ok(PropertyValue::Real(self.cov_increment))
241            }
242            p if p == PropertyIdentifier::MIN_PRES_VALUE => match self.min_pres_value {
243                Some(v) => Ok(PropertyValue::Real(v)),
244                None => Err(common::unknown_property_error()),
245            },
246            p if p == PropertyIdentifier::MAX_PRES_VALUE => match self.max_pres_value {
247                Some(v) => Ok(PropertyValue::Real(v)),
248                None => Err(common::unknown_property_error()),
249            },
250            _ => Err(common::unknown_property_error()),
251        }
252    }
253
254    fn write_property_from(
255        &mut self,
256        property: PropertyIdentifier,
257        array_index: Option<u32>,
258        value: PropertyValue,
259        priority: Option<u8>,
260        origin: &crate::command_source::CommandOrigin,
261    ) -> Result<(), Error> {
262        if matches!(
263            property,
264            PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
265        ) && array_index.is_some()
266        {
267            return Err(common::property_is_not_an_array_error());
268        }
269        if property == PropertyIdentifier::VALUE_SOURCE
270            && self.access == PresentValueAccess::Commandable
271        {
272            return self.value_source.correct(value, priority, origin);
273        }
274        if property == PropertyIdentifier::PRESENT_VALUE {
275            return match self.access {
276                PresentValueAccess::Commandable => {
277                    crate::command_source::write_sourced_priority!(
278                        self,
279                        value,
280                        priority,
281                        origin,
282                        Self::checked_present_value
283                    )
284                }
285                PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
286                    self.write_property(property, array_index, value, priority)
287                }
288            };
289        }
290        self.write_property(property, array_index, value, priority)
291    }
292
293    fn write_property(
294        &mut self,
295        property: PropertyIdentifier,
296        array_index: Option<u32>,
297        value: PropertyValue,
298        priority: Option<u8>,
299    ) -> Result<(), Error> {
300        if metadata::excludes(self, property) {
301            return Err(common::unknown_property_error());
302        }
303        if let Some(result) = self
304            .audit_policy
305            .write(property, array_index, &value, priority)
306        {
307            return result;
308        }
309
310        if property == PropertyIdentifier::PRESENT_VALUE {
311            if self.access == PresentValueAccess::Commandable {
312                return Err(common::write_access_denied_error());
313            }
314            if array_index.is_some() {
315                return Err(common::property_is_not_an_array_error());
316            }
317            if self.access == PresentValueAccess::ReadOnly && !self.out_of_service {
318                return Err(common::write_access_denied_error());
319            }
320            // Clause 19.2: an otherwise permitted noncommandable NULL is a no-op.
321            if value == PropertyValue::Null {
322                return Ok(());
323            }
324            self.present_value = Self::checked_present_value(value)?;
325            return Ok(());
326        }
327        if property == PropertyIdentifier::VALUE_SOURCE {
328            return Err(common::write_access_denied_error());
329        }
330        if let Some(result) = self.reliability_inhibit.write_inhibit(
331            &mut self.reliability,
332            self.out_of_service,
333            property,
334            &value,
335        ) {
336            return result;
337        }
338        if let Some(result) = self.reliability_inhibit.write_out_of_service(
339            &mut self.out_of_service,
340            &mut self.reliability,
341            &mut self.reliability_before_out_of_service,
342            property,
343            &value,
344        ) {
345            return result.map(|_| ());
346        }
347        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
348            return result;
349        }
350        if let Some(result) = common::write_description(&mut self.description, property, &value) {
351            return result;
352        }
353        // Clause 12.4 requires simulation/test writes while Out_Of_Service is TRUE:
354        // Present_Value is writable, as is Reliability when that property exists
355        // and supports values beyond NO_FAULT_DETECTED.
356        // `is_writable_property` stays statically true because it describes capability.
357        if let Some(result) = self.reliability_inhibit.write_client_reliability(
358            self.out_of_service,
359            &mut self.reliability,
360            property,
361            &value,
362        ) {
363            return result;
364        }
365        if property == PropertyIdentifier::RELINQUISH_DEFAULT {
366            if let PropertyValue::Real(v) = value {
367                return self.set_relinquish_default(v);
368            }
369            return Err(common::invalid_data_type_error());
370        }
371        if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
372        {
373            return result;
374        }
375        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
376            if let PropertyValue::Boolean(v) = value {
377                self.event_detection_enable = v;
378                if !v {
379                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
380                    self.event_detector.acked_transitions =
381                        bacnet_types::bitstring::EventTransitionBits::all();
382                    self.event_detector.pending = None;
383                    self.event_detector.fault_reliability = None;
384                    self.event_history.reset();
385                }
386                return Ok(());
387            }
388            return Err(common::invalid_data_type_error());
389        }
390        if let Some(result) = write_analog_event_properties!(self, property, value) {
391            return result;
392        }
393        // Event_Message_Texts_Config and the Event_Algorithm_Inhibit pair (#1329).
394        if let Some(result) =
395            self.event_history
396                .write(property, array_index, &value, self.event_detection_enable)
397        {
398            return result;
399        }
400        if let Some(result) = write_generic_event_properties!(self, property, value) {
401            return result;
402        }
403        Err(crate::common::unhandled_write_error(
404            self.property_metadata().as_ref(),
405            property,
406            array_index,
407        ))
408    }
409
410    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
411        metadata::for_object(self)
412    }
413
414    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
415        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
416    }
417
418    fn supports_cov(&self) -> bool {
419        true
420    }
421
422    fn cov_increment(&self) -> Option<f64> {
423        Some(f64::from(self.cov_increment))
424    }
425
426    crate::event::impl_builtin_intrinsic_reporting!(
427        event_detector,
428        event_history,
429        [present_value],
430        reliability,
431        event_detection_enable,
432        OutOfRangeDetector::ALGORITHM
433    );
434
435    fn acknowledge_alarm(
436        &mut self,
437        transition_bit: bacnet_types::bitstring::EventTransitionBits,
438    ) -> Result<(), bacnet_types::error::Error> {
439        self.event_detector.acked_transitions |=
440            transition_bit & bacnet_types::bitstring::EventTransitionBits::all();
441        Ok(())
442    }
443
444    fn acknowledge_alarm_correlated_internal(
445        &mut self,
446        event_state: EventState,
447        timestamp: &BACnetTimeStamp,
448    ) -> Result<(), Error> {
449        if !self.event_detection_enable {
450            return Err(Error::Protocol {
451                class: ErrorClass::OBJECT.to_raw() as u32,
452                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
453            });
454        }
455        self.event_history.acknowledge_correlated(
456            &mut self.event_detector.acked_transitions,
457            event_state,
458            timestamp,
459        )
460    }
461
462    fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
463        if self.out_of_service || self.reliability_inhibit.enabled() {
464            return Err(common::write_access_denied_error());
465        }
466        if !common::is_reliability_value_valid(reliability) {
467            return Err(common::value_out_of_range_error());
468        }
469        self.reliability = reliability;
470        self.fault_out_of_range.clear_ownership();
471        Ok(())
472    }
473
474    fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
475        match self.access {
476            PresentValueAccess::Commandable => {
477                Err(common::optional_functionality_not_supported_error())
478            }
479            _ if self.out_of_service => Err(common::write_access_denied_error()),
480            PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
481                self.present_value = Self::checked_present_value(value)?;
482                Ok(())
483            }
484        }
485    }
486
487    fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
488        if self.out_of_service || self.reliability_inhibit.enabled() {
489            return Ok(ReliabilityEvaluation::Unchanged);
490        }
491        self.fault_out_of_range
492            .evaluate(self.present_value, &mut self.reliability)
493    }
494
495    fn reliability_evaluation_inhibited_internal(&self) -> bool {
496        self.reliability_inhibit.enabled()
497    }
498
499    /// AnalogValue is NOT createable: `handle_create_object` has no branch for
500    /// it, so PICS must not advertise createability the runtime rejects.
501    fn is_createable(&self) -> bool {
502        false
503    }
504}
505
506#[cfg(test)]
507mod detection_enable_reset_tests {
508    use super::*;
509
510    /// Regression guard for issue #123: once transitions populate timestamps and messages,
511    /// disabling event detection must still restore their Clause 13.2.2.1 initial conditions.
512    #[test]
513    fn av_disabling_detection_resets_event_history() {
514        let mut av = AnalogValueObject::new(1, "AV-1", 62).unwrap();
515        assert_eq!(
516            av.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
517                .unwrap(),
518            PropertyValue::Boolean(true)
519        );
520        av.event_detector.event_state = bacnet_types::enums::EventState::HIGH_LIMIT;
521        av.event_detector.acked_transitions = bacnet_types::bitstring::EventTransitionBits::empty();
522        av.event_detector.pending = Some(crate::event::PendingTransition {
523            state: bacnet_types::enums::EventState::HIGH_LIMIT,
524            remaining: 2,
525        });
526        av.event_detector.fault_reliability = Some(bacnet_types::enums::Reliability::NO_SENSOR);
527        av.event_history.time_stamps = [
528            BACnetTimeStamp::SequenceNumber(1),
529            BACnetTimeStamp::SequenceNumber(2),
530            BACnetTimeStamp::SequenceNumber(3),
531        ];
532        av.event_history.message_texts = ["offnormal".into(), "fault".into(), "normal".into()];
533
534        av.write_property(
535            PropertyIdentifier::EVENT_DETECTION_ENABLE,
536            None,
537            PropertyValue::Boolean(false),
538            None,
539        )
540        .unwrap();
541
542        assert_eq!(
543            av.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
544                .unwrap(),
545            PropertyValue::Boolean(false)
546        );
547        assert_eq!(
548            av.event_detector.event_state,
549            bacnet_types::enums::EventState::NORMAL
550        );
551        assert_eq!(
552            av.event_detector.acked_transitions,
553            bacnet_types::bitstring::EventTransitionBits::all()
554        );
555        assert!(av.event_detector.pending.is_none());
556        assert!(av.event_detector.fault_reliability.is_none());
557        assert_eq!(
558            av.event_history.time_stamps,
559            [
560                BACnetTimeStamp::SequenceNumber(0),
561                BACnetTimeStamp::SequenceNumber(0),
562                BACnetTimeStamp::SequenceNumber(0),
563            ]
564        );
565        assert_eq!(
566            av.event_history.message_texts,
567            [String::new(), String::new(), String::new()]
568        );
569    }
570}
571
572#[cfg(test)]
573mod fault_out_of_range_non_finite_tests {
574    use super::*;
575    use bacnet_types::enums::{ErrorClass, ErrorCode, Reliability};
576
577    fn assert_value_out_of_range(result: Result<ReliabilityEvaluation, Error>) {
578        assert!(matches!(
579            result,
580            Err(Error::Protocol { class, code })
581                if class == ErrorClass::PROPERTY.to_raw() as u32
582                    && code == ErrorCode::VALUE_OUT_OF_RANGE.to_raw() as u32
583        ));
584    }
585
586    #[test]
587    fn av_non_finite_monitored_values_preserve_reliability_status_and_ownership() {
588        for non_finite in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
589            let mut normal = AnalogValueObject::new(1, "AV-normal", 62).unwrap();
590            normal.configure_fault_out_of_range(10.0, 20.0).unwrap();
591            let status_before = normal
592                .read_property(PropertyIdentifier::STATUS_FLAGS, None)
593                .unwrap();
594            normal.present_value = non_finite;
595
596            assert_value_out_of_range(normal.evaluate_reliability_internal());
597            assert_eq!(normal.present_value.to_bits(), non_finite.to_bits());
598            assert_eq!(normal.reliability, Reliability::NO_FAULT_DETECTED);
599            assert_eq!(
600                normal
601                    .read_property(PropertyIdentifier::STATUS_FLAGS, None)
602                    .unwrap(),
603                status_before
604            );
605            assert!(normal.fault_out_of_range.owned_fault.is_none());
606
607            normal.present_value = 9.0;
608            assert_eq!(
609                normal.evaluate_reliability_internal().unwrap(),
610                ReliabilityEvaluation::Changed {
611                    old_reliability: Reliability::NO_FAULT_DETECTED,
612                    new_reliability: Reliability::UNDER_RANGE,
613                }
614            );
615
616            let mut owned = AnalogValueObject::new(2, "AV-owned", 62).unwrap();
617            owned.configure_fault_out_of_range(10.0, 20.0).unwrap();
618            owned.present_value = 9.0;
619            owned.evaluate_reliability_internal().unwrap();
620            let status_before = owned
621                .read_property(PropertyIdentifier::STATUS_FLAGS, None)
622                .unwrap();
623            owned.present_value = non_finite;
624
625            assert_value_out_of_range(owned.evaluate_reliability_internal());
626            assert_eq!(owned.present_value.to_bits(), non_finite.to_bits());
627            assert_eq!(owned.reliability, Reliability::UNDER_RANGE);
628            assert_eq!(
629                owned
630                    .read_property(PropertyIdentifier::STATUS_FLAGS, None)
631                    .unwrap(),
632                status_before
633            );
634            assert!(matches!(
635                owned.fault_out_of_range.owned_fault,
636                Some(OwnedRangeFault::UnderRange)
637            ));
638
639            owned.present_value = 21.0;
640            assert_eq!(
641                owned.evaluate_reliability_internal().unwrap(),
642                ReliabilityEvaluation::Changed {
643                    old_reliability: Reliability::UNDER_RANGE,
644                    new_reliability: Reliability::OVER_RANGE,
645                }
646            );
647        }
648    }
649}