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
10pub 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: f32,
27 event_detector: OutOfRangeDetector,
28 event_detection_enable: bool,
31 reliability: Reliability,
33 reliability_before_out_of_service: Option<Reliability>,
34 reliability_inhibit: common::ReliabilityInhibitState,
35 fault_out_of_range: FaultOutOfRangeState,
36 min_pres_value: Option<f32>,
38 max_pres_value: Option<f32>,
40 pub(crate) event_history: EventHistory,
41}
42
43impl AnalogInputObject {
44 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 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 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 pub fn set_description(&mut self, desc: impl Into<String>) {
98 self.description = desc.into();
99 }
100
101 pub fn set_min_pres_value(&mut self, value: f32) {
103 self.min_pres_value = Some(value);
104 }
105
106 pub fn set_max_pres_value(&mut self, value: f32) {
108 self.max_pres_value = Some(value);
109 }
110
111 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 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 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 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 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 #[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}