1use super::*;
2
3pub struct MultiStateValueObject {
12 oid: ObjectIdentifier,
13 name: String,
14 description: String,
15 present_value: u32,
16 number_of_states: u32,
17 out_of_service: bool,
18 status_flags: StatusFlags,
19 priority_array: [Option<u32>; 16],
20 relinquish_default: u32,
21 reliability: u32,
23 reliability_before_out_of_service: Option<u32>,
24 reliability_inhibit: common::ReliabilityInhibitState,
25 reliability_evaluator: MultiStateReliabilityState,
26 state_text: Vec<String>,
27 event_detector: ChangeOfStateDetector,
29 event_detection_enable: bool,
32 pub(crate) event_history: EventHistory,
33 value_source: common::ValueSourceTracking,
35}
36
37impl MultiStateValueObject {
38 pub fn new(
39 instance: u32,
40 name: impl Into<String>,
41 number_of_states: u32,
42 ) -> Result<Self, Error> {
43 let oid = ObjectIdentifier::new(ObjectType::MULTI_STATE_VALUE, instance)?;
44 require_nonzero_states(number_of_states)?;
45 Ok(Self {
46 oid,
47 name: name.into(),
48 description: String::new(),
49 present_value: 1,
50 number_of_states,
51 out_of_service: false,
52 status_flags: StatusFlags::empty(),
53 priority_array: [None; 16],
54 relinquish_default: 1,
55 reliability: 0,
56 reliability_before_out_of_service: None,
57 reliability_inhibit: common::ReliabilityInhibitState::default(),
58 reliability_evaluator: MultiStateReliabilityState::default(),
59 state_text: (1..=number_of_states)
60 .map(|i| format!("State {i}"))
61 .collect(),
62 event_detector: ChangeOfStateDetector::default(),
63 event_detection_enable: true,
64 event_history: EventHistory::default(),
65 value_source: common::ValueSourceTracking::default(),
66 })
67 }
68
69 pub fn set_description(&mut self, desc: impl Into<String>) {
71 self.description = desc.into();
72 }
73
74 fn recalculate_present_value(&mut self) {
75 self.present_value =
76 common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
77 let _ = self.recompute_reliability();
78 }
79
80 fn configuration_invalid(&self) -> bool {
81 let is_invalid = |value: u32| !(1..=self.number_of_states).contains(&value);
82 self.priority_array
83 .iter()
84 .flatten()
85 .copied()
86 .any(is_invalid)
87 || is_invalid(self.relinquish_default)
88 || self
89 .event_detector
90 .alarm_values
91 .iter()
92 .copied()
93 .any(is_invalid)
94 }
95
96 fn recompute_reliability(&mut self) -> ReliabilityEvaluation {
97 if self.out_of_service || self.reliability_inhibit.enabled() {
98 return ReliabilityEvaluation::Unchanged;
99 }
100 let configuration_invalid = self.configuration_invalid();
101 self.reliability_evaluator.evaluate(
102 configuration_invalid,
103 self.present_value,
104 self.number_of_states,
105 &mut self.reliability,
106 )
107 }
108
109 pub fn set_number_of_states(&mut self, number_of_states: u32) -> Result<(), Error> {
115 resize_state_text(
116 &mut self.number_of_states,
117 &mut self.state_text,
118 number_of_states,
119 )?;
120 let _ = self.recompute_reliability();
121 Ok(())
122 }
123
124 pub fn set_alarm_values(&mut self, values: Vec<u32>) {
126 self.event_detector.alarm_values = values;
127 let _ = self.recompute_reliability();
128 }
129
130 pub fn set_relinquish_default(&mut self, value: u32) -> Result<(), Error> {
144 if value < 1 || value > self.number_of_states {
145 return Err(common::value_out_of_range_error());
146 }
147 self.relinquish_default = value;
148 self.recalculate_present_value();
149 Ok(())
150 }
151}
152
153impl BACnetObject for MultiStateValueObject {
154 fn object_identifier(&self) -> ObjectIdentifier {
155 self.oid
156 }
157
158 fn object_name(&self) -> &str {
159 &self.name
160 }
161
162 fn supports_cov(&self) -> bool {
163 true
164 }
165
166 crate::event::impl_builtin_intrinsic_reporting!(
167 event_detector,
168 event_history,
169 [present_value],
170 reliability,
171 event_detection_enable,
172 ChangeOfStateDetector::ALGORITHM
173 );
174 impl_intrinsic_write_rollback!(
175 event_detector,
176 event_detection_enable,
177 event_history,
178 reliability_inhibit,
179 reliability,
180 out_of_service,
181 reliability_before_out_of_service;
182 reliability_evaluator,
183 priority_array,
184 relinquish_default,
185 present_value
186 );
187
188 fn acknowledge_alarm_correlated_internal(
189 &mut self,
190 event_state: EventState,
191 timestamp: &BACnetTimeStamp,
192 ) -> Result<(), Error> {
193 if !self.event_detection_enable {
194 return Err(Error::Protocol {
195 class: ErrorClass::OBJECT.to_raw() as u32,
196 code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
197 });
198 }
199 self.event_history.acknowledge_correlated(
200 &mut self.event_detector.acked_transitions,
201 event_state,
202 timestamp,
203 )
204 }
205
206 fn read_property(
207 &self,
208 property: PropertyIdentifier,
209 array_index: Option<u32>,
210 ) -> Result<PropertyValue, Error> {
211 if property == PropertyIdentifier::STATUS_FLAGS {
212 return Ok(common::compute_status_flags(
213 self.status_flags,
214 self.reliability,
215 self.out_of_service,
216 self.event_detector.event_state.to_raw(),
217 ));
218 }
219 if let Some(value) = self.reliability_inhibit.read(property) {
220 return Ok(value);
221 }
222 if let Some(result) = read_common_properties!(self, property, array_index) {
223 return result;
224 }
225 if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
226 return Ok(PropertyValue::Boolean(self.event_detection_enable));
227 }
228 if let Some(result) = read_generic_event_properties!(self, property) {
229 return result;
230 }
231 if let Some(result) = self.event_history.read(property, array_index) {
232 return result;
233 }
234 match property {
235 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
236 ObjectType::MULTI_STATE_VALUE.to_raw(),
237 )),
238 p if p == PropertyIdentifier::PRESENT_VALUE => {
239 Ok(PropertyValue::Unsigned(self.present_value as u64))
240 }
241 p if p == PropertyIdentifier::NUMBER_OF_STATES => {
242 Ok(PropertyValue::Unsigned(self.number_of_states as u64))
243 }
244 p if p == PropertyIdentifier::VALUE_SOURCE => {
245 Ok(self.value_source.value_source.clone())
246 }
247 p if p == PropertyIdentifier::LAST_COMMAND_TIME => Ok(PropertyValue::Unsigned(
248 match self.value_source.last_command_time {
249 BACnetTimeStamp::SequenceNumber(n) => u64::from(n),
250 _ => 0,
251 },
252 )),
253 p if p == PropertyIdentifier::PRIORITY_ARRAY => {
254 common::read_priority_array!(self, array_index, |v: u32| PropertyValue::Unsigned(
255 v as u64
256 ))
257 }
258 p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
259 Ok(PropertyValue::Unsigned(self.relinquish_default as u64))
260 }
261 p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
262 Ok(common::current_command_priority(&self.priority_array))
263 }
264 p if p == PropertyIdentifier::STATE_TEXT => match array_index {
265 None => Ok(PropertyValue::List(
266 self.state_text
267 .iter()
268 .map(|s| PropertyValue::CharacterString(s.clone()))
269 .collect(),
270 )),
271 Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
272 Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
273 PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
274 ),
275 _ => Err(common::invalid_array_index_error()),
276 },
277 p if p == PropertyIdentifier::ALARM_VALUES => Ok(PropertyValue::List(
278 self.event_detector
279 .alarm_values
280 .iter()
281 .map(|v| PropertyValue::Unsigned(*v as u64))
282 .collect(),
283 )),
284 _ => Err(common::unknown_property_error()),
285 }
286 }
287
288 fn write_property(
289 &mut self,
290 property: PropertyIdentifier,
291 array_index: Option<u32>,
292 value: PropertyValue,
293 priority: Option<u8>,
294 ) -> Result<(), Error> {
295 {
296 let num_states = self.number_of_states;
297 common::write_priority_array_direct!(self, property, array_index, value, |v| {
298 if let PropertyValue::Unsigned(u) = v {
299 if u < 1 || u > num_states as u64 {
300 Err(common::value_out_of_range_error())
301 } else {
302 Ok(u as u32)
303 }
304 } else {
305 Err(common::invalid_data_type_error())
306 }
307 });
308 }
309 if property == PropertyIdentifier::PRESENT_VALUE {
310 let num_states = self.number_of_states;
311 return common::write_priority_array!(self, value, priority, |v| {
312 if let PropertyValue::Unsigned(u) = v {
313 if u < 1 || u > num_states as u64 {
314 Err(common::value_out_of_range_error())
315 } else {
316 Ok(u as u32)
317 }
318 } else {
319 Err(common::invalid_data_type_error())
320 }
321 });
322 }
323 if property == PropertyIdentifier::STATE_TEXT {
324 match array_index {
325 Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => {
326 if let PropertyValue::CharacterString(s) = value {
327 self.state_text[(idx - 1) as usize] = s;
328 return Ok(());
329 }
330 return Err(common::invalid_data_type_error());
331 }
332 None => return Err(common::write_access_denied_error()),
333 _ => return Err(common::invalid_array_index_error()),
334 }
335 }
336 if property == PropertyIdentifier::ALARM_VALUES {
337 let values = decode_alarm_values_write(array_index, value)?;
338 self.set_alarm_values(values);
339 return Ok(());
340 }
341 if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
342 if let PropertyValue::Boolean(v) = value {
343 self.event_detection_enable = v;
344 if !v {
345 self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
346 self.event_detector.acked_transitions = 0b111;
347 self.event_detector.pending = None;
348 self.event_detector.fault_reliability = None;
349 self.event_history.reset();
350 }
351 return Ok(());
352 }
353 return Err(common::invalid_data_type_error());
354 }
355 if let Some(result) = write_generic_event_properties!(self, property, value) {
356 return result;
357 }
358 if let Some(result) = self.reliability_inhibit.write_inhibit(
359 &mut self.reliability,
360 self.out_of_service,
361 property,
362 &value,
363 ) {
364 result?;
365 let _ = self.recompute_reliability();
366 return Ok(());
367 }
368 if let Some(result) = self.reliability_inhibit.write_out_of_service(
369 &mut self.out_of_service,
370 &mut self.reliability,
371 &mut self.reliability_before_out_of_service,
372 property,
373 &value,
374 ) {
375 result?;
376 let _ = self.recompute_reliability();
377 return Ok(());
378 }
379 if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
380 return result;
381 }
382 if let Some(result) = common::write_description(&mut self.description, property, &value) {
383 return result;
384 }
385 if let Some(result) = self.reliability_inhibit.write_client_reliability(
391 self.out_of_service,
392 &mut self.reliability,
393 property,
394 &value,
395 ) {
396 return result;
397 }
398 if property == PropertyIdentifier::RELINQUISH_DEFAULT {
399 if let PropertyValue::Unsigned(u) = value {
400 let v = common::u64_to_u32(u)?;
401 return self.set_relinquish_default(v);
402 }
403 return Err(common::invalid_data_type_error());
404 }
405 Err(common::write_access_denied_error())
406 }
407
408 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
409 static PROPS: &[PropertyIdentifier] = &[
410 PropertyIdentifier::OBJECT_IDENTIFIER,
411 PropertyIdentifier::OBJECT_NAME,
412 PropertyIdentifier::DESCRIPTION,
413 PropertyIdentifier::OBJECT_TYPE,
414 PropertyIdentifier::PRESENT_VALUE,
415 PropertyIdentifier::STATUS_FLAGS,
416 PropertyIdentifier::EVENT_STATE,
417 PropertyIdentifier::EVENT_DETECTION_ENABLE,
418 PropertyIdentifier::EVENT_ENABLE,
419 PropertyIdentifier::TIME_DELAY,
420 PropertyIdentifier::TIME_DELAY_NORMAL,
421 PropertyIdentifier::NOTIFY_TYPE,
422 PropertyIdentifier::NOTIFICATION_CLASS,
423 PropertyIdentifier::ACKED_TRANSITIONS,
424 PropertyIdentifier::EVENT_TIME_STAMPS,
425 PropertyIdentifier::EVENT_MESSAGE_TEXTS,
426 PropertyIdentifier::OUT_OF_SERVICE,
427 PropertyIdentifier::NUMBER_OF_STATES,
428 PropertyIdentifier::PRIORITY_ARRAY,
429 PropertyIdentifier::RELINQUISH_DEFAULT,
430 PropertyIdentifier::CURRENT_COMMAND_PRIORITY,
431 PropertyIdentifier::RELIABILITY,
432 PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT,
433 PropertyIdentifier::STATE_TEXT,
434 PropertyIdentifier::ALARM_VALUES,
435 ];
436 Cow::Borrowed(PROPS)
437 }
438
439 fn is_createable(&self) -> bool {
440 true
441 }
442 fn set_reliability_internal(&mut self, reliability: u32) -> Result<(), Error> {
443 if self.out_of_service || self.reliability_inhibit.enabled() {
444 return Err(common::write_access_denied_error());
445 }
446 if !common::is_reliability_value_valid(reliability) {
447 return Err(common::value_out_of_range_error());
448 }
449 self.reliability = reliability;
450 self.reliability_evaluator.clear_ownership();
451 Ok(())
452 }
453
454 fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
455 Ok(self.recompute_reliability())
456 }
457
458 fn reliability_evaluation_inhibited_internal(&self) -> bool {
459 self.reliability_inhibit.enabled()
460 }
461
462 fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
463 common::is_multistate_commandable_writable(property)
468 || common::is_generic_event_property_writable(property)
469 || property == PropertyIdentifier::RELIABILITY
470 || property == PropertyIdentifier::RELIABILITY_EVALUATION_INHIBIT
471 || property == PropertyIdentifier::EVENT_DETECTION_ENABLE
472 || property == PropertyIdentifier::ALARM_VALUES
473 }
474}
475
476#[cfg(test)]
477mod detection_enable_tests {
478 use super::*;
479
480 #[test]
481 fn msv_detection_enable_resets_and_gates_intrinsic_reporting() {
482 let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
483 assert_eq!(
484 msv.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
485 .unwrap(),
486 PropertyValue::Boolean(true)
487 );
488 msv.event_detector.alarm_values = vec![2];
489 msv.event_detector.time_delay = 2;
490 msv.write_property(
491 PropertyIdentifier::PRESENT_VALUE,
492 None,
493 PropertyValue::Unsigned(2),
494 Some(8),
495 )
496 .unwrap();
497 assert_eq!(msv.evaluate_intrinsic_reporting(), None);
498 assert!(msv.event_detector.pending.is_some());
499
500 msv.event_detector.event_state = bacnet_types::enums::EventState::OFFNORMAL;
501 msv.event_detector.acked_transitions = 0;
502 msv.event_detector.fault_reliability = Some(1);
503 msv.write_property(
504 PropertyIdentifier::EVENT_DETECTION_ENABLE,
505 None,
506 PropertyValue::Boolean(false),
507 None,
508 )
509 .unwrap();
510
511 assert_eq!(
512 msv.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
513 .unwrap(),
514 PropertyValue::Boolean(false)
515 );
516 assert_eq!(
517 msv.read_property(PropertyIdentifier::EVENT_STATE, None)
518 .unwrap(),
519 PropertyValue::Enumerated(bacnet_types::enums::EventState::NORMAL.to_raw())
520 );
521 assert_eq!(
522 msv.read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
523 .unwrap(),
524 PropertyValue::BitString {
525 unused_bits: 5,
526 data: vec![0xe0],
527 }
528 );
529 assert!(msv.event_detector.pending.is_none());
530 assert!(msv.event_detector.fault_reliability.is_none());
531 assert_eq!(msv.evaluate_intrinsic_reporting(), None);
532 assert_eq!(msv.tick_intrinsic_reporting(), None);
533 assert!(
534 msv.event_detector.pending.is_none(),
535 "evaluate/tick re-armed a countdown while detection is disabled"
536 );
537 assert!(msv
538 .property_list()
539 .contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
540 assert!(msv.is_writable_property(PropertyIdentifier::EVENT_DETECTION_ENABLE));
541 }
542}
543
544#[cfg(test)]
545mod reliability_evaluator_tests {
546 use super::*;
547 use bacnet_types::enums::Reliability;
548
549 #[test]
550 fn configuration_error_dominates_bypassed_invalid_present_value() {
551 let mut msv = MultiStateValueObject::new(1, "MSV-dominance", 2).unwrap();
552 msv.present_value = 3;
553 msv.event_detector.alarm_values = vec![3];
554
555 msv.evaluate_reliability_internal().unwrap();
556 assert_eq!(
557 msv.reliability,
558 Reliability::CONFIGURATION_ERROR.to_raw(),
559 "invalid configuration must dominate invalid Present_Value"
560 );
561 msv.event_detector.alarm_values = vec![1];
562 msv.evaluate_reliability_internal().unwrap();
563 assert_eq!(
564 msv.reliability,
565 Reliability::MULTI_STATE_OUT_OF_RANGE.to_raw()
566 );
567 msv.set_number_of_states(3).unwrap();
568 assert_eq!(
569 msv.reliability,
570 Reliability::NO_FAULT_DETECTED.to_raw(),
571 "count growth must synchronously recover the retained Present_Value"
572 );
573 }
574}