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