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