1use super::*;
2use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
3use crate::present_value_access::PresentValueAccess;
4use crate::property_metadata::PropertyMetadata;
5use std::sync::Arc;
6
7mod metadata;
8
9pub struct MultiStateValueObject {
17 profile: ProfileState,
18 oid: ObjectIdentifier,
19 name: String,
20 description: String,
21 present_value: u32,
22 number_of_states: u32,
23 out_of_service: bool,
24 status_flags: StatusFlags,
25 access: PresentValueAccess,
26 priority_array: [Option<u32>; 16],
27 relinquish_default: u32,
28 reliability: Reliability,
30 reliability_before_out_of_service: Option<Reliability>,
31 reliability_inhibit: common::ReliabilityInhibitState,
32 reliability_evaluator: MultiStateReliabilityState,
33 state_text: Vec<String>,
34 event_detector: ChangeOfStateDetector,
36 event_detection_enable: bool,
39 pub(crate) event_history: EventHistory,
40 value_source: crate::command_source::ValueSourceTracking,
42 write_source: crate::command_source::SingleValueSource,
44}
45
46impl MultiStateValueObject {
47 pub fn new(
49 instance: u32,
50 name: impl Into<String>,
51 number_of_states: u32,
52 ) -> Result<Self, Error> {
53 Self::with_access(
54 instance,
55 name,
56 number_of_states,
57 PresentValueAccess::Commandable,
58 )
59 }
60
61 pub fn with_access(
63 instance: u32,
64 name: impl Into<String>,
65 number_of_states: u32,
66 access: PresentValueAccess,
67 ) -> Result<Self, Error> {
68 let oid = ObjectIdentifier::new(ObjectType::MULTI_STATE_VALUE, instance)?;
69 require_nonzero_states(number_of_states)?;
70 Ok(Self {
71 profile: ProfileState::default(),
72 oid,
73 name: name.into(),
74 description: String::new(),
75 present_value: 1,
76 number_of_states,
77 out_of_service: false,
78 status_flags: StatusFlags::empty(),
79 access,
80 priority_array: [None; 16],
81 relinquish_default: 1,
82 reliability: Reliability::NO_FAULT_DETECTED,
83 reliability_before_out_of_service: None,
84 reliability_inhibit: common::ReliabilityInhibitState::default(),
85 reliability_evaluator: MultiStateReliabilityState::default(),
86 state_text: (1..=number_of_states)
87 .map(|i| format!("State {i}"))
88 .collect(),
89 event_detector: ChangeOfStateDetector::default(),
90 event_detection_enable: true,
91 event_history: EventHistory::default(),
92 value_source: crate::command_source::ValueSourceTracking::default(),
93 write_source: crate::command_source::SingleValueSource::default(),
94 })
95 }
96
97 pub fn with_tags_persistence(
102 instance: u32,
103 name: impl Into<String>,
104 number_of_states: u32,
105 access: PresentValueAccess,
106 persistence: Arc<dyn TagsPersistence>,
107 ) -> Result<Self, Error> {
108 let mut object = Self::with_access(instance, name, number_of_states, access)?;
109 object.profile = ProfileState::persistent(object.oid, persistence)?;
110 Ok(object)
111 }
112
113 pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
118 self.profile.provision(profile)
119 }
120
121 pub fn wait_for_tag_saves(&self) {
124 self.profile.wait_for_saves();
125 }
126
127 pub fn set_value_source_tracking(&mut self, enabled: bool) {
132 self.write_source.set_enabled(enabled);
133 }
134
135 pub fn set_description(&mut self, desc: impl Into<String>) {
137 self.description = desc.into();
138 }
139
140 fn recalculate_present_value(&mut self) {
141 self.present_value =
142 common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
143 let _ = self.recompute_reliability();
144 }
145
146 fn configuration_invalid(&self) -> bool {
147 let is_invalid = |value: u32| !(1..=self.number_of_states).contains(&value);
148 self.priority_array
149 .iter()
150 .flatten()
151 .copied()
152 .any(is_invalid)
153 || is_invalid(self.relinquish_default)
154 || self
155 .event_detector
156 .alarm_values
157 .iter()
158 .copied()
159 .any(is_invalid)
160 }
161
162 fn recompute_reliability(&mut self) -> ReliabilityEvaluation {
163 if self.out_of_service || self.reliability_inhibit.enabled() {
164 return ReliabilityEvaluation::Unchanged;
165 }
166 let configuration_invalid = self.configuration_invalid();
167 self.reliability_evaluator.evaluate(
168 configuration_invalid,
169 self.present_value,
170 self.number_of_states,
171 &mut self.reliability,
172 )
173 }
174
175 pub fn set_number_of_states(&mut self, number_of_states: u32) -> Result<(), Error> {
181 resize_state_text(
182 &mut self.number_of_states,
183 &mut self.state_text,
184 number_of_states,
185 )?;
186 let _ = self.recompute_reliability();
187 Ok(())
188 }
189
190 pub fn set_alarm_values(&mut self, values: Vec<u32>) {
196 self.event_detector.alarm_values = values;
197 let _ = self.recompute_reliability();
198 }
199
200 pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
214 if self.access != PresentValueAccess::Commandable {
215 return Err(common::unknown_property_error());
216 }
217 if value < 1 || value > self.number_of_states {
218 return Err(common::value_out_of_range_error());
219 }
220 self.relinquish_default = value;
221 self.recalculate_present_value();
222 Ok(())
223 }
224
225 fn checked_present_value(number_of_states: u32, value: PropertyValue) -> Result<u32, Error> {
226 let PropertyValue::Unsigned(u) = value else {
227 return Err(common::invalid_data_type_error());
228 };
229 if u < 1 || u > u64::from(number_of_states) {
230 return Err(common::value_out_of_range_error());
231 }
232 Ok(u as u32)
233 }
234
235 fn held_states(&self) -> impl Iterator<Item = u32> + '_ {
239 self.priority_array
240 .iter()
241 .flatten()
242 .copied()
243 .chain([self.present_value, self.relinquish_default])
244 .chain(self.event_detector.alarm_values.iter().copied())
245 }
246
247 fn write_direct_present_value(
249 &mut self,
250 array_index: Option<u32>,
251 value: PropertyValue,
252 ) -> Result<(), Error> {
253 if array_index.is_some() {
254 return Err(common::property_is_not_an_array_error());
255 }
256 if self.access == PresentValueAccess::ReadOnly && !self.out_of_service {
257 return Err(common::write_access_denied_error());
258 }
259 if value == PropertyValue::Null {
261 return Ok(());
262 }
263 self.set_present_value_directly(value)
264 }
265
266 fn set_present_value_directly(&mut self, value: PropertyValue) -> Result<(), Error> {
267 self.present_value = Self::checked_present_value(self.number_of_states, value)?;
268 let _ = self.recompute_reliability();
269 Ok(())
270 }
271}
272
273impl BACnetObject for MultiStateValueObject {
274 fn object_identifier(&self) -> ObjectIdentifier {
275 self.oid
276 }
277
278 fn object_name(&self) -> &str {
279 &self.name
280 }
281
282 fn supports_cov(&self) -> bool {
283 true
284 }
285
286 crate::event::impl_builtin_intrinsic_reporting!(
287 event_detector,
288 event_history,
289 [present_value],
290 reliability,
291 event_detection_enable,
292 ChangeOfStateDetector::ALGORITHM
293 );
294
295 fn acknowledge_alarm_correlated_internal(
296 &mut self,
297 event_state: EventState,
298 timestamp: &BACnetTimeStamp,
299 ) -> Result<(), Error> {
300 if !self.event_detection_enable {
301 return Err(Error::Protocol {
302 class: ErrorClass::OBJECT.to_raw() as u32,
303 code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
304 });
305 }
306 self.event_history.acknowledge_correlated(
307 &mut self.event_detector.acked_transitions,
308 event_state,
309 timestamp,
310 )
311 }
312
313 fn read_property(
314 &self,
315 property: PropertyIdentifier,
316 array_index: Option<u32>,
317 ) -> Result<PropertyValue, Error> {
318 if metadata::excludes(self, property) {
319 return Err(common::unknown_property_error());
320 }
321 let source = match self.access {
322 PresentValueAccess::Commandable => {
323 self.value_source
324 .read(property, array_index, &self.priority_array)
325 }
326 _ => self.write_source.read(property, array_index),
327 };
328 if let Some(result) = source {
329 return result;
330 }
331 if let Some(result) = self.profile.read(property, array_index) {
332 return result;
333 }
334
335 if property == PropertyIdentifier::STATUS_FLAGS {
336 return Ok(common::compute_status_flags(
337 self.status_flags,
338 self.reliability,
339 self.out_of_service,
340 self.event_detector.event_state,
341 ));
342 }
343 if let Some(value) = self.reliability_inhibit.read(property) {
344 return Ok(value);
345 }
346 if let Some(result) = read_common_properties!(self, property, array_index) {
347 return result;
348 }
349 if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
350 return Ok(PropertyValue::Boolean(self.event_detection_enable));
351 }
352 if let Some(result) = read_generic_event_properties!(self, property) {
353 return result;
354 }
355 if let Some(result) = self.event_history.read(property, array_index) {
356 return result;
357 }
358 match property {
359 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
360 ObjectType::MULTI_STATE_VALUE.to_raw(),
361 )),
362 p if p == PropertyIdentifier::PRESENT_VALUE => {
363 Ok(PropertyValue::Unsigned(self.present_value as u64))
364 }
365 p if p == PropertyIdentifier::NUMBER_OF_STATES => {
366 Ok(PropertyValue::Unsigned(self.number_of_states as u64))
367 }
368 p if p == PropertyIdentifier::PRIORITY_ARRAY => {
369 common::read_priority_array!(self, array_index, |v: u32| PropertyValue::Unsigned(
370 v as u64
371 ))
372 }
373 p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
374 Ok(PropertyValue::Unsigned(self.relinquish_default as u64))
375 }
376 p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
377 Ok(common::current_command_priority(&self.priority_array))
378 }
379 p if p == PropertyIdentifier::STATE_TEXT => match array_index {
380 None => Ok(PropertyValue::List(
381 self.state_text
382 .iter()
383 .map(|s| PropertyValue::CharacterString(s.clone()))
384 .collect(),
385 )),
386 Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
387 Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
388 PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
389 ),
390 _ => Err(common::invalid_array_index_error()),
391 },
392 p if p == PropertyIdentifier::ALARM_VALUES => Ok(PropertyValue::List(
393 self.event_detector
394 .alarm_values
395 .iter()
396 .map(|v| PropertyValue::Unsigned(*v as u64))
397 .collect(),
398 )),
399 _ => Err(common::unknown_property_error()),
400 }
401 }
402
403 fn write_property_from(
404 &mut self,
405 property: PropertyIdentifier,
406 array_index: Option<u32>,
407 value: PropertyValue,
408 priority: Option<u8>,
409 origin: &crate::command_source::CommandOrigin,
410 ) -> Result<(), Error> {
411 if matches!(
412 property,
413 PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
414 ) && array_index.is_some()
415 {
416 return Err(common::property_is_not_an_array_error());
417 }
418 if property == PropertyIdentifier::VALUE_SOURCE {
419 return match self.access {
420 PresentValueAccess::Commandable => {
421 self.value_source.correct(value, priority, origin)
422 }
423 _ => self.write_source.correct(array_index, value, origin),
424 };
425 }
426 if property == PropertyIdentifier::PRESENT_VALUE {
427 let number_of_states = self.number_of_states;
428 return match self.access {
429 PresentValueAccess::Commandable => {
430 crate::command_source::write_sourced_priority!(
431 self,
432 value,
433 priority,
434 origin,
435 |v| Self::checked_present_value(number_of_states, v)
436 )
437 }
438 PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
439 self.write_source.admit(origin)?;
440 let null = value == PropertyValue::Null;
442 self.write_direct_present_value(array_index, value)?;
443 if !null {
444 self.write_source.record(origin);
445 }
446 Ok(())
447 }
448 };
449 }
450 self.write_property(property, array_index, value, priority)
451 }
452
453 fn write_property(
454 &mut self,
455 property: PropertyIdentifier,
456 array_index: Option<u32>,
457 value: PropertyValue,
458 _priority: Option<u8>,
459 ) -> Result<(), Error> {
460 if metadata::excludes(self, property) {
461 return Err(common::unknown_property_error());
462 }
463 if let Some(result) = self.profile.write(property, array_index, &value) {
464 return result;
465 }
466 if property == PropertyIdentifier::PRESENT_VALUE {
467 if self.access == PresentValueAccess::Commandable {
468 return Err(common::write_access_denied_error());
469 }
470 self.write_source.unsourced()?;
472 return self.write_direct_present_value(array_index, value);
473 }
474 if property == PropertyIdentifier::VALUE_SOURCE {
475 return Err(common::write_access_denied_error());
476 }
477 if property == PropertyIdentifier::STATE_TEXT {
478 let held: Vec<u32> = self.held_states().collect();
480 write_state_text(
481 &mut self.number_of_states,
482 &mut self.state_text,
483 held,
484 array_index,
485 value,
486 )?;
487 let _ = self.recompute_reliability();
488 return Ok(());
489 }
490 if property == PropertyIdentifier::ALARM_VALUES {
491 let values = decode_alarm_values_write(array_index, value, self.number_of_states)?;
492 self.set_alarm_values(values);
493 return Ok(());
494 }
495 if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
496 if let PropertyValue::Boolean(v) = value {
497 self.event_detection_enable = v;
498 if !v {
499 self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
500 self.event_detector.acked_transitions =
501 bacnet_types::bitstring::EventTransitionBits::all();
502 self.event_detector.pending = None;
503 self.event_detector.fault_reliability = None;
504 self.event_history.reset();
505 }
506 return Ok(());
507 }
508 return Err(common::invalid_data_type_error());
509 }
510 if let Some(result) =
512 self.event_history
513 .write(property, array_index, &value, self.event_detection_enable)
514 {
515 return result;
516 }
517 if let Some(result) = write_generic_event_properties!(self, property, value) {
518 return result;
519 }
520 if let Some(result) = self.reliability_inhibit.write_inhibit(
521 &mut self.reliability,
522 self.out_of_service,
523 property,
524 &value,
525 ) {
526 result?;
527 let _ = self.recompute_reliability();
528 return Ok(());
529 }
530 if let Some(result) = self.reliability_inhibit.write_out_of_service(
531 &mut self.out_of_service,
532 &mut self.reliability,
533 &mut self.reliability_before_out_of_service,
534 property,
535 &value,
536 ) {
537 if result? == crate::reliability_inhibit::OutOfServiceWrite::Applied {
538 let _ = self.recompute_reliability();
539 }
540 return Ok(());
541 }
542 if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
543 return result;
544 }
545 if let Some(result) = common::write_description(&mut self.description, property, &value) {
546 return result;
547 }
548 if let Some(result) = self.reliability_inhibit.write_client_reliability(
553 self.out_of_service,
554 &mut self.reliability,
555 property,
556 &value,
557 ) {
558 return result;
559 }
560 if property == PropertyIdentifier::RELINQUISH_DEFAULT {
561 if let PropertyValue::Unsigned(u) = value {
562 let v = common::u64_to_u32(u)?;
563 return self.set_relinquish_default(v);
564 }
565 return Err(common::invalid_data_type_error());
566 }
567 Err(crate::common::unhandled_write_error(
568 self.property_metadata().as_ref(),
569 property,
570 array_index,
571 ))
572 }
573
574 fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
575 self.profile.capability()
576 }
577
578 fn advance_monotonic_time_internal(&mut self, now: std::time::Duration) -> bool {
579 self.profile.expire(now);
580 false
581 }
582
583 fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
584 metadata::for_object(self)
585 }
586
587 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
588 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
589 }
590
591 fn is_createable(&self) -> bool {
592 true
593 }
594 fn creation_only_properties(&self) -> &'static [PropertyIdentifier] {
595 CREATION_ONLY
596 }
597 fn initialize_property(
598 &mut self,
599 property: PropertyIdentifier,
600 value: PropertyValue,
601 ) -> Result<(), Error> {
602 let held: Vec<u32> = self.held_states().collect();
603 initialize_states(
604 &mut self.number_of_states,
605 &mut self.state_text,
606 held,
607 property,
608 value,
609 )?;
610 let _ = self.recompute_reliability();
611 Ok(())
612 }
613 fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
614 if self.out_of_service || self.reliability_inhibit.enabled() {
615 return Err(common::write_access_denied_error());
616 }
617 if !common::is_reliability_value_valid(reliability) {
618 return Err(common::value_out_of_range_error());
619 }
620 self.reliability = reliability;
621 self.reliability_evaluator.clear_ownership();
622 Ok(())
623 }
624
625 fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
626 Ok(self.recompute_reliability())
627 }
628
629 fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
630 match self.access {
631 PresentValueAccess::Commandable => {
632 Err(common::optional_functionality_not_supported_error())
633 }
634 _ if self.out_of_service => Err(common::write_access_denied_error()),
635 PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
636 self.set_present_value_directly(value)?;
637 self.write_source.forget();
638 Ok(())
639 }
640 }
641 }
642
643 fn set_present_value_from_internal(
644 &mut self,
645 value: PropertyValue,
646 origin: &crate::command_source::CommandOrigin,
647 ) -> Result<(), Error> {
648 self.write_source.admit(origin)?;
649 self.set_present_value_internal(value)?;
650 self.write_source.record(origin);
651 Ok(())
652 }
653
654 fn reliability_evaluation_inhibited_internal(&self) -> bool {
655 self.reliability_inhibit.enabled()
656 }
657}
658
659#[cfg(test)]
660mod detection_enable_tests {
661 use super::*;
662
663 #[test]
664 fn msv_detection_enable_resets_and_gates_intrinsic_reporting() {
665 let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
666 assert_eq!(
667 msv.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
668 .unwrap(),
669 PropertyValue::Boolean(true)
670 );
671 msv.event_detector.alarm_values = vec![2];
672 msv.event_detector.time_delay = 2;
673 msv.write_property_from(
674 PropertyIdentifier::PRESENT_VALUE,
675 None,
676 PropertyValue::Unsigned(2),
677 Some(8),
678 &crate::command_source::test_origin(),
679 )
680 .unwrap();
681 assert_eq!(msv.evaluate_intrinsic_reporting(), None);
682 assert!(msv.event_detector.pending.is_some());
683
684 msv.event_detector.event_state = bacnet_types::enums::EventState::OFFNORMAL;
685 msv.event_detector.acked_transitions =
686 bacnet_types::bitstring::EventTransitionBits::empty();
687 msv.event_detector.fault_reliability = Some(bacnet_types::enums::Reliability::NO_SENSOR);
688 msv.write_property(
689 PropertyIdentifier::EVENT_DETECTION_ENABLE,
690 None,
691 PropertyValue::Boolean(false),
692 None,
693 )
694 .unwrap();
695
696 assert_eq!(
697 msv.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
698 .unwrap(),
699 PropertyValue::Boolean(false)
700 );
701 assert_eq!(
702 msv.read_property(PropertyIdentifier::EVENT_STATE, None)
703 .unwrap(),
704 PropertyValue::Enumerated(bacnet_types::enums::EventState::NORMAL.to_raw())
705 );
706 assert_eq!(
707 msv.read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
708 .unwrap(),
709 PropertyValue::BitString {
710 unused_bits: 5,
711 data: vec![0xe0],
712 }
713 );
714 assert!(msv.event_detector.pending.is_none());
715 assert!(msv.event_detector.fault_reliability.is_none());
716 assert_eq!(msv.evaluate_intrinsic_reporting(), None);
717 assert_eq!(msv.tick_intrinsic_reporting(), None);
718 assert!(
719 msv.event_detector.pending.is_none(),
720 "evaluate/tick re-armed a countdown while detection is disabled"
721 );
722 assert!(msv
723 .property_list()
724 .contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
725 assert!(msv.is_writable_property(PropertyIdentifier::EVENT_DETECTION_ENABLE));
726 }
727}
728
729#[cfg(test)]
730mod reliability_evaluator_tests {
731 use super::*;
732 use bacnet_types::enums::Reliability;
733
734 #[test]
735 fn configuration_error_dominates_bypassed_invalid_present_value() {
736 let mut msv = MultiStateValueObject::new(1, "MSV-dominance", 2).unwrap();
737 msv.present_value = 3;
738 msv.event_detector.alarm_values = vec![3];
739
740 msv.evaluate_reliability_internal().unwrap();
741 assert_eq!(
742 msv.reliability,
743 Reliability::CONFIGURATION_ERROR,
744 "invalid configuration must dominate invalid Present_Value"
745 );
746 msv.event_detector.alarm_values = vec![1];
747 msv.evaluate_reliability_internal().unwrap();
748 assert_eq!(msv.reliability, Reliability::MULTI_STATE_OUT_OF_RANGE);
749 msv.set_number_of_states(3).unwrap();
750 assert_eq!(
751 msv.reliability,
752 Reliability::NO_FAULT_DETECTED,
753 "count growth must synchronously recover the retained Present_Value"
754 );
755 }
756}