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