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