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
12pub 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: f32,
30 event_detector: OutOfRangeDetector,
31 event_detection_enable: bool,
34 reliability: Reliability,
36 reliability_before_out_of_service: Option<Reliability>,
37 reliability_inhibit: common::ReliabilityInhibitState,
38 fault_out_of_range: FaultOutOfRangeState,
39 min_pres_value: Option<f32>,
41 max_pres_value: Option<f32>,
43 pub(crate) event_history: EventHistory,
44}
45
46impl AnalogInputObject {
47 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 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 pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
91 self.profile.provision(profile)
92 }
93
94 pub fn wait_for_tag_saves(&self) {
97 self.profile.wait_for_saves();
98 }
99
100 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 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 pub fn set_description(&mut self, desc: impl Into<String>) {
130 self.description = desc.into();
131 }
132
133 pub fn set_min_pres_value(&mut self, value: f32) {
135 self.min_pres_value = Some(value);
136 }
137
138 pub fn set_max_pres_value(&mut self, value: f32) {
140 self.max_pres_value = Some(value);
141 }
142
143 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 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 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 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 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 #[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}