1use super::*;
2use crate::present_value_access::PresentValueAccess;
3use crate::property_metadata::PropertyMetadata;
4
5mod metadata;
6
7pub struct MultiStateValueObject {
15 oid: ObjectIdentifier,
16 name: String,
17 description: String,
18 present_value: u32,
19 number_of_states: u32,
20 out_of_service: bool,
21 status_flags: StatusFlags,
22 access: PresentValueAccess,
23 priority_array: [Option<u32>; 16],
24 relinquish_default: u32,
25 reliability: Reliability,
27 reliability_before_out_of_service: Option<Reliability>,
28 reliability_inhibit: common::ReliabilityInhibitState,
29 reliability_evaluator: MultiStateReliabilityState,
30 state_text: Vec<String>,
31 event_detector: ChangeOfStateDetector,
33 event_detection_enable: bool,
36 pub(crate) event_history: EventHistory,
37 value_source: crate::command_source::ValueSourceTracking,
39}
40
41impl MultiStateValueObject {
42 pub fn new(
44 instance: u32,
45 name: impl Into<String>,
46 number_of_states: u32,
47 ) -> Result<Self, Error> {
48 Self::with_access(
49 instance,
50 name,
51 number_of_states,
52 PresentValueAccess::Commandable,
53 )
54 }
55
56 pub fn with_access(
58 instance: u32,
59 name: impl Into<String>,
60 number_of_states: u32,
61 access: PresentValueAccess,
62 ) -> Result<Self, Error> {
63 let oid = ObjectIdentifier::new(ObjectType::MULTI_STATE_VALUE, instance)?;
64 require_nonzero_states(number_of_states)?;
65 Ok(Self {
66 oid,
67 name: name.into(),
68 description: String::new(),
69 present_value: 1,
70 number_of_states,
71 out_of_service: false,
72 status_flags: StatusFlags::empty(),
73 access,
74 priority_array: [None; 16],
75 relinquish_default: 1,
76 reliability: Reliability::NO_FAULT_DETECTED,
77 reliability_before_out_of_service: None,
78 reliability_inhibit: common::ReliabilityInhibitState::default(),
79 reliability_evaluator: MultiStateReliabilityState::default(),
80 state_text: (1..=number_of_states)
81 .map(|i| format!("State {i}"))
82 .collect(),
83 event_detector: ChangeOfStateDetector::default(),
84 event_detection_enable: true,
85 event_history: EventHistory::default(),
86 value_source: crate::command_source::ValueSourceTracking::default(),
87 })
88 }
89
90 pub fn set_description(&mut self, desc: impl Into<String>) {
92 self.description = desc.into();
93 }
94
95 fn recalculate_present_value(&mut self) {
96 self.present_value =
97 common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
98 let _ = self.recompute_reliability();
99 }
100
101 fn configuration_invalid(&self) -> bool {
102 let is_invalid = |value: u32| !(1..=self.number_of_states).contains(&value);
103 self.priority_array
104 .iter()
105 .flatten()
106 .copied()
107 .any(is_invalid)
108 || is_invalid(self.relinquish_default)
109 || self
110 .event_detector
111 .alarm_values
112 .iter()
113 .copied()
114 .any(is_invalid)
115 }
116
117 fn recompute_reliability(&mut self) -> ReliabilityEvaluation {
118 if self.out_of_service || self.reliability_inhibit.enabled() {
119 return ReliabilityEvaluation::Unchanged;
120 }
121 let configuration_invalid = self.configuration_invalid();
122 self.reliability_evaluator.evaluate(
123 configuration_invalid,
124 self.present_value,
125 self.number_of_states,
126 &mut self.reliability,
127 )
128 }
129
130 pub fn set_number_of_states(&mut self, number_of_states: u32) -> Result<(), Error> {
136 resize_state_text(
137 &mut self.number_of_states,
138 &mut self.state_text,
139 number_of_states,
140 )?;
141 let _ = self.recompute_reliability();
142 Ok(())
143 }
144
145 pub fn set_alarm_values(&mut self, values: Vec<u32>) {
151 self.event_detector.alarm_values = values;
152 let _ = self.recompute_reliability();
153 }
154
155 pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
169 if self.access != PresentValueAccess::Commandable {
170 return Err(common::unknown_property_error());
171 }
172 if value < 1 || value > self.number_of_states {
173 return Err(common::value_out_of_range_error());
174 }
175 self.relinquish_default = value;
176 self.recalculate_present_value();
177 Ok(())
178 }
179
180 fn checked_present_value(number_of_states: u32, value: PropertyValue) -> Result<u32, Error> {
181 let PropertyValue::Unsigned(u) = value else {
182 return Err(common::invalid_data_type_error());
183 };
184 if u < 1 || u > u64::from(number_of_states) {
185 return Err(common::value_out_of_range_error());
186 }
187 Ok(u as u32)
188 }
189
190 fn held_states(&self) -> impl Iterator<Item = u32> + '_ {
194 self.priority_array
195 .iter()
196 .flatten()
197 .copied()
198 .chain([self.present_value, self.relinquish_default])
199 .chain(self.event_detector.alarm_values.iter().copied())
200 }
201
202 fn set_present_value_directly(&mut self, value: PropertyValue) -> Result<(), Error> {
203 self.present_value = Self::checked_present_value(self.number_of_states, value)?;
204 let _ = self.recompute_reliability();
205 Ok(())
206 }
207}
208
209impl BACnetObject for MultiStateValueObject {
210 fn object_identifier(&self) -> ObjectIdentifier {
211 self.oid
212 }
213
214 fn object_name(&self) -> &str {
215 &self.name
216 }
217
218 fn supports_cov(&self) -> bool {
219 true
220 }
221
222 crate::event::impl_builtin_intrinsic_reporting!(
223 event_detector,
224 event_history,
225 [present_value],
226 reliability,
227 event_detection_enable,
228 ChangeOfStateDetector::ALGORITHM
229 );
230
231 fn acknowledge_alarm_correlated_internal(
232 &mut self,
233 event_state: EventState,
234 timestamp: &BACnetTimeStamp,
235 ) -> Result<(), Error> {
236 if !self.event_detection_enable {
237 return Err(Error::Protocol {
238 class: ErrorClass::OBJECT.to_raw() as u32,
239 code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
240 });
241 }
242 self.event_history.acknowledge_correlated(
243 &mut self.event_detector.acked_transitions,
244 event_state,
245 timestamp,
246 )
247 }
248
249 fn read_property(
250 &self,
251 property: PropertyIdentifier,
252 array_index: Option<u32>,
253 ) -> Result<PropertyValue, Error> {
254 if metadata::excludes(self, property) {
255 return Err(common::unknown_property_error());
256 }
257 if let Some(result) = self
258 .value_source
259 .read(property, array_index, &self.priority_array)
260 {
261 return result;
262 }
263
264 if property == PropertyIdentifier::STATUS_FLAGS {
265 return Ok(common::compute_status_flags(
266 self.status_flags,
267 self.reliability,
268 self.out_of_service,
269 self.event_detector.event_state,
270 ));
271 }
272 if let Some(value) = self.reliability_inhibit.read(property) {
273 return Ok(value);
274 }
275 if let Some(result) = read_common_properties!(self, property, array_index) {
276 return result;
277 }
278 if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
279 return Ok(PropertyValue::Boolean(self.event_detection_enable));
280 }
281 if let Some(result) = read_generic_event_properties!(self, property) {
282 return result;
283 }
284 if let Some(result) = self.event_history.read(property, array_index) {
285 return result;
286 }
287 match property {
288 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
289 ObjectType::MULTI_STATE_VALUE.to_raw(),
290 )),
291 p if p == PropertyIdentifier::PRESENT_VALUE => {
292 Ok(PropertyValue::Unsigned(self.present_value as u64))
293 }
294 p if p == PropertyIdentifier::NUMBER_OF_STATES => {
295 Ok(PropertyValue::Unsigned(self.number_of_states as u64))
296 }
297 p if p == PropertyIdentifier::PRIORITY_ARRAY => {
298 common::read_priority_array!(self, array_index, |v: u32| PropertyValue::Unsigned(
299 v as u64
300 ))
301 }
302 p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
303 Ok(PropertyValue::Unsigned(self.relinquish_default as u64))
304 }
305 p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
306 Ok(common::current_command_priority(&self.priority_array))
307 }
308 p if p == PropertyIdentifier::STATE_TEXT => match array_index {
309 None => Ok(PropertyValue::List(
310 self.state_text
311 .iter()
312 .map(|s| PropertyValue::CharacterString(s.clone()))
313 .collect(),
314 )),
315 Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
316 Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
317 PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
318 ),
319 _ => Err(common::invalid_array_index_error()),
320 },
321 p if p == PropertyIdentifier::ALARM_VALUES => Ok(PropertyValue::List(
322 self.event_detector
323 .alarm_values
324 .iter()
325 .map(|v| PropertyValue::Unsigned(*v as u64))
326 .collect(),
327 )),
328 _ => Err(common::unknown_property_error()),
329 }
330 }
331
332 fn write_property_from(
333 &mut self,
334 property: PropertyIdentifier,
335 array_index: Option<u32>,
336 value: PropertyValue,
337 priority: Option<u8>,
338 origin: &crate::command_source::CommandOrigin,
339 ) -> Result<(), Error> {
340 if matches!(
341 property,
342 PropertyIdentifier::PRESENT_VALUE | PropertyIdentifier::VALUE_SOURCE
343 ) && array_index.is_some()
344 {
345 return Err(common::property_is_not_an_array_error());
346 }
347 if property == PropertyIdentifier::VALUE_SOURCE
348 && self.access == PresentValueAccess::Commandable
349 {
350 return self.value_source.correct(value, priority, origin);
351 }
352 if property == PropertyIdentifier::PRESENT_VALUE {
353 let number_of_states = self.number_of_states;
354 return match self.access {
355 PresentValueAccess::Commandable => {
356 crate::command_source::write_sourced_priority!(
357 self,
358 value,
359 priority,
360 origin,
361 |v| Self::checked_present_value(number_of_states, v)
362 )
363 }
364 PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
365 self.write_property(property, array_index, value, priority)
366 }
367 };
368 }
369 self.write_property(property, array_index, value, priority)
370 }
371
372 fn write_property(
373 &mut self,
374 property: PropertyIdentifier,
375 array_index: Option<u32>,
376 value: PropertyValue,
377 _priority: Option<u8>,
378 ) -> Result<(), Error> {
379 if metadata::excludes(self, property) {
380 return Err(common::unknown_property_error());
381 }
382 if property == PropertyIdentifier::PRESENT_VALUE {
383 if self.access == PresentValueAccess::Commandable {
384 return Err(common::write_access_denied_error());
385 }
386 if array_index.is_some() {
387 return Err(common::property_is_not_an_array_error());
388 }
389 if self.access == PresentValueAccess::ReadOnly && !self.out_of_service {
390 return Err(common::write_access_denied_error());
391 }
392 if value == PropertyValue::Null {
394 return Ok(());
395 }
396 return self.set_present_value_directly(value);
397 }
398 if property == PropertyIdentifier::VALUE_SOURCE {
399 return Err(common::write_access_denied_error());
400 }
401 if property == PropertyIdentifier::STATE_TEXT {
402 let held: Vec<u32> = self.held_states().collect();
404 write_state_text(
405 &mut self.number_of_states,
406 &mut self.state_text,
407 held,
408 array_index,
409 value,
410 )?;
411 let _ = self.recompute_reliability();
412 return Ok(());
413 }
414 if property == PropertyIdentifier::ALARM_VALUES {
415 let values = decode_alarm_values_write(array_index, value, self.number_of_states)?;
416 self.set_alarm_values(values);
417 return Ok(());
418 }
419 if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
420 if let PropertyValue::Boolean(v) = value {
421 self.event_detection_enable = v;
422 if !v {
423 self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
424 self.event_detector.acked_transitions =
425 bacnet_types::bitstring::EventTransitionBits::all();
426 self.event_detector.pending = None;
427 self.event_detector.fault_reliability = None;
428 self.event_history.reset();
429 }
430 return Ok(());
431 }
432 return Err(common::invalid_data_type_error());
433 }
434 if let Some(result) =
436 self.event_history
437 .write(property, array_index, &value, self.event_detection_enable)
438 {
439 return result;
440 }
441 if let Some(result) = write_generic_event_properties!(self, property, value) {
442 return result;
443 }
444 if let Some(result) = self.reliability_inhibit.write_inhibit(
445 &mut self.reliability,
446 self.out_of_service,
447 property,
448 &value,
449 ) {
450 result?;
451 let _ = self.recompute_reliability();
452 return Ok(());
453 }
454 if let Some(result) = self.reliability_inhibit.write_out_of_service(
455 &mut self.out_of_service,
456 &mut self.reliability,
457 &mut self.reliability_before_out_of_service,
458 property,
459 &value,
460 ) {
461 if result? == crate::reliability_inhibit::OutOfServiceWrite::Applied {
462 let _ = self.recompute_reliability();
463 }
464 return Ok(());
465 }
466 if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
467 return result;
468 }
469 if let Some(result) = common::write_description(&mut self.description, property, &value) {
470 return result;
471 }
472 if let Some(result) = self.reliability_inhibit.write_client_reliability(
477 self.out_of_service,
478 &mut self.reliability,
479 property,
480 &value,
481 ) {
482 return result;
483 }
484 if property == PropertyIdentifier::RELINQUISH_DEFAULT {
485 if let PropertyValue::Unsigned(u) = value {
486 let v = common::u64_to_u32(u)?;
487 return self.set_relinquish_default(v);
488 }
489 return Err(common::invalid_data_type_error());
490 }
491 Err(crate::common::unhandled_write_error(
492 self.property_metadata().as_ref(),
493 property,
494 array_index,
495 ))
496 }
497
498 fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
499 metadata::for_object(self)
500 }
501
502 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
503 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
504 }
505
506 fn is_createable(&self) -> bool {
507 true
508 }
509 fn creation_only_properties(&self) -> &'static [PropertyIdentifier] {
510 CREATION_ONLY
511 }
512 fn initialize_property(
513 &mut self,
514 property: PropertyIdentifier,
515 value: PropertyValue,
516 ) -> Result<(), Error> {
517 let held: Vec<u32> = self.held_states().collect();
518 initialize_states(
519 &mut self.number_of_states,
520 &mut self.state_text,
521 held,
522 property,
523 value,
524 )?;
525 let _ = self.recompute_reliability();
526 Ok(())
527 }
528 fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
529 if self.out_of_service || self.reliability_inhibit.enabled() {
530 return Err(common::write_access_denied_error());
531 }
532 if !common::is_reliability_value_valid(reliability) {
533 return Err(common::value_out_of_range_error());
534 }
535 self.reliability = reliability;
536 self.reliability_evaluator.clear_ownership();
537 Ok(())
538 }
539
540 fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
541 Ok(self.recompute_reliability())
542 }
543
544 fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
545 match self.access {
546 PresentValueAccess::Commandable => {
547 Err(common::optional_functionality_not_supported_error())
548 }
549 _ if self.out_of_service => Err(common::write_access_denied_error()),
550 PresentValueAccess::ReadOnly | PresentValueAccess::Writable => {
551 self.set_present_value_directly(value)
552 }
553 }
554 }
555
556 fn reliability_evaluation_inhibited_internal(&self) -> bool {
557 self.reliability_inhibit.enabled()
558 }
559}
560
561#[cfg(test)]
562mod detection_enable_tests {
563 use super::*;
564
565 #[test]
566 fn msv_detection_enable_resets_and_gates_intrinsic_reporting() {
567 let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
568 assert_eq!(
569 msv.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
570 .unwrap(),
571 PropertyValue::Boolean(true)
572 );
573 msv.event_detector.alarm_values = vec![2];
574 msv.event_detector.time_delay = 2;
575 msv.write_property_from(
576 PropertyIdentifier::PRESENT_VALUE,
577 None,
578 PropertyValue::Unsigned(2),
579 Some(8),
580 &crate::command_source::test_origin(),
581 )
582 .unwrap();
583 assert_eq!(msv.evaluate_intrinsic_reporting(), None);
584 assert!(msv.event_detector.pending.is_some());
585
586 msv.event_detector.event_state = bacnet_types::enums::EventState::OFFNORMAL;
587 msv.event_detector.acked_transitions =
588 bacnet_types::bitstring::EventTransitionBits::empty();
589 msv.event_detector.fault_reliability = Some(bacnet_types::enums::Reliability::NO_SENSOR);
590 msv.write_property(
591 PropertyIdentifier::EVENT_DETECTION_ENABLE,
592 None,
593 PropertyValue::Boolean(false),
594 None,
595 )
596 .unwrap();
597
598 assert_eq!(
599 msv.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
600 .unwrap(),
601 PropertyValue::Boolean(false)
602 );
603 assert_eq!(
604 msv.read_property(PropertyIdentifier::EVENT_STATE, None)
605 .unwrap(),
606 PropertyValue::Enumerated(bacnet_types::enums::EventState::NORMAL.to_raw())
607 );
608 assert_eq!(
609 msv.read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
610 .unwrap(),
611 PropertyValue::BitString {
612 unused_bits: 5,
613 data: vec![0xe0],
614 }
615 );
616 assert!(msv.event_detector.pending.is_none());
617 assert!(msv.event_detector.fault_reliability.is_none());
618 assert_eq!(msv.evaluate_intrinsic_reporting(), None);
619 assert_eq!(msv.tick_intrinsic_reporting(), None);
620 assert!(
621 msv.event_detector.pending.is_none(),
622 "evaluate/tick re-armed a countdown while detection is disabled"
623 );
624 assert!(msv
625 .property_list()
626 .contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
627 assert!(msv.is_writable_property(PropertyIdentifier::EVENT_DETECTION_ENABLE));
628 }
629}
630
631#[cfg(test)]
632mod reliability_evaluator_tests {
633 use super::*;
634 use bacnet_types::enums::Reliability;
635
636 #[test]
637 fn configuration_error_dominates_bypassed_invalid_present_value() {
638 let mut msv = MultiStateValueObject::new(1, "MSV-dominance", 2).unwrap();
639 msv.present_value = 3;
640 msv.event_detector.alarm_values = vec![3];
641
642 msv.evaluate_reliability_internal().unwrap();
643 assert_eq!(
644 msv.reliability,
645 Reliability::CONFIGURATION_ERROR,
646 "invalid configuration must dominate invalid Present_Value"
647 );
648 msv.event_detector.alarm_values = vec![1];
649 msv.evaluate_reliability_internal().unwrap();
650 assert_eq!(msv.reliability, Reliability::MULTI_STATE_OUT_OF_RANGE);
651 msv.set_number_of_states(3).unwrap();
652 assert_eq!(
653 msv.reliability,
654 Reliability::NO_FAULT_DETECTED,
655 "count growth must synchronously recover the retained Present_Value"
656 );
657 }
658}