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