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