Skip to main content

bacnet_objects/analog/
input.rs

1use super::*;
2use crate::common::{
3    read_analog_event_properties, read_generic_event_properties, write_analog_event_properties,
4    write_generic_event_properties,
5};
6
7// ---------------------------------------------------------------------------
8// AnalogInput (type 0)
9// ---------------------------------------------------------------------------
10
11/// BACnet Analog Input object.
12pub struct AnalogInputObject {
13    oid: ObjectIdentifier,
14    name: String,
15    description: String,
16    present_value: f32,
17    units: u32,
18    out_of_service: bool,
19    status_flags: StatusFlags,
20    /// COV_Increment: minimum change threshold for COV notifications.
21    /// Default 0.0 means notify on any write (including no-change).
22    /// Set to a positive value for delta-based filtering.
23    cov_increment: f32,
24    event_detector: OutOfRangeDetector,
25    /// Event_Detection_Enable (Clause 12.2). Clause 13.2.2.1: "If the
26    /// Event_Detection_Enable property is FALSE, then this state machine is not evaluated."
27    event_detection_enable: bool,
28    /// Reliability: 0 = NO_FAULT_DETECTED.
29    reliability: u32,
30    reliability_before_out_of_service: Option<u32>,
31    reliability_inhibit: common::ReliabilityInhibitState,
32    fault_out_of_range: FaultOutOfRangeState,
33    /// Optional minimum engineering bound metadata for Present_Value.
34    min_pres_value: Option<f32>,
35    /// Optional maximum engineering bound metadata for Present_Value.
36    max_pres_value: Option<f32>,
37    pub(crate) event_history: EventHistory,
38}
39
40impl AnalogInputObject {
41    /// Create a new Analog Input object.
42    pub fn new(instance: u32, name: impl Into<String>, units: u32) -> Result<Self, Error> {
43        let _oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, instance)?;
44        Ok(Self {
45            oid: _oid,
46            name: name.into(),
47            description: String::new(),
48            present_value: 0.0,
49            units,
50            out_of_service: false,
51            status_flags: StatusFlags::empty(),
52            cov_increment: 0.0,
53            event_detector: OutOfRangeDetector::default(),
54            event_detection_enable: true,
55            reliability: 0,
56            reliability_before_out_of_service: None,
57            reliability_inhibit: common::ReliabilityInhibitState::default(),
58            fault_out_of_range: FaultOutOfRangeState::default(),
59            min_pres_value: None,
60            max_pres_value: None,
61            event_history: EventHistory::default(),
62        })
63    }
64
65    /// Mutate `Present_Value` on an unattached or otherwise application-owned object.
66    ///
67    /// This low-level helper bypasses running-server `Out_Of_Service` ownership,
68    /// validation, intrinsic-event processing, and COV processing. Applications
69    /// updating a live object should use `BACnetServer::set_present_value_local`.
70    pub fn set_present_value(&mut self, value: f32) {
71        debug_assert!(
72            value.is_finite(),
73            "set_present_value called with non-finite value"
74        );
75        self.present_value = value;
76    }
77
78    /// Validate and store a `Present_Value` write, without any access check.
79    ///
80    /// Shared by the network and internal routes, which differ only in the
81    /// `Out_Of_Service` condition each requires.
82    fn apply_present_value(&mut self, value: PropertyValue) -> Result<(), Error> {
83        let PropertyValue::Real(v) = value else {
84            return Err(common::invalid_data_type_error());
85        };
86        if !v.is_finite() {
87            return Err(common::value_out_of_range_error());
88        }
89        self.present_value = v;
90        Ok(())
91    }
92
93    /// Set the description string.
94    pub fn set_description(&mut self, desc: impl Into<String>) {
95        self.description = desc.into();
96    }
97
98    /// Set minimum engineering-bound metadata; this is not a reliability fault limit.
99    pub fn set_min_pres_value(&mut self, value: f32) {
100        self.min_pres_value = Some(value);
101    }
102
103    /// Set maximum engineering-bound metadata; this is not a reliability fault limit.
104    pub fn set_max_pres_value(&mut self, value: f32) {
105        self.max_pres_value = Some(value);
106    }
107
108    /// Configure the optional object-owned FAULT_OUT_OF_RANGE algorithm.
109    ///
110    /// Both limits become readable together. Equal limits are valid; non-finite
111    /// limits and a low limit greater than the high limit are rejected without
112    /// changing the prior configuration.
113    pub fn configure_fault_out_of_range(&mut self, low: f32, high: f32) -> Result<(), Error> {
114        self.fault_out_of_range.configure(low, high)
115    }
116}
117
118impl BACnetObject for AnalogInputObject {
119    fn object_identifier(&self) -> ObjectIdentifier {
120        self.oid
121    }
122
123    fn object_name(&self) -> &str {
124        &self.name
125    }
126
127    fn read_property(
128        &self,
129        property: PropertyIdentifier,
130        array_index: Option<u32>,
131    ) -> Result<PropertyValue, Error> {
132        // IN_ALARM: override STATUS_FLAGS with event_state before common macro
133        if property == PropertyIdentifier::STATUS_FLAGS {
134            return Ok(common::compute_status_flags(
135                self.status_flags,
136                self.reliability,
137                self.out_of_service,
138                self.event_detector.event_state.to_raw(),
139            ));
140        }
141        if let Some(value) = self.reliability_inhibit.read(property) {
142            return Ok(value);
143        }
144        if let Some(result) = read_common_properties!(self, property, array_index) {
145            return result;
146        }
147        if let Some(result) = read_analog_event_properties!(self, property) {
148            return result;
149        }
150        if let Some(result) = self.event_history.read(property, array_index) {
151            return result;
152        }
153        if let Some(result) = read_generic_event_properties!(self, property) {
154            return result;
155        }
156        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
157            return Ok(PropertyValue::Boolean(self.event_detection_enable));
158        }
159        if let Some(value) = self.fault_out_of_range.read_limit(property) {
160            return Ok(value);
161        }
162        match property {
163            p if p == PropertyIdentifier::OBJECT_TYPE => {
164                Ok(PropertyValue::Enumerated(ObjectType::ANALOG_INPUT.to_raw()))
165            }
166            p if p == PropertyIdentifier::PRESENT_VALUE => {
167                Ok(PropertyValue::Real(self.present_value))
168            }
169            p if p == PropertyIdentifier::UNITS => Ok(PropertyValue::Enumerated(self.units)),
170            p if p == PropertyIdentifier::COV_INCREMENT => {
171                Ok(PropertyValue::Real(self.cov_increment))
172            }
173            p if p == PropertyIdentifier::MIN_PRES_VALUE => match self.min_pres_value {
174                Some(v) => Ok(PropertyValue::Real(v)),
175                None => Err(common::unknown_property_error()),
176            },
177            p if p == PropertyIdentifier::MAX_PRES_VALUE => match self.max_pres_value {
178                Some(v) => Ok(PropertyValue::Real(v)),
179                None => Err(common::unknown_property_error()),
180            },
181            _ => Err(common::unknown_property_error()),
182        }
183    }
184
185    fn write_property(
186        &mut self,
187        property: PropertyIdentifier,
188        _array_index: Option<u32>,
189        value: PropertyValue,
190        _priority: Option<u8>,
191    ) -> Result<(), Error> {
192        // AI present-value is writable only when out-of-service
193        if property == PropertyIdentifier::PRESENT_VALUE {
194            if !self.out_of_service {
195                return Err(common::write_access_denied_error());
196            }
197            return self.apply_present_value(value);
198        }
199        if let Some(result) = self.reliability_inhibit.write_inhibit(
200            &mut self.reliability,
201            self.out_of_service,
202            property,
203            &value,
204        ) {
205            return result;
206        }
207        if let Some(result) = self.reliability_inhibit.write_out_of_service(
208            &mut self.out_of_service,
209            &mut self.reliability,
210            &mut self.reliability_before_out_of_service,
211            property,
212            &value,
213        ) {
214            return result;
215        }
216        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
217            return result;
218        }
219        if let Some(result) = common::write_description(&mut self.description, property, &value) {
220            return result;
221        }
222        if let Some(result) = self.reliability_inhibit.write_client_reliability(
223            self.out_of_service,
224            &mut self.reliability,
225            property,
226            &value,
227        ) {
228            return result;
229        }
230        if let Some(result) = common::write_cov_increment(&mut self.cov_increment, property, &value)
231        {
232            return result;
233        }
234        if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
235            if let PropertyValue::Boolean(v) = value {
236                self.event_detection_enable = v;
237                if !v {
238                    self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
239                    self.event_detector.acked_transitions = 0b111;
240                    self.event_detector.pending = None;
241                    self.event_detector.fault_reliability = None;
242                    self.event_history.reset();
243                }
244                return Ok(());
245            }
246            return Err(common::invalid_data_type_error());
247        }
248        if let Some(result) = write_analog_event_properties!(self, property, value) {
249            return result;
250        }
251        if let Some(result) = write_generic_event_properties!(self, property, value) {
252            return result;
253        }
254        Err(common::write_access_denied_error())
255    }
256
257    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
258        static PROPS: &[PropertyIdentifier] = &[
259            PropertyIdentifier::OBJECT_IDENTIFIER,
260            PropertyIdentifier::OBJECT_NAME,
261            PropertyIdentifier::DESCRIPTION,
262            PropertyIdentifier::OBJECT_TYPE,
263            PropertyIdentifier::PRESENT_VALUE,
264            PropertyIdentifier::STATUS_FLAGS,
265            PropertyIdentifier::EVENT_STATE,
266            PropertyIdentifier::EVENT_DETECTION_ENABLE,
267            PropertyIdentifier::OUT_OF_SERVICE,
268            PropertyIdentifier::UNITS,
269            PropertyIdentifier::COV_INCREMENT,
270            PropertyIdentifier::HIGH_LIMIT,
271            PropertyIdentifier::LOW_LIMIT,
272            PropertyIdentifier::DEADBAND,
273            PropertyIdentifier::LIMIT_ENABLE,
274            PropertyIdentifier::EVENT_ENABLE,
275            PropertyIdentifier::NOTIFY_TYPE,
276            PropertyIdentifier::NOTIFICATION_CLASS,
277            PropertyIdentifier::TIME_DELAY,
278            PropertyIdentifier::TIME_DELAY_NORMAL,
279            PropertyIdentifier::RELIABILITY,
280            PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT,
281            PropertyIdentifier::ACKED_TRANSITIONS,
282            PropertyIdentifier::EVENT_TIME_STAMPS,
283            PropertyIdentifier::EVENT_MESSAGE_TEXTS,
284        ];
285        self.fault_out_of_range.property_list(PROPS)
286    }
287
288    fn supports_cov(&self) -> bool {
289        true
290    }
291
292    fn cov_increment(&self) -> Option<f32> {
293        Some(self.cov_increment)
294    }
295
296    crate::event::impl_builtin_intrinsic_reporting!(
297        event_detector,
298        event_history,
299        [present_value],
300        reliability,
301        event_detection_enable,
302        OutOfRangeDetector::ALGORITHM
303    );
304    impl_intrinsic_write_rollback!(
305        event_detector,
306        event_detection_enable,
307        event_history,
308        reliability_inhibit,
309        reliability,
310        out_of_service,
311        reliability_before_out_of_service,
312        fault_out_of_range
313    );
314
315    fn acknowledge_alarm(&mut self, transition_bit: u8) -> Result<(), bacnet_types::error::Error> {
316        self.event_detector.acked_transitions |= transition_bit & 0x07;
317        Ok(())
318    }
319
320    fn acknowledge_alarm_correlated_internal(
321        &mut self,
322        event_state: EventState,
323        timestamp: &BACnetTimeStamp,
324    ) -> Result<(), Error> {
325        if !self.event_detection_enable {
326            return Err(Error::Protocol {
327                class: ErrorClass::OBJECT.to_raw() as u32,
328                code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
329            });
330        }
331        self.event_history.acknowledge_correlated(
332            &mut self.event_detector.acked_transitions,
333            event_state,
334            timestamp,
335        )
336    }
337
338    fn set_reliability_internal(&mut self, reliability: u32) -> Result<(), Error> {
339        if self.out_of_service || self.reliability_inhibit.enabled() {
340            return Err(common::write_access_denied_error());
341        }
342        if !common::is_reliability_value_valid(reliability) {
343            return Err(common::value_out_of_range_error());
344        }
345        self.reliability = reliability;
346        self.fault_out_of_range.clear_ownership();
347        Ok(())
348    }
349
350    fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
351        // Local safe-ownership policy: preserve the client's OOS simulation.
352        if self.out_of_service {
353            return Err(common::write_access_denied_error());
354        }
355        self.apply_present_value(value)
356    }
357
358    fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
359        if self.out_of_service || self.reliability_inhibit.enabled() {
360            return Ok(ReliabilityEvaluation::Unchanged);
361        }
362        self.fault_out_of_range
363            .evaluate(self.present_value, &mut self.reliability)
364    }
365
366    fn reliability_evaluation_inhibited_internal(&self) -> bool {
367        self.reliability_inhibit.enabled()
368    }
369
370    fn is_createable(&self) -> bool {
371        true
372    }
373
374    fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
375        // Mirrors the AnalogInput `write_property` arms.
376        common::is_common_writable(property)
377            || property == PropertyIdentifier::PRESENT_VALUE
378            || property == PropertyIdentifier::RELIABILITY
379            || property == PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT
380            || property == PropertyIdentifier::COV_INCREMENT
381            || common::is_event_property_writable(property)
382            || property == PropertyIdentifier::EVENT_DETECTION_ENABLE
383    }
384}
385
386#[cfg(test)]
387mod detection_enable_reset_tests {
388    use super::*;
389
390    #[test]
391    fn built_in_intrinsic_transition_is_a_proposal_until_committed() {
392        use crate::event::EventTransitionCommit;
393
394        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
395        ai.event_detector.high_limit = 80.0;
396        ai.event_detector.limit_enable = crate::event::LimitEnable::BOTH;
397        ai.event_detector.event_enable = 0x07;
398        ai.set_present_value(81.0);
399
400        let outcome = ai
401            .evaluate_intrinsic_reporting()
402            .expect("out-of-range value should propose TO_OFFNORMAL");
403        assert_eq!(
404            outcome.change.to,
405            bacnet_types::enums::EventState::HIGH_LIMIT
406        );
407        assert_eq!(
408            ai.event_detector.event_state,
409            bacnet_types::enums::EventState::NORMAL,
410            "built-in evaluation must not confirm its own proposal"
411        );
412        assert_eq!(ai.event_detector.acked_transitions, 0b111);
413        assert!(ai.event_detector.pending.is_none());
414
415        ai.commit_event_transition_internal(EventTransitionCommit {
416            coordinate: outcome.change.transition(),
417            change: outcome.change,
418            ack_required: true,
419            timestamp: BACnetTimeStamp::SequenceNumber(41),
420            message_text: None,
421        })
422        .expect("built-in object should lend all transition state to the kernel");
423
424        assert_eq!(
425            ai.event_detector.event_state,
426            bacnet_types::enums::EventState::HIGH_LIMIT
427        );
428        assert_eq!(ai.event_detector.acked_transitions, 0b110);
429        assert_eq!(
430            ai.event_history.time_stamps[0],
431            BACnetTimeStamp::SequenceNumber(41)
432        );
433    }
434
435    #[test]
436    fn rejected_delayed_and_fault_reindication_proposals_remain_retryable() {
437        use crate::event::{
438            EventStateChange, EventTransition, EventTransitionCommit, EventTransitionCommitError,
439        };
440        use bacnet_types::enums::{EventState, Reliability};
441
442        let mut delayed = AnalogInputObject::new(1, "AI-delayed", 62).unwrap();
443        delayed.event_detector.high_limit = 80.0;
444        delayed.event_detector.limit_enable = crate::event::LimitEnable::BOTH;
445        delayed.event_detector.time_delay = 1;
446        delayed.set_present_value(81.0);
447        assert_eq!(delayed.evaluate_intrinsic_reporting(), None);
448        let proposal = delayed.tick_intrinsic_reporting().unwrap();
449        assert_eq!(
450            delayed.event_detector.pending.as_ref().unwrap().remaining,
451            1
452        );
453
454        let stale = EventTransitionCommit {
455            change: EventStateChange {
456                from: EventState::FAULT,
457                to: proposal.change.to,
458            },
459            coordinate: proposal.change.transition(),
460            ack_required: false,
461            timestamp: BACnetTimeStamp::SequenceNumber(9),
462            message_text: None,
463        };
464        assert_eq!(
465            delayed.commit_event_transition_internal(stale),
466            Err(EventTransitionCommitError::CurrentStateMismatch {
467                expected: EventState::FAULT,
468                actual: EventState::NORMAL,
469            })
470        );
471        assert_eq!(
472            delayed.event_detector.pending.as_ref().unwrap().remaining,
473            1
474        );
475        assert_eq!(delayed.tick_intrinsic_reporting(), Some(proposal));
476
477        let mut faulted = AnalogInputObject::new(2, "AI-fault", 62).unwrap();
478        faulted.reliability = Reliability::OVER_RANGE.to_raw();
479        let entry = faulted.evaluate_intrinsic_reporting().unwrap();
480        crate::event::commit_test_proposal(&mut faulted, entry);
481        faulted.reliability = Reliability::NO_SENSOR.to_raw();
482        let reindication = faulted.evaluate_intrinsic_reporting().unwrap();
483        assert_eq!(
484            reindication.change,
485            EventStateChange {
486                from: EventState::FAULT,
487                to: EventState::FAULT,
488            }
489        );
490        assert_eq!(
491            faulted.commit_event_transition_internal(EventTransitionCommit {
492                change: reindication.change.clone(),
493                coordinate: EventTransition::ToNormal,
494                ack_required: false,
495                timestamp: BACnetTimeStamp::SequenceNumber(10),
496                message_text: None,
497            }),
498            Err(EventTransitionCommitError::CoordinateTargetMismatch {
499                coordinate: EventTransition::ToNormal,
500                target: EventState::FAULT,
501            })
502        );
503        assert_eq!(
504            faulted.event_detector.fault_reliability,
505            Some(Reliability::OVER_RANGE.to_raw())
506        );
507        assert_eq!(faulted.evaluate_intrinsic_reporting(), Some(reindication));
508    }
509
510    /// Regression guard for issue #123: once transitions populate timestamps and messages,
511    /// disabling event detection must still restore their Clause 13.2.2.1 initial conditions.
512    #[test]
513    fn ai_disabling_detection_resets_event_history() {
514        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
515        assert_eq!(
516            ai.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
517                .unwrap(),
518            PropertyValue::Boolean(true)
519        );
520        ai.event_detector.event_state = bacnet_types::enums::EventState::HIGH_LIMIT;
521        ai.event_detector.acked_transitions = 0;
522        ai.event_detector.pending = Some(crate::event::PendingTransition {
523            state: bacnet_types::enums::EventState::HIGH_LIMIT,
524            remaining: 2,
525        });
526        ai.event_detector.fault_reliability = Some(1);
527        ai.event_history.time_stamps = [
528            BACnetTimeStamp::SequenceNumber(1),
529            BACnetTimeStamp::SequenceNumber(2),
530            BACnetTimeStamp::SequenceNumber(3),
531        ];
532        ai.event_history.message_texts = ["offnormal".into(), "fault".into(), "normal".into()];
533
534        ai.write_property(
535            PropertyIdentifier::EVENT_DETECTION_ENABLE,
536            None,
537            PropertyValue::Boolean(false),
538            None,
539        )
540        .unwrap();
541
542        assert_eq!(
543            ai.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
544                .unwrap(),
545            PropertyValue::Boolean(false)
546        );
547        assert_eq!(
548            ai.event_detector.event_state,
549            bacnet_types::enums::EventState::NORMAL
550        );
551        assert_eq!(ai.event_detector.acked_transitions, 0b111);
552        assert!(ai.event_detector.pending.is_none());
553        assert!(ai.event_detector.fault_reliability.is_none());
554        assert_eq!(
555            ai.event_history.time_stamps,
556            [
557                BACnetTimeStamp::SequenceNumber(0),
558                BACnetTimeStamp::SequenceNumber(0),
559                BACnetTimeStamp::SequenceNumber(0),
560            ]
561        );
562        assert_eq!(ai.event_history.original_from_states, [None, None, None]);
563        assert_eq!(ai.event_history.original_to_states, [None, None, None]);
564        assert_eq!(
565            ai.event_history.message_texts,
566            [String::new(), String::new(), String::new()]
567        );
568    }
569
570    #[test]
571    fn ai_write_rollback_restores_detection_state() {
572        let mut ai = AnalogInputObject::new(1, "AI-1", 62).unwrap();
573        ai.event_detector.event_state = bacnet_types::enums::EventState::HIGH_LIMIT;
574        ai.event_detector.acked_transitions = 0b010;
575        ai.event_detector.pending = Some(crate::event::PendingTransition {
576            state: bacnet_types::enums::EventState::NORMAL,
577            remaining: 2,
578        });
579        ai.event_detector.fault_reliability = Some(1);
580        ai.event_history.time_stamps[0] = BACnetTimeStamp::SequenceNumber(7);
581        ai.event_history.original_from_states[0] = Some(EventState::NORMAL);
582        ai.event_history.original_to_states[0] = Some(EventState::HIGH_LIMIT);
583        ai.event_history.message_texts[0] = "offnormal".into();
584        let rollback = ai
585            .capture_write_property_rollback(
586                PropertyIdentifier::EVENT_DETECTION_ENABLE,
587                &PropertyValue::Boolean(false),
588            )
589            .unwrap();
590
591        ai.write_property(
592            PropertyIdentifier::EVENT_DETECTION_ENABLE,
593            None,
594            PropertyValue::Boolean(false),
595            None,
596        )
597        .unwrap();
598        ai.restore_write_property_rollback(rollback).unwrap();
599
600        assert!(ai.event_detection_enable);
601        assert_eq!(
602            ai.event_detector.event_state,
603            bacnet_types::enums::EventState::HIGH_LIMIT
604        );
605        assert_eq!(ai.event_detector.acked_transitions, 0b010);
606        assert_eq!(ai.event_detector.pending.unwrap().remaining, 2);
607        assert_eq!(ai.event_detector.fault_reliability, Some(1));
608        assert_eq!(
609            ai.event_history.time_stamps[0],
610            BACnetTimeStamp::SequenceNumber(7)
611        );
612        assert_eq!(
613            ai.event_history.original_from_states[0],
614            Some(EventState::NORMAL)
615        );
616        assert_eq!(
617            ai.event_history.original_to_states[0],
618            Some(EventState::HIGH_LIMIT)
619        );
620        assert_eq!(ai.event_history.message_texts[0], "offnormal");
621    }
622}
623
624#[cfg(test)]
625mod acknowledge_alarm_correlation_tests;
626
627#[cfg(test)]
628mod fault_out_of_range_non_finite_tests {
629    use super::*;
630    use bacnet_types::enums::{ErrorClass, ErrorCode, Reliability};
631
632    fn assert_value_out_of_range(result: Result<ReliabilityEvaluation, Error>) {
633        assert!(matches!(
634            result,
635            Err(Error::Protocol { class, code })
636                if class == ErrorClass::PROPERTY.to_raw() as u32
637                    && code == ErrorCode::VALUE_OUT_OF_RANGE.to_raw() as u32
638        ));
639    }
640
641    #[test]
642    fn ai_non_finite_monitored_values_preserve_reliability_status_and_ownership() {
643        for non_finite in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
644            let mut normal = AnalogInputObject::new(1, "AI-normal", 62).unwrap();
645            normal.configure_fault_out_of_range(10.0, 20.0).unwrap();
646            let status_before = normal
647                .read_property(PropertyIdentifier::STATUS_FLAGS, None)
648                .unwrap();
649            normal.present_value = non_finite;
650
651            assert_value_out_of_range(normal.evaluate_reliability_internal());
652            assert_eq!(normal.present_value.to_bits(), non_finite.to_bits());
653            assert_eq!(normal.reliability, Reliability::NO_FAULT_DETECTED.to_raw());
654            assert_eq!(
655                normal
656                    .read_property(PropertyIdentifier::STATUS_FLAGS, None)
657                    .unwrap(),
658                status_before
659            );
660            assert!(normal.fault_out_of_range.owned_fault.is_none());
661
662            normal.present_value = 9.0;
663            assert_eq!(
664                normal.evaluate_reliability_internal().unwrap(),
665                ReliabilityEvaluation::Changed {
666                    old_reliability: Reliability::NO_FAULT_DETECTED.to_raw(),
667                    new_reliability: Reliability::UNDER_RANGE.to_raw(),
668                }
669            );
670
671            let mut owned = AnalogInputObject::new(2, "AI-owned", 62).unwrap();
672            owned.configure_fault_out_of_range(10.0, 20.0).unwrap();
673            owned.present_value = 9.0;
674            owned.evaluate_reliability_internal().unwrap();
675            let status_before = owned
676                .read_property(PropertyIdentifier::STATUS_FLAGS, None)
677                .unwrap();
678            owned.present_value = non_finite;
679
680            assert_value_out_of_range(owned.evaluate_reliability_internal());
681            assert_eq!(owned.present_value.to_bits(), non_finite.to_bits());
682            assert_eq!(owned.reliability, Reliability::UNDER_RANGE.to_raw());
683            assert_eq!(
684                owned
685                    .read_property(PropertyIdentifier::STATUS_FLAGS, None)
686                    .unwrap(),
687                status_before
688            );
689            assert!(matches!(
690                owned.fault_out_of_range.owned_fault,
691                Some(OwnedRangeFault::UnderRange)
692            ));
693
694            owned.present_value = 21.0;
695            assert_eq!(
696                owned.evaluate_reliability_internal().unwrap(),
697                ReliabilityEvaluation::Changed {
698                    old_reliability: Reliability::UNDER_RANGE.to_raw(),
699                    new_reliability: Reliability::OVER_RANGE.to_raw(),
700                }
701            );
702        }
703    }
704}