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