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