1use super::*;
2use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
3use crate::property_metadata::PropertyMetadata;
4use std::sync::Arc;
5
6mod metadata;
7
8pub struct MultiStateInputObject {
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 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}
38
39impl MultiStateInputObject {
40 pub fn new(
42 instance: u32,
43 name: impl Into<String>,
44 number_of_states: u32,
45 ) -> Result<Self, Error> {
46 let oid = ObjectIdentifier::new(ObjectType::MULTI_STATE_INPUT, instance)?;
47 require_nonzero_states(number_of_states)?;
48 Ok(Self {
49 profile: ProfileState::default(),
50 oid,
51 name: name.into(),
52 description: String::new(),
53 present_value: 1,
54 number_of_states,
55 out_of_service: false,
56 status_flags: StatusFlags::empty(),
57 reliability: Reliability::NO_FAULT_DETECTED,
58 reliability_before_out_of_service: None,
59 reliability_inhibit: common::ReliabilityInhibitState::default(),
60 reliability_evaluator: MultiStateReliabilityState::default(),
61 state_text: (1..=number_of_states)
62 .map(|i| format!("State {i}"))
63 .collect(),
64 event_detector: ChangeOfStateDetector::default(),
65 event_detection_enable: true,
66 event_history: EventHistory::default(),
67 })
68 }
69
70 pub fn set_alarm_values(&mut self, values: Vec<u32>) {
75 self.event_detector.alarm_values = values;
76 }
77
78 pub fn with_tags_persistence(
82 instance: u32,
83 name: impl Into<String>,
84 number_of_states: u32,
85 persistence: Arc<dyn TagsPersistence>,
86 ) -> Result<Self, Error> {
87 let mut object = Self::new(instance, name, number_of_states)?;
88 object.profile = ProfileState::persistent(object.oid, persistence)?;
89 Ok(object)
90 }
91
92 pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
97 self.profile.provision(profile)
98 }
99
100 pub fn wait_for_tag_saves(&self) {
103 self.profile.wait_for_saves();
104 }
105
106 pub fn set_present_value(&mut self, value: u32) {
113 self.present_value = value;
114 let _ = self.recompute_reliability();
115 }
116
117 fn apply_present_value(&mut self, value: PropertyValue) -> Result<(), Error> {
122 let PropertyValue::Unsigned(v) = value else {
123 return Err(common::invalid_data_type_error());
124 };
125 if v < 1 || v > self.number_of_states as u64 {
126 return Err(common::value_out_of_range_error());
127 }
128 self.present_value = v as u32;
129 let _ = self.recompute_reliability();
130 Ok(())
131 }
132
133 pub fn set_number_of_states(&mut self, number_of_states: u32) -> Result<(), Error> {
140 resize_state_text(
141 &mut self.number_of_states,
142 &mut self.state_text,
143 number_of_states,
144 )?;
145 let _ = self.recompute_reliability();
146 Ok(())
147 }
148
149 pub fn set_description(&mut self, desc: impl Into<String>) {
151 self.description = desc.into();
152 }
153
154 fn held_states(&self) -> impl Iterator<Item = u32> + '_ {
157 std::iter::once(self.present_value).chain(self.event_detector.alarm_values.iter().copied())
158 }
159
160 fn recompute_reliability(&mut self) -> ReliabilityEvaluation {
161 if self.out_of_service || self.reliability_inhibit.enabled() {
162 return ReliabilityEvaluation::Unchanged;
163 }
164 self.reliability_evaluator.evaluate(
165 false,
166 self.present_value,
167 self.number_of_states,
168 &mut self.reliability,
169 )
170 }
171}
172
173impl BACnetObject for MultiStateInputObject {
174 fn object_identifier(&self) -> ObjectIdentifier {
175 self.oid
176 }
177
178 fn object_name(&self) -> &str {
179 &self.name
180 }
181
182 fn supports_cov(&self) -> bool {
183 true
184 }
185
186 crate::event::impl_builtin_intrinsic_reporting!(
187 event_detector,
188 event_history,
189 [present_value],
190 reliability,
191 event_detection_enable,
192 ChangeOfStateDetector::ALGORITHM
193 );
194
195 fn acknowledge_alarm_correlated_internal(
196 &mut self,
197 event_state: EventState,
198 timestamp: &BACnetTimeStamp,
199 ) -> Result<(), Error> {
200 if !self.event_detection_enable {
201 return Err(Error::Protocol {
202 class: ErrorClass::OBJECT.to_raw() as u32,
203 code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
204 });
205 }
206 self.event_history.acknowledge_correlated(
207 &mut self.event_detector.acked_transitions,
208 event_state,
209 timestamp,
210 )
211 }
212
213 fn read_property(
214 &self,
215 property: PropertyIdentifier,
216 array_index: Option<u32>,
217 ) -> Result<PropertyValue, Error> {
218 if property == PropertyIdentifier::STATUS_FLAGS {
219 return Ok(common::compute_status_flags(
220 self.status_flags,
221 self.reliability,
222 self.out_of_service,
223 self.event_detector.event_state,
224 ));
225 }
226 if let Some(value) = self.reliability_inhibit.read(property) {
227 return Ok(value);
228 }
229 if let Some(result) = read_common_properties!(self, property, array_index) {
230 return result;
231 }
232 if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
233 return Ok(PropertyValue::Boolean(self.event_detection_enable));
234 }
235 if let Some(result) = read_generic_event_properties!(self, property) {
236 return result;
237 }
238 if let Some(result) = self.event_history.read(property, array_index) {
239 return result;
240 }
241 if let Some(result) = self.profile.read(property, array_index) {
242 return result;
243 }
244 match property {
245 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
246 ObjectType::MULTI_STATE_INPUT.to_raw(),
247 )),
248 p if p == PropertyIdentifier::PRESENT_VALUE => {
249 Ok(PropertyValue::Unsigned(self.present_value as u64))
250 }
251 p if p == PropertyIdentifier::NUMBER_OF_STATES => {
252 Ok(PropertyValue::Unsigned(self.number_of_states as u64))
253 }
254 p if p == PropertyIdentifier::STATE_TEXT => match array_index {
255 None => Ok(PropertyValue::List(
256 self.state_text
257 .iter()
258 .map(|s| PropertyValue::CharacterString(s.clone()))
259 .collect(),
260 )),
261 Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
262 Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
263 PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
264 ),
265 _ => Err(common::invalid_array_index_error()),
266 },
267 p if p == PropertyIdentifier::ALARM_VALUES => Ok(PropertyValue::List(
268 self.event_detector
269 .alarm_values
270 .iter()
271 .map(|v| PropertyValue::Unsigned(*v as u64))
272 .collect(),
273 )),
274 _ => Err(common::unknown_property_error()),
275 }
276 }
277
278 fn write_property(
279 &mut self,
280 property: PropertyIdentifier,
281 array_index: Option<u32>,
282 value: PropertyValue,
283 _priority: Option<u8>,
284 ) -> Result<(), Error> {
285 if property == PropertyIdentifier::PRESENT_VALUE {
286 if !self.out_of_service {
287 return Err(common::write_access_denied_error());
288 }
289 return self.apply_present_value(value);
290 }
291 if let Some(result) = self.profile.write(property, array_index, &value) {
292 return result;
293 }
294 if property == PropertyIdentifier::STATE_TEXT {
295 let held: Vec<u32> = self.held_states().collect();
297 write_state_text(
298 &mut self.number_of_states,
299 &mut self.state_text,
300 held,
301 array_index,
302 value,
303 )?;
304 let _ = self.recompute_reliability();
305 return Ok(());
306 }
307 if property == PropertyIdentifier::ALARM_VALUES {
308 let values = decode_alarm_values_write(array_index, value, self.number_of_states)?;
309 self.event_detector.alarm_values = values;
310 return Ok(());
311 }
312 if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
313 if let PropertyValue::Boolean(v) = value {
314 self.event_detection_enable = v;
315 if !v {
316 self.event_detector.event_state = bacnet_types::enums::EventState::NORMAL;
317 self.event_detector.acked_transitions =
318 bacnet_types::bitstring::EventTransitionBits::all();
319 self.event_detector.pending = None;
320 self.event_detector.fault_reliability = None;
321 self.event_history.reset();
322 }
323 return Ok(());
324 }
325 return Err(common::invalid_data_type_error());
326 }
327 if let Some(result) =
329 self.event_history
330 .write(property, array_index, &value, self.event_detection_enable)
331 {
332 return result;
333 }
334 if let Some(result) = write_generic_event_properties!(self, property, value) {
335 return result;
336 }
337 if let Some(result) = self.reliability_inhibit.write_inhibit(
338 &mut self.reliability,
339 self.out_of_service,
340 property,
341 &value,
342 ) {
343 result?;
344 let _ = self.recompute_reliability();
345 return Ok(());
346 }
347 if let Some(result) = self.reliability_inhibit.write_out_of_service(
348 &mut self.out_of_service,
349 &mut self.reliability,
350 &mut self.reliability_before_out_of_service,
351 property,
352 &value,
353 ) {
354 if result? == crate::reliability_inhibit::OutOfServiceWrite::Applied {
355 let _ = self.recompute_reliability();
356 }
357 return Ok(());
358 }
359 if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
360 return result;
361 }
362 if let Some(result) = common::write_description(&mut self.description, property, &value) {
363 return result;
364 }
365 if let Some(result) = self.reliability_inhibit.write_client_reliability(
370 self.out_of_service,
371 &mut self.reliability,
372 property,
373 &value,
374 ) {
375 return result;
376 }
377 Err(crate::common::unhandled_write_error(
378 self.property_metadata().as_ref(),
379 property,
380 array_index,
381 ))
382 }
383
384 fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
385 self.profile.capability()
386 }
387
388 fn advance_monotonic_time_internal(&mut self, now: std::time::Duration) -> bool {
389 self.profile.expire(now);
390 false
391 }
392
393 fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
394 metadata::for_object(self)
395 }
396
397 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
398 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
399 }
400
401 fn is_createable(&self) -> bool {
402 true
403 }
404 fn creation_only_properties(&self) -> &'static [PropertyIdentifier] {
405 CREATION_ONLY
406 }
407 fn initialize_property(
408 &mut self,
409 property: PropertyIdentifier,
410 value: PropertyValue,
411 ) -> Result<(), Error> {
412 let held: Vec<u32> = self.held_states().collect();
413 initialize_states(
414 &mut self.number_of_states,
415 &mut self.state_text,
416 held,
417 property,
418 value,
419 )?;
420 let _ = self.recompute_reliability();
421 Ok(())
422 }
423 fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
424 if self.out_of_service || self.reliability_inhibit.enabled() {
425 return Err(common::write_access_denied_error());
426 }
427 if !common::is_reliability_value_valid(reliability) {
428 return Err(common::value_out_of_range_error());
429 }
430 self.reliability = reliability;
431 self.reliability_evaluator.clear_ownership();
432 Ok(())
433 }
434
435 fn set_present_value_internal(&mut self, value: PropertyValue) -> Result<(), Error> {
436 if self.out_of_service {
438 return Err(common::write_access_denied_error());
439 }
440 self.apply_present_value(value)
441 }
442
443 fn evaluate_reliability_internal(&mut self) -> Result<ReliabilityEvaluation, Error> {
444 Ok(self.recompute_reliability())
445 }
446
447 fn reliability_evaluation_inhibited_internal(&self) -> bool {
448 self.reliability_inhibit.enabled()
449 }
450}
451
452#[cfg(test)]
453mod detection_enable_tests {
454 use super::*;
455
456 #[test]
457 fn msi_detection_enable_resets_and_gates_intrinsic_reporting() {
458 let mut msi = MultiStateInputObject::new(1, "MSI-1", 3).unwrap();
459 assert_eq!(
460 msi.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
461 .unwrap(),
462 PropertyValue::Boolean(true)
463 );
464 msi.set_alarm_values(vec![2]);
465 msi.event_detector.time_delay = 2;
466 msi.set_present_value(2);
467 assert_eq!(msi.evaluate_intrinsic_reporting(), None);
468 assert!(msi.event_detector.pending.is_some());
469
470 msi.event_detector.event_state = bacnet_types::enums::EventState::OFFNORMAL;
471 msi.event_detector.acked_transitions =
472 bacnet_types::bitstring::EventTransitionBits::empty();
473 msi.event_detector.fault_reliability = Some(bacnet_types::enums::Reliability::NO_SENSOR);
474 msi.write_property(
475 PropertyIdentifier::EVENT_DETECTION_ENABLE,
476 None,
477 PropertyValue::Boolean(false),
478 None,
479 )
480 .unwrap();
481
482 assert_eq!(
483 msi.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
484 .unwrap(),
485 PropertyValue::Boolean(false)
486 );
487 assert_eq!(
488 msi.read_property(PropertyIdentifier::EVENT_STATE, None)
489 .unwrap(),
490 PropertyValue::Enumerated(bacnet_types::enums::EventState::NORMAL.to_raw())
491 );
492 assert_eq!(
493 msi.read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
494 .unwrap(),
495 PropertyValue::BitString {
496 unused_bits: 5,
497 data: vec![0xe0],
498 }
499 );
500 assert!(msi.event_detector.pending.is_none());
501 assert!(msi.event_detector.fault_reliability.is_none());
502 assert_eq!(msi.evaluate_intrinsic_reporting(), None);
503 assert_eq!(msi.tick_intrinsic_reporting(), None);
504 assert!(
505 msi.event_detector.pending.is_none(),
506 "evaluate/tick re-armed a countdown while detection is disabled"
507 );
508 assert!(msi
509 .property_list()
510 .contains(&PropertyIdentifier::EVENT_DETECTION_ENABLE));
511 assert!(msi.is_writable_property(PropertyIdentifier::EVENT_DETECTION_ENABLE));
512 }
513}
514
515#[cfg(test)]
516mod reliability_safety_net_tests {
517 use super::*;
518 use crate::traits::ReliabilityEvaluation;
519 use bacnet_types::enums::Reliability;
520
521 #[test]
522 fn periodic_hook_repairs_one_stale_bypassed_state_then_is_unchanged() {
523 let mut msi = MultiStateInputObject::new(1, "MSI-stale", 2).unwrap();
524 msi.present_value = 3;
525
526 assert_eq!(
527 msi.evaluate_reliability_internal().unwrap(),
528 ReliabilityEvaluation::Changed {
529 old_reliability: Reliability::NO_FAULT_DETECTED,
530 new_reliability: Reliability::MULTI_STATE_OUT_OF_RANGE,
531 }
532 );
533 assert_eq!(
534 msi.evaluate_reliability_internal().unwrap(),
535 ReliabilityEvaluation::Unchanged
536 );
537 }
538}