Skip to main content

bacnet_objects/
multistate.rs

1//! Multi-State Input (type 13), Multi-State Output (type 14), and
2//! Multi-State Value (type 19) objects per ASHRAE 135-2020 Clauses 12.20-12.22.
3
4use bacnet_types::enums::{ObjectType, PropertyIdentifier};
5use bacnet_types::error::Error;
6use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier, PropertyValue, StatusFlags};
7use std::borrow::Cow;
8
9use crate::common::{self, read_common_properties};
10use crate::event::{ChangeOfStateDetector, EventStateChange};
11use crate::traits::BACnetObject;
12
13// ---------------------------------------------------------------------------
14// MultiStateInput (type 13)
15// ---------------------------------------------------------------------------
16
17/// BACnet Multi-State Input object.
18///
19/// Read-only multi-state point. Present_Value is writable only when out-of-service.
20/// Present_Value is Unsigned, range 1..=number_of_states.
21pub struct MultiStateInputObject {
22    oid: ObjectIdentifier,
23    name: String,
24    description: String,
25    present_value: u32,
26    number_of_states: u32,
27    out_of_service: bool,
28    status_flags: StatusFlags,
29    /// Reliability: 0 = NO_FAULT_DETECTED.
30    reliability: u32,
31    state_text: Vec<String>,
32    /// Alarm_Values — state values that trigger OFFNORMAL.
33    alarm_values: Vec<u32>,
34    /// Fault_Values — state values that indicate a fault.
35    fault_values: Vec<u32>,
36    /// CHANGE_OF_STATE event detector.
37    event_detector: ChangeOfStateDetector,
38}
39
40impl MultiStateInputObject {
41    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        Ok(Self {
48            oid,
49            name: name.into(),
50            description: String::new(),
51            present_value: 1,
52            number_of_states,
53            out_of_service: false,
54            status_flags: StatusFlags::empty(),
55            reliability: 0,
56            state_text: (1..=number_of_states)
57                .map(|i| format!("State {i}"))
58                .collect(),
59            alarm_values: Vec::new(),
60            fault_values: Vec::new(),
61            event_detector: ChangeOfStateDetector::default(),
62        })
63    }
64
65    /// Set the alarm values (states that trigger OFFNORMAL).
66    pub fn set_alarm_values(&mut self, values: Vec<u32>) {
67        self.alarm_values = values.clone();
68        self.event_detector.alarm_values = values;
69    }
70
71    /// Set the fault values (states that indicate a fault).
72    pub fn set_fault_values(&mut self, values: Vec<u32>) {
73        self.fault_values = values;
74    }
75
76    /// Set the present value (used by application to update input state).
77    pub fn set_present_value(&mut self, value: u32) {
78        self.present_value = value;
79    }
80
81    /// Set the description string.
82    pub fn set_description(&mut self, desc: impl Into<String>) {
83        self.description = desc.into();
84    }
85}
86
87impl BACnetObject for MultiStateInputObject {
88    fn object_identifier(&self) -> ObjectIdentifier {
89        self.oid
90    }
91
92    fn object_name(&self) -> &str {
93        &self.name
94    }
95
96    fn supports_cov(&self) -> bool {
97        true
98    }
99
100    fn evaluate_intrinsic_reporting(&mut self) -> Option<EventStateChange> {
101        self.event_detector.evaluate(self.present_value)
102    }
103
104    fn read_property(
105        &self,
106        property: PropertyIdentifier,
107        array_index: Option<u32>,
108    ) -> Result<PropertyValue, Error> {
109        if property == PropertyIdentifier::STATUS_FLAGS {
110            return Ok(common::compute_status_flags(
111                self.status_flags,
112                self.reliability,
113                self.out_of_service,
114                self.event_detector.event_state.to_raw(),
115            ));
116        }
117        if let Some(result) = read_common_properties!(self, property, array_index) {
118            return result;
119        }
120        match property {
121            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
122                ObjectType::MULTI_STATE_INPUT.to_raw(),
123            )),
124            p if p == PropertyIdentifier::PRESENT_VALUE => {
125                Ok(PropertyValue::Unsigned(self.present_value as u64))
126            }
127            p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(
128                self.event_detector.event_state.to_raw(),
129            )),
130            p if p == PropertyIdentifier::NUMBER_OF_STATES => {
131                Ok(PropertyValue::Unsigned(self.number_of_states as u64))
132            }
133            p if p == PropertyIdentifier::STATE_TEXT => match array_index {
134                None => Ok(PropertyValue::List(
135                    self.state_text
136                        .iter()
137                        .map(|s| PropertyValue::CharacterString(s.clone()))
138                        .collect(),
139                )),
140                Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
141                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
142                    PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
143                ),
144                _ => Err(common::invalid_array_index_error()),
145            },
146            p if p == PropertyIdentifier::ALARM_VALUES => Ok(PropertyValue::List(
147                self.alarm_values
148                    .iter()
149                    .map(|v| PropertyValue::Unsigned(*v as u64))
150                    .collect(),
151            )),
152            p if p == PropertyIdentifier::FAULT_VALUES => Ok(PropertyValue::List(
153                self.fault_values
154                    .iter()
155                    .map(|v| PropertyValue::Unsigned(*v as u64))
156                    .collect(),
157            )),
158            p if p == PropertyIdentifier::EVENT_ENABLE => Ok(PropertyValue::BitString {
159                unused_bits: 5,
160                data: vec![self.event_detector.event_enable << 5],
161            }),
162            p if p == PropertyIdentifier::ACKED_TRANSITIONS => Ok(PropertyValue::BitString {
163                unused_bits: 5,
164                data: vec![self.event_detector.acked_transitions << 5],
165            }),
166            p if p == PropertyIdentifier::NOTIFICATION_CLASS => Ok(PropertyValue::Unsigned(
167                self.event_detector.notification_class as u64,
168            )),
169            _ => Err(common::unknown_property_error()),
170        }
171    }
172
173    fn write_property(
174        &mut self,
175        property: PropertyIdentifier,
176        array_index: Option<u32>,
177        value: PropertyValue,
178        _priority: Option<u8>,
179    ) -> Result<(), Error> {
180        if property == PropertyIdentifier::PRESENT_VALUE {
181            if !self.out_of_service {
182                return Err(common::write_access_denied_error());
183            }
184            if let PropertyValue::Unsigned(v) = value {
185                if v < 1 || v > self.number_of_states as u64 {
186                    return Err(common::value_out_of_range_error());
187                }
188                self.present_value = v as u32;
189                return Ok(());
190            }
191            return Err(common::invalid_data_type_error());
192        }
193        if property == PropertyIdentifier::STATE_TEXT {
194            match array_index {
195                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => {
196                    if let PropertyValue::CharacterString(s) = value {
197                        self.state_text[(idx - 1) as usize] = s;
198                        return Ok(());
199                    }
200                    return Err(common::invalid_data_type_error());
201                }
202                None => return Err(common::write_access_denied_error()),
203                _ => return Err(common::invalid_array_index_error()),
204            }
205        }
206        if let Some(result) =
207            common::write_out_of_service(&mut self.out_of_service, property, &value)
208        {
209            return result;
210        }
211        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
212            return result;
213        }
214        if let Some(result) = common::write_description(&mut self.description, property, &value) {
215            return result;
216        }
217        Err(common::write_access_denied_error())
218    }
219
220    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
221        static PROPS: &[PropertyIdentifier] = &[
222            PropertyIdentifier::OBJECT_IDENTIFIER,
223            PropertyIdentifier::OBJECT_NAME,
224            PropertyIdentifier::DESCRIPTION,
225            PropertyIdentifier::OBJECT_TYPE,
226            PropertyIdentifier::PRESENT_VALUE,
227            PropertyIdentifier::STATUS_FLAGS,
228            PropertyIdentifier::EVENT_STATE,
229            PropertyIdentifier::OUT_OF_SERVICE,
230            PropertyIdentifier::NUMBER_OF_STATES,
231            PropertyIdentifier::RELIABILITY,
232            PropertyIdentifier::STATE_TEXT,
233            PropertyIdentifier::ALARM_VALUES,
234            PropertyIdentifier::FAULT_VALUES,
235        ];
236        Cow::Borrowed(PROPS)
237    }
238}
239
240// ---------------------------------------------------------------------------
241// MultiStateOutput (type 14)
242// ---------------------------------------------------------------------------
243
244/// BACnet Multi-State Output object.
245///
246/// Commandable multi-state output with 16-level priority array.
247/// Present_Value is Unsigned, range 1..=number_of_states.
248pub struct MultiStateOutputObject {
249    oid: ObjectIdentifier,
250    name: String,
251    description: String,
252    present_value: u32,
253    number_of_states: u32,
254    out_of_service: bool,
255    status_flags: StatusFlags,
256    priority_array: [Option<u32>; 16],
257    relinquish_default: u32,
258    /// Reliability: 0 = NO_FAULT_DETECTED.
259    reliability: u32,
260    state_text: Vec<String>,
261    alarm_values: Vec<u32>,
262    fault_values: Vec<u32>,
263    /// CHANGE_OF_STATE event detector.
264    event_detector: ChangeOfStateDetector,
265    /// Value source tracking (optional per spec — exposed via VALUE_SOURCE property).
266    value_source: common::ValueSourceTracking,
267}
268
269impl MultiStateOutputObject {
270    pub fn new(
271        instance: u32,
272        name: impl Into<String>,
273        number_of_states: u32,
274    ) -> Result<Self, Error> {
275        let oid = ObjectIdentifier::new(ObjectType::MULTI_STATE_OUTPUT, instance)?;
276        Ok(Self {
277            oid,
278            name: name.into(),
279            description: String::new(),
280            present_value: 1,
281            number_of_states,
282            out_of_service: false,
283            status_flags: StatusFlags::empty(),
284            priority_array: [None; 16],
285            relinquish_default: 1,
286            reliability: 0,
287            state_text: (1..=number_of_states)
288                .map(|i| format!("State {i}"))
289                .collect(),
290            alarm_values: Vec::new(),
291            fault_values: Vec::new(),
292            event_detector: ChangeOfStateDetector::default(),
293            value_source: common::ValueSourceTracking::default(),
294        })
295    }
296
297    /// Set the description string.
298    pub fn set_description(&mut self, desc: impl Into<String>) {
299        self.description = desc.into();
300    }
301
302    fn recalculate_present_value(&mut self) {
303        self.present_value =
304            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
305    }
306}
307
308impl BACnetObject for MultiStateOutputObject {
309    fn object_identifier(&self) -> ObjectIdentifier {
310        self.oid
311    }
312
313    fn object_name(&self) -> &str {
314        &self.name
315    }
316
317    fn supports_cov(&self) -> bool {
318        true
319    }
320
321    fn evaluate_intrinsic_reporting(&mut self) -> Option<EventStateChange> {
322        self.event_detector.evaluate(self.present_value)
323    }
324
325    fn read_property(
326        &self,
327        property: PropertyIdentifier,
328        array_index: Option<u32>,
329    ) -> Result<PropertyValue, Error> {
330        if property == PropertyIdentifier::STATUS_FLAGS {
331            return Ok(common::compute_status_flags(
332                self.status_flags,
333                self.reliability,
334                self.out_of_service,
335                self.event_detector.event_state.to_raw(),
336            ));
337        }
338        if let Some(result) = read_common_properties!(self, property, array_index) {
339            return result;
340        }
341        match property {
342            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
343                ObjectType::MULTI_STATE_OUTPUT.to_raw(),
344            )),
345            p if p == PropertyIdentifier::PRESENT_VALUE => {
346                Ok(PropertyValue::Unsigned(self.present_value as u64))
347            }
348            p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(
349                self.event_detector.event_state.to_raw(),
350            )),
351            p if p == PropertyIdentifier::NUMBER_OF_STATES => {
352                Ok(PropertyValue::Unsigned(self.number_of_states as u64))
353            }
354            p if p == PropertyIdentifier::VALUE_SOURCE => {
355                Ok(self.value_source.value_source.clone())
356            }
357            p if p == PropertyIdentifier::LAST_COMMAND_TIME => Ok(PropertyValue::Unsigned(
358                match self.value_source.last_command_time {
359                    BACnetTimeStamp::SequenceNumber(n) => n,
360                    _ => 0,
361                },
362            )),
363            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
364                common::read_priority_array!(self, array_index, |v: u32| PropertyValue::Unsigned(
365                    v as u64
366                ))
367            }
368            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
369                Ok(PropertyValue::Unsigned(self.relinquish_default as u64))
370            }
371            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
372                Ok(common::current_command_priority(&self.priority_array))
373            }
374            p if p == PropertyIdentifier::STATE_TEXT => match array_index {
375                None => Ok(PropertyValue::List(
376                    self.state_text
377                        .iter()
378                        .map(|s| PropertyValue::CharacterString(s.clone()))
379                        .collect(),
380                )),
381                Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
382                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
383                    PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
384                ),
385                _ => Err(common::invalid_array_index_error()),
386            },
387            p if p == PropertyIdentifier::ALARM_VALUES => Ok(PropertyValue::List(
388                self.alarm_values
389                    .iter()
390                    .map(|v| PropertyValue::Unsigned(*v as u64))
391                    .collect(),
392            )),
393            p if p == PropertyIdentifier::FAULT_VALUES => Ok(PropertyValue::List(
394                self.fault_values
395                    .iter()
396                    .map(|v| PropertyValue::Unsigned(*v as u64))
397                    .collect(),
398            )),
399            p if p == PropertyIdentifier::EVENT_ENABLE => Ok(PropertyValue::BitString {
400                unused_bits: 5,
401                data: vec![self.event_detector.event_enable << 5],
402            }),
403            p if p == PropertyIdentifier::ACKED_TRANSITIONS => Ok(PropertyValue::BitString {
404                unused_bits: 5,
405                data: vec![self.event_detector.acked_transitions << 5],
406            }),
407            p if p == PropertyIdentifier::NOTIFICATION_CLASS => Ok(PropertyValue::Unsigned(
408                self.event_detector.notification_class as u64,
409            )),
410            _ => Err(common::unknown_property_error()),
411        }
412    }
413
414    fn write_property(
415        &mut self,
416        property: PropertyIdentifier,
417        array_index: Option<u32>,
418        value: PropertyValue,
419        priority: Option<u8>,
420    ) -> Result<(), Error> {
421        {
422            let num_states = self.number_of_states;
423            common::write_priority_array_direct!(self, property, array_index, value, |v| {
424                if let PropertyValue::Unsigned(u) = v {
425                    if u < 1 || u > num_states as u64 {
426                        Err(common::value_out_of_range_error())
427                    } else {
428                        Ok(u as u32)
429                    }
430                } else {
431                    Err(common::invalid_data_type_error())
432                }
433            });
434        }
435        if property == PropertyIdentifier::PRESENT_VALUE {
436            let num_states = self.number_of_states;
437            return common::write_priority_array!(self, value, priority, |v| {
438                if let PropertyValue::Unsigned(u) = v {
439                    if u < 1 || u > num_states as u64 {
440                        Err(common::value_out_of_range_error())
441                    } else {
442                        Ok(u as u32)
443                    }
444                } else {
445                    Err(common::invalid_data_type_error())
446                }
447            });
448        }
449        if property == PropertyIdentifier::STATE_TEXT {
450            match array_index {
451                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => {
452                    if let PropertyValue::CharacterString(s) = value {
453                        self.state_text[(idx - 1) as usize] = s;
454                        return Ok(());
455                    }
456                    return Err(common::invalid_data_type_error());
457                }
458                None => return Err(common::write_access_denied_error()),
459                _ => return Err(common::invalid_array_index_error()),
460            }
461        }
462        if let Some(result) =
463            common::write_out_of_service(&mut self.out_of_service, property, &value)
464        {
465            return result;
466        }
467        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
468            return result;
469        }
470        if let Some(result) = common::write_description(&mut self.description, property, &value) {
471            return result;
472        }
473        Err(common::write_access_denied_error())
474    }
475
476    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
477        static PROPS: &[PropertyIdentifier] = &[
478            PropertyIdentifier::OBJECT_IDENTIFIER,
479            PropertyIdentifier::OBJECT_NAME,
480            PropertyIdentifier::DESCRIPTION,
481            PropertyIdentifier::OBJECT_TYPE,
482            PropertyIdentifier::PRESENT_VALUE,
483            PropertyIdentifier::STATUS_FLAGS,
484            PropertyIdentifier::EVENT_STATE,
485            PropertyIdentifier::OUT_OF_SERVICE,
486            PropertyIdentifier::NUMBER_OF_STATES,
487            PropertyIdentifier::PRIORITY_ARRAY,
488            PropertyIdentifier::RELINQUISH_DEFAULT,
489            PropertyIdentifier::CURRENT_COMMAND_PRIORITY,
490            PropertyIdentifier::RELIABILITY,
491            PropertyIdentifier::STATE_TEXT,
492            PropertyIdentifier::ALARM_VALUES,
493            PropertyIdentifier::FAULT_VALUES,
494        ];
495        Cow::Borrowed(PROPS)
496    }
497}
498
499// ---------------------------------------------------------------------------
500// MultiStateValue (type 19)
501// ---------------------------------------------------------------------------
502
503/// BACnet Multi-State Value object.
504///
505/// Commandable multi-state value with 16-level priority array.
506/// Present_Value is Unsigned, range 1..=number_of_states.
507pub struct MultiStateValueObject {
508    oid: ObjectIdentifier,
509    name: String,
510    description: String,
511    present_value: u32,
512    number_of_states: u32,
513    out_of_service: bool,
514    status_flags: StatusFlags,
515    priority_array: [Option<u32>; 16],
516    relinquish_default: u32,
517    /// Reliability: 0 = NO_FAULT_DETECTED.
518    reliability: u32,
519    state_text: Vec<String>,
520    alarm_values: Vec<u32>,
521    fault_values: Vec<u32>,
522    /// CHANGE_OF_STATE event detector.
523    event_detector: ChangeOfStateDetector,
524    /// Value source tracking (optional per spec — exposed via VALUE_SOURCE property).
525    value_source: common::ValueSourceTracking,
526}
527
528impl MultiStateValueObject {
529    pub fn new(
530        instance: u32,
531        name: impl Into<String>,
532        number_of_states: u32,
533    ) -> Result<Self, Error> {
534        let oid = ObjectIdentifier::new(ObjectType::MULTI_STATE_VALUE, instance)?;
535        Ok(Self {
536            oid,
537            name: name.into(),
538            description: String::new(),
539            present_value: 1,
540            number_of_states,
541            out_of_service: false,
542            status_flags: StatusFlags::empty(),
543            priority_array: [None; 16],
544            relinquish_default: 1,
545            reliability: 0,
546            state_text: (1..=number_of_states)
547                .map(|i| format!("State {i}"))
548                .collect(),
549            alarm_values: Vec::new(),
550            fault_values: Vec::new(),
551            event_detector: ChangeOfStateDetector::default(),
552            value_source: common::ValueSourceTracking::default(),
553        })
554    }
555
556    /// Set the description string.
557    pub fn set_description(&mut self, desc: impl Into<String>) {
558        self.description = desc.into();
559    }
560
561    fn recalculate_present_value(&mut self) {
562        self.present_value =
563            common::recalculate_from_priority_array(&self.priority_array, self.relinquish_default);
564    }
565}
566
567impl BACnetObject for MultiStateValueObject {
568    fn object_identifier(&self) -> ObjectIdentifier {
569        self.oid
570    }
571
572    fn object_name(&self) -> &str {
573        &self.name
574    }
575
576    fn supports_cov(&self) -> bool {
577        true
578    }
579
580    fn evaluate_intrinsic_reporting(&mut self) -> Option<EventStateChange> {
581        self.event_detector.evaluate(self.present_value)
582    }
583
584    fn read_property(
585        &self,
586        property: PropertyIdentifier,
587        array_index: Option<u32>,
588    ) -> Result<PropertyValue, Error> {
589        if property == PropertyIdentifier::STATUS_FLAGS {
590            return Ok(common::compute_status_flags(
591                self.status_flags,
592                self.reliability,
593                self.out_of_service,
594                self.event_detector.event_state.to_raw(),
595            ));
596        }
597        if let Some(result) = read_common_properties!(self, property, array_index) {
598            return result;
599        }
600        match property {
601            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
602                ObjectType::MULTI_STATE_VALUE.to_raw(),
603            )),
604            p if p == PropertyIdentifier::PRESENT_VALUE => {
605                Ok(PropertyValue::Unsigned(self.present_value as u64))
606            }
607            p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(
608                self.event_detector.event_state.to_raw(),
609            )),
610            p if p == PropertyIdentifier::NUMBER_OF_STATES => {
611                Ok(PropertyValue::Unsigned(self.number_of_states as u64))
612            }
613            p if p == PropertyIdentifier::VALUE_SOURCE => {
614                Ok(self.value_source.value_source.clone())
615            }
616            p if p == PropertyIdentifier::LAST_COMMAND_TIME => Ok(PropertyValue::Unsigned(
617                match self.value_source.last_command_time {
618                    BACnetTimeStamp::SequenceNumber(n) => n,
619                    _ => 0,
620                },
621            )),
622            p if p == PropertyIdentifier::PRIORITY_ARRAY => {
623                common::read_priority_array!(self, array_index, |v: u32| PropertyValue::Unsigned(
624                    v as u64
625                ))
626            }
627            p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
628                Ok(PropertyValue::Unsigned(self.relinquish_default as u64))
629            }
630            p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
631                Ok(common::current_command_priority(&self.priority_array))
632            }
633            p if p == PropertyIdentifier::STATE_TEXT => match array_index {
634                None => Ok(PropertyValue::List(
635                    self.state_text
636                        .iter()
637                        .map(|s| PropertyValue::CharacterString(s.clone()))
638                        .collect(),
639                )),
640                Some(0) => Ok(PropertyValue::Unsigned(self.state_text.len() as u64)),
641                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => Ok(
642                    PropertyValue::CharacterString(self.state_text[(idx - 1) as usize].clone()),
643                ),
644                _ => Err(common::invalid_array_index_error()),
645            },
646            p if p == PropertyIdentifier::ALARM_VALUES => Ok(PropertyValue::List(
647                self.alarm_values
648                    .iter()
649                    .map(|v| PropertyValue::Unsigned(*v as u64))
650                    .collect(),
651            )),
652            p if p == PropertyIdentifier::FAULT_VALUES => Ok(PropertyValue::List(
653                self.fault_values
654                    .iter()
655                    .map(|v| PropertyValue::Unsigned(*v as u64))
656                    .collect(),
657            )),
658            p if p == PropertyIdentifier::EVENT_ENABLE => Ok(PropertyValue::BitString {
659                unused_bits: 5,
660                data: vec![self.event_detector.event_enable << 5],
661            }),
662            p if p == PropertyIdentifier::ACKED_TRANSITIONS => Ok(PropertyValue::BitString {
663                unused_bits: 5,
664                data: vec![self.event_detector.acked_transitions << 5],
665            }),
666            p if p == PropertyIdentifier::NOTIFICATION_CLASS => Ok(PropertyValue::Unsigned(
667                self.event_detector.notification_class as u64,
668            )),
669            _ => Err(common::unknown_property_error()),
670        }
671    }
672
673    fn write_property(
674        &mut self,
675        property: PropertyIdentifier,
676        array_index: Option<u32>,
677        value: PropertyValue,
678        priority: Option<u8>,
679    ) -> Result<(), Error> {
680        {
681            let num_states = self.number_of_states;
682            common::write_priority_array_direct!(self, property, array_index, value, |v| {
683                if let PropertyValue::Unsigned(u) = v {
684                    if u < 1 || u > num_states as u64 {
685                        Err(common::value_out_of_range_error())
686                    } else {
687                        Ok(u as u32)
688                    }
689                } else {
690                    Err(common::invalid_data_type_error())
691                }
692            });
693        }
694        if property == PropertyIdentifier::PRESENT_VALUE {
695            let num_states = self.number_of_states;
696            return common::write_priority_array!(self, value, priority, |v| {
697                if let PropertyValue::Unsigned(u) = v {
698                    if u < 1 || u > num_states as u64 {
699                        Err(common::value_out_of_range_error())
700                    } else {
701                        Ok(u as u32)
702                    }
703                } else {
704                    Err(common::invalid_data_type_error())
705                }
706            });
707        }
708        if property == PropertyIdentifier::STATE_TEXT {
709            match array_index {
710                Some(idx) if idx >= 1 && (idx as usize) <= self.state_text.len() => {
711                    if let PropertyValue::CharacterString(s) = value {
712                        self.state_text[(idx - 1) as usize] = s;
713                        return Ok(());
714                    }
715                    return Err(common::invalid_data_type_error());
716                }
717                None => return Err(common::write_access_denied_error()),
718                _ => return Err(common::invalid_array_index_error()),
719            }
720        }
721        if let Some(result) =
722            common::write_out_of_service(&mut self.out_of_service, property, &value)
723        {
724            return result;
725        }
726        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
727            return result;
728        }
729        if let Some(result) = common::write_description(&mut self.description, property, &value) {
730            return result;
731        }
732        Err(common::write_access_denied_error())
733    }
734
735    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
736        static PROPS: &[PropertyIdentifier] = &[
737            PropertyIdentifier::OBJECT_IDENTIFIER,
738            PropertyIdentifier::OBJECT_NAME,
739            PropertyIdentifier::DESCRIPTION,
740            PropertyIdentifier::OBJECT_TYPE,
741            PropertyIdentifier::PRESENT_VALUE,
742            PropertyIdentifier::STATUS_FLAGS,
743            PropertyIdentifier::EVENT_STATE,
744            PropertyIdentifier::OUT_OF_SERVICE,
745            PropertyIdentifier::NUMBER_OF_STATES,
746            PropertyIdentifier::PRIORITY_ARRAY,
747            PropertyIdentifier::RELINQUISH_DEFAULT,
748            PropertyIdentifier::CURRENT_COMMAND_PRIORITY,
749            PropertyIdentifier::RELIABILITY,
750            PropertyIdentifier::STATE_TEXT,
751        ];
752        Cow::Borrowed(PROPS)
753    }
754}
755
756#[cfg(test)]
757mod tests {
758    use super::*;
759
760    // --- MultiStateInput ---
761
762    #[test]
763    fn msi_read_present_value_default() {
764        let msi = MultiStateInputObject::new(1, "MSI-1", 4).unwrap();
765        let val = msi
766            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
767            .unwrap();
768        assert_eq!(val, PropertyValue::Unsigned(1));
769    }
770
771    #[test]
772    fn msi_read_number_of_states() {
773        let msi = MultiStateInputObject::new(1, "MSI-1", 4).unwrap();
774        let val = msi
775            .read_property(PropertyIdentifier::NUMBER_OF_STATES, None)
776            .unwrap();
777        assert_eq!(val, PropertyValue::Unsigned(4));
778    }
779
780    #[test]
781    fn msi_write_denied_when_in_service() {
782        let mut msi = MultiStateInputObject::new(1, "MSI-1", 4).unwrap();
783        let result = msi.write_property(
784            PropertyIdentifier::PRESENT_VALUE,
785            None,
786            PropertyValue::Unsigned(2),
787            None,
788        );
789        assert!(result.is_err());
790    }
791
792    #[test]
793    fn msi_write_allowed_when_out_of_service() {
794        let mut msi = MultiStateInputObject::new(1, "MSI-1", 4).unwrap();
795        msi.write_property(
796            PropertyIdentifier::OUT_OF_SERVICE,
797            None,
798            PropertyValue::Boolean(true),
799            None,
800        )
801        .unwrap();
802        msi.write_property(
803            PropertyIdentifier::PRESENT_VALUE,
804            None,
805            PropertyValue::Unsigned(3),
806            None,
807        )
808        .unwrap();
809        let val = msi
810            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
811            .unwrap();
812        assert_eq!(val, PropertyValue::Unsigned(3));
813    }
814
815    #[test]
816    fn msi_write_out_of_range_rejected() {
817        let mut msi = MultiStateInputObject::new(1, "MSI-1", 4).unwrap();
818        msi.write_property(
819            PropertyIdentifier::OUT_OF_SERVICE,
820            None,
821            PropertyValue::Boolean(true),
822            None,
823        )
824        .unwrap();
825        assert!(msi
826            .write_property(
827                PropertyIdentifier::PRESENT_VALUE,
828                None,
829                PropertyValue::Unsigned(0),
830                None
831            )
832            .is_err());
833        assert!(msi
834            .write_property(
835                PropertyIdentifier::PRESENT_VALUE,
836                None,
837                PropertyValue::Unsigned(5),
838                None
839            )
840            .is_err());
841    }
842
843    #[test]
844    fn msi_read_reliability_default() {
845        let msi = MultiStateInputObject::new(1, "MSI-1", 4).unwrap();
846        let val = msi
847            .read_property(PropertyIdentifier::RELIABILITY, None)
848            .unwrap();
849        assert_eq!(val, PropertyValue::Enumerated(0)); // NO_FAULT_DETECTED
850    }
851
852    // --- MultiStateOutput ---
853
854    #[test]
855    fn mso_write_with_priority() {
856        let mut mso = MultiStateOutputObject::new(1, "MSO-1", 5).unwrap();
857        mso.write_property(
858            PropertyIdentifier::PRESENT_VALUE,
859            None,
860            PropertyValue::Unsigned(3),
861            Some(8),
862        )
863        .unwrap();
864        let val = mso
865            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
866            .unwrap();
867        assert_eq!(val, PropertyValue::Unsigned(3));
868        let slot = mso
869            .read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(8))
870            .unwrap();
871        assert_eq!(slot, PropertyValue::Unsigned(3));
872    }
873
874    #[test]
875    fn mso_relinquish_falls_to_default() {
876        let mut mso = MultiStateOutputObject::new(1, "MSO-1", 5).unwrap();
877        mso.write_property(
878            PropertyIdentifier::PRESENT_VALUE,
879            None,
880            PropertyValue::Unsigned(4),
881            Some(16),
882        )
883        .unwrap();
884        assert_eq!(
885            mso.read_property(PropertyIdentifier::PRESENT_VALUE, None)
886                .unwrap(),
887            PropertyValue::Unsigned(4)
888        );
889        mso.write_property(
890            PropertyIdentifier::PRESENT_VALUE,
891            None,
892            PropertyValue::Null,
893            Some(16),
894        )
895        .unwrap();
896        assert_eq!(
897            mso.read_property(PropertyIdentifier::PRESENT_VALUE, None)
898                .unwrap(),
899            PropertyValue::Unsigned(1)
900        ); // default
901    }
902
903    #[test]
904    fn mso_out_of_range_rejected() {
905        let mut mso = MultiStateOutputObject::new(1, "MSO-1", 5).unwrap();
906        assert!(mso
907            .write_property(
908                PropertyIdentifier::PRESENT_VALUE,
909                None,
910                PropertyValue::Unsigned(0),
911                None
912            )
913            .is_err());
914        assert!(mso
915            .write_property(
916                PropertyIdentifier::PRESENT_VALUE,
917                None,
918                PropertyValue::Unsigned(6),
919                None
920            )
921            .is_err());
922    }
923
924    #[test]
925    fn mso_read_reliability_default() {
926        let mso = MultiStateOutputObject::new(1, "MSO-1", 5).unwrap();
927        let val = mso
928            .read_property(PropertyIdentifier::RELIABILITY, None)
929            .unwrap();
930        assert_eq!(val, PropertyValue::Enumerated(0)); // NO_FAULT_DETECTED
931    }
932
933    // --- MultiStateValue ---
934
935    #[test]
936    fn msv_read_present_value_default() {
937        let msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
938        let val = msv
939            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
940            .unwrap();
941        assert_eq!(val, PropertyValue::Unsigned(1));
942    }
943
944    #[test]
945    fn msv_write_with_priority() {
946        let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
947        msv.write_property(
948            PropertyIdentifier::PRESENT_VALUE,
949            None,
950            PropertyValue::Unsigned(2),
951            Some(8),
952        )
953        .unwrap();
954        let val = msv
955            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
956            .unwrap();
957        assert_eq!(val, PropertyValue::Unsigned(2));
958        let slot = msv
959            .read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(8))
960            .unwrap();
961        assert_eq!(slot, PropertyValue::Unsigned(2));
962    }
963
964    #[test]
965    fn msv_relinquish_falls_to_default() {
966        let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
967        msv.write_property(
968            PropertyIdentifier::PRESENT_VALUE,
969            None,
970            PropertyValue::Unsigned(3),
971            Some(16),
972        )
973        .unwrap();
974        assert_eq!(
975            msv.read_property(PropertyIdentifier::PRESENT_VALUE, None)
976                .unwrap(),
977            PropertyValue::Unsigned(3)
978        );
979        msv.write_property(
980            PropertyIdentifier::PRESENT_VALUE,
981            None,
982            PropertyValue::Null,
983            Some(16),
984        )
985        .unwrap();
986        assert_eq!(
987            msv.read_property(PropertyIdentifier::PRESENT_VALUE, None)
988                .unwrap(),
989            PropertyValue::Unsigned(1)
990        ); // relinquish_default
991    }
992
993    #[test]
994    fn msv_read_priority_array_all_none() {
995        let msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
996        let val = msv
997            .read_property(PropertyIdentifier::PRIORITY_ARRAY, None)
998            .unwrap();
999        if let PropertyValue::List(elements) = val {
1000            assert_eq!(elements.len(), 16);
1001            for elem in &elements {
1002                assert_eq!(elem, &PropertyValue::Null);
1003            }
1004        } else {
1005            panic!("Expected List for priority array without index");
1006        }
1007    }
1008
1009    #[test]
1010    fn msv_read_relinquish_default() {
1011        let msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1012        let val = msv
1013            .read_property(PropertyIdentifier::RELINQUISH_DEFAULT, None)
1014            .unwrap();
1015        assert_eq!(val, PropertyValue::Unsigned(1));
1016    }
1017
1018    #[test]
1019    fn msv_write_out_of_range_rejected() {
1020        let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1021        assert!(msv
1022            .write_property(
1023                PropertyIdentifier::PRESENT_VALUE,
1024                None,
1025                PropertyValue::Unsigned(0),
1026                None
1027            )
1028            .is_err());
1029        assert!(msv
1030            .write_property(
1031                PropertyIdentifier::PRESENT_VALUE,
1032                None,
1033                PropertyValue::Unsigned(4),
1034                None
1035            )
1036            .is_err());
1037    }
1038
1039    #[test]
1040    fn msv_write_wrong_type_rejected() {
1041        let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1042        let result = msv.write_property(
1043            PropertyIdentifier::PRESENT_VALUE,
1044            None,
1045            PropertyValue::Real(1.0),
1046            None,
1047        );
1048        assert!(result.is_err());
1049    }
1050
1051    #[test]
1052    fn msv_read_object_type() {
1053        let msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1054        let val = msv
1055            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
1056            .unwrap();
1057        assert_eq!(
1058            val,
1059            PropertyValue::Enumerated(ObjectType::MULTI_STATE_VALUE.to_raw())
1060        );
1061    }
1062
1063    #[test]
1064    fn msv_read_reliability_default() {
1065        let msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1066        let val = msv
1067            .read_property(PropertyIdentifier::RELIABILITY, None)
1068            .unwrap();
1069        assert_eq!(val, PropertyValue::Enumerated(0)); // NO_FAULT_DETECTED
1070    }
1071
1072    // --- MultiStateValue direct PRIORITY_ARRAY writes ---
1073
1074    #[test]
1075    fn msv_direct_priority_array_write_value() {
1076        let mut msv = MultiStateValueObject::new(1, "MSV-1", 5).unwrap();
1077        msv.write_property(
1078            PropertyIdentifier::PRIORITY_ARRAY,
1079            Some(5),
1080            PropertyValue::Unsigned(3),
1081            None,
1082        )
1083        .unwrap();
1084        assert_eq!(
1085            msv.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1086                .unwrap(),
1087            PropertyValue::Unsigned(3)
1088        );
1089        assert_eq!(
1090            msv.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(5))
1091                .unwrap(),
1092            PropertyValue::Unsigned(3)
1093        );
1094    }
1095
1096    #[test]
1097    fn msv_direct_priority_array_relinquish() {
1098        let mut msv = MultiStateValueObject::new(1, "MSV-1", 5).unwrap();
1099        msv.write_property(
1100            PropertyIdentifier::PRIORITY_ARRAY,
1101            Some(5),
1102            PropertyValue::Unsigned(3),
1103            None,
1104        )
1105        .unwrap();
1106        msv.write_property(
1107            PropertyIdentifier::PRIORITY_ARRAY,
1108            Some(5),
1109            PropertyValue::Null,
1110            None,
1111        )
1112        .unwrap();
1113        assert_eq!(
1114            msv.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1115                .unwrap(),
1116            PropertyValue::Unsigned(1)
1117        ); // relinquish_default
1118    }
1119
1120    #[test]
1121    fn msv_direct_priority_array_no_index_error() {
1122        let mut msv = MultiStateValueObject::new(1, "MSV-1", 5).unwrap();
1123        let result = msv.write_property(
1124            PropertyIdentifier::PRIORITY_ARRAY,
1125            None,
1126            PropertyValue::Unsigned(3),
1127            None,
1128        );
1129        assert!(result.is_err());
1130    }
1131
1132    #[test]
1133    fn msv_direct_priority_array_index_zero_error() {
1134        let mut msv = MultiStateValueObject::new(1, "MSV-1", 5).unwrap();
1135        let result = msv.write_property(
1136            PropertyIdentifier::PRIORITY_ARRAY,
1137            Some(0),
1138            PropertyValue::Unsigned(3),
1139            None,
1140        );
1141        assert!(result.is_err());
1142    }
1143
1144    #[test]
1145    fn msv_direct_priority_array_index_17_error() {
1146        let mut msv = MultiStateValueObject::new(1, "MSV-1", 5).unwrap();
1147        let result = msv.write_property(
1148            PropertyIdentifier::PRIORITY_ARRAY,
1149            Some(17),
1150            PropertyValue::Unsigned(3),
1151            None,
1152        );
1153        assert!(result.is_err());
1154    }
1155
1156    #[test]
1157    fn msv_direct_priority_array_range_validation() {
1158        let mut msv = MultiStateValueObject::new(1, "MSV-1", 5).unwrap();
1159        // Value 0 is out of range (valid: 1..=5)
1160        assert!(msv
1161            .write_property(
1162                PropertyIdentifier::PRIORITY_ARRAY,
1163                Some(1),
1164                PropertyValue::Unsigned(0),
1165                None
1166            )
1167            .is_err());
1168        // Value 6 is out of range
1169        assert!(msv
1170            .write_property(
1171                PropertyIdentifier::PRIORITY_ARRAY,
1172                Some(1),
1173                PropertyValue::Unsigned(6),
1174                None
1175            )
1176            .is_err());
1177        // Value 5 is valid
1178        msv.write_property(
1179            PropertyIdentifier::PRIORITY_ARRAY,
1180            Some(1),
1181            PropertyValue::Unsigned(5),
1182            None,
1183        )
1184        .unwrap();
1185    }
1186
1187    // --- Direct PRIORITY_ARRAY writes ---
1188
1189    #[test]
1190    fn mso_direct_priority_array_write_value() {
1191        let mut mso = MultiStateOutputObject::new(1, "MSO-1", 5).unwrap();
1192        mso.write_property(
1193            PropertyIdentifier::PRIORITY_ARRAY,
1194            Some(5),
1195            PropertyValue::Unsigned(3),
1196            None,
1197        )
1198        .unwrap();
1199        assert_eq!(
1200            mso.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1201                .unwrap(),
1202            PropertyValue::Unsigned(3)
1203        );
1204        assert_eq!(
1205            mso.read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(5))
1206                .unwrap(),
1207            PropertyValue::Unsigned(3)
1208        );
1209    }
1210
1211    #[test]
1212    fn mso_direct_priority_array_relinquish() {
1213        let mut mso = MultiStateOutputObject::new(1, "MSO-1", 5).unwrap();
1214        mso.write_property(
1215            PropertyIdentifier::PRIORITY_ARRAY,
1216            Some(5),
1217            PropertyValue::Unsigned(3),
1218            None,
1219        )
1220        .unwrap();
1221        mso.write_property(
1222            PropertyIdentifier::PRIORITY_ARRAY,
1223            Some(5),
1224            PropertyValue::Null,
1225            None,
1226        )
1227        .unwrap();
1228        // Fall back to relinquish default (1)
1229        assert_eq!(
1230            mso.read_property(PropertyIdentifier::PRESENT_VALUE, None)
1231                .unwrap(),
1232            PropertyValue::Unsigned(1)
1233        );
1234    }
1235
1236    #[test]
1237    fn mso_direct_priority_array_no_index_error() {
1238        let mut mso = MultiStateOutputObject::new(1, "MSO-1", 5).unwrap();
1239        let result = mso.write_property(
1240            PropertyIdentifier::PRIORITY_ARRAY,
1241            None,
1242            PropertyValue::Unsigned(3),
1243            None,
1244        );
1245        assert!(result.is_err());
1246    }
1247
1248    #[test]
1249    fn mso_direct_priority_array_index_zero_error() {
1250        let mut mso = MultiStateOutputObject::new(1, "MSO-1", 5).unwrap();
1251        let result = mso.write_property(
1252            PropertyIdentifier::PRIORITY_ARRAY,
1253            Some(0),
1254            PropertyValue::Unsigned(3),
1255            None,
1256        );
1257        assert!(result.is_err());
1258    }
1259
1260    #[test]
1261    fn mso_direct_priority_array_index_17_error() {
1262        let mut mso = MultiStateOutputObject::new(1, "MSO-1", 5).unwrap();
1263        let result = mso.write_property(
1264            PropertyIdentifier::PRIORITY_ARRAY,
1265            Some(17),
1266            PropertyValue::Unsigned(3),
1267            None,
1268        );
1269        assert!(result.is_err());
1270    }
1271
1272    // --- state_text tests ---
1273
1274    #[test]
1275    fn msi_state_text_defaults() {
1276        let msi = MultiStateInputObject::new(1, "MSI-1", 3).unwrap();
1277        let val = msi
1278            .read_property(PropertyIdentifier::STATE_TEXT, None)
1279            .unwrap();
1280        assert_eq!(
1281            val,
1282            PropertyValue::List(vec![
1283                PropertyValue::CharacterString("State 1".into()),
1284                PropertyValue::CharacterString("State 2".into()),
1285                PropertyValue::CharacterString("State 3".into()),
1286            ])
1287        );
1288    }
1289
1290    #[test]
1291    fn msi_state_text_index_zero_returns_length() {
1292        let msi = MultiStateInputObject::new(1, "MSI-1", 4).unwrap();
1293        let val = msi
1294            .read_property(PropertyIdentifier::STATE_TEXT, Some(0))
1295            .unwrap();
1296        assert_eq!(val, PropertyValue::Unsigned(4));
1297    }
1298
1299    #[test]
1300    fn msi_state_text_valid_index() {
1301        let msi = MultiStateInputObject::new(1, "MSI-1", 3).unwrap();
1302        let val = msi
1303            .read_property(PropertyIdentifier::STATE_TEXT, Some(2))
1304            .unwrap();
1305        assert_eq!(val, PropertyValue::CharacterString("State 2".into()));
1306    }
1307
1308    #[test]
1309    fn msi_state_text_invalid_index_error() {
1310        let msi = MultiStateInputObject::new(1, "MSI-1", 3).unwrap();
1311        assert!(msi
1312            .read_property(PropertyIdentifier::STATE_TEXT, Some(4))
1313            .is_err());
1314        assert!(msi
1315            .read_property(PropertyIdentifier::STATE_TEXT, Some(100))
1316            .is_err());
1317    }
1318
1319    #[test]
1320    fn msi_state_text_write_at_index() {
1321        let mut msi = MultiStateInputObject::new(1, "MSI-1", 3).unwrap();
1322        msi.write_property(
1323            PropertyIdentifier::STATE_TEXT,
1324            Some(2),
1325            PropertyValue::CharacterString("Occupied".into()),
1326            None,
1327        )
1328        .unwrap();
1329        let val = msi
1330            .read_property(PropertyIdentifier::STATE_TEXT, Some(2))
1331            .unwrap();
1332        assert_eq!(val, PropertyValue::CharacterString("Occupied".into()));
1333    }
1334
1335    #[test]
1336    fn msi_state_text_write_wrong_type_rejected() {
1337        let mut msi = MultiStateInputObject::new(1, "MSI-1", 3).unwrap();
1338        assert!(msi
1339            .write_property(
1340                PropertyIdentifier::STATE_TEXT,
1341                Some(1),
1342                PropertyValue::Unsigned(42),
1343                None,
1344            )
1345            .is_err());
1346    }
1347
1348    #[test]
1349    fn msi_state_text_write_bad_index_rejected() {
1350        let mut msi = MultiStateInputObject::new(1, "MSI-1", 3).unwrap();
1351        // index 0 is invalid for write
1352        assert!(msi
1353            .write_property(
1354                PropertyIdentifier::STATE_TEXT,
1355                Some(0),
1356                PropertyValue::CharacterString("X".into()),
1357                None,
1358            )
1359            .is_err());
1360        // out-of-range index
1361        assert!(msi
1362            .write_property(
1363                PropertyIdentifier::STATE_TEXT,
1364                Some(4),
1365                PropertyValue::CharacterString("X".into()),
1366                None,
1367            )
1368            .is_err());
1369        // no index
1370        assert!(msi
1371            .write_property(
1372                PropertyIdentifier::STATE_TEXT,
1373                None,
1374                PropertyValue::CharacterString("X".into()),
1375                None,
1376            )
1377            .is_err());
1378    }
1379
1380    #[test]
1381    fn msi_state_text_in_property_list() {
1382        let msi = MultiStateInputObject::new(1, "MSI-1", 3).unwrap();
1383        assert!(msi
1384            .property_list()
1385            .contains(&PropertyIdentifier::STATE_TEXT));
1386    }
1387
1388    #[test]
1389    fn mso_state_text_defaults() {
1390        let mso = MultiStateOutputObject::new(1, "MSO-1", 2).unwrap();
1391        let val = mso
1392            .read_property(PropertyIdentifier::STATE_TEXT, None)
1393            .unwrap();
1394        assert_eq!(
1395            val,
1396            PropertyValue::List(vec![
1397                PropertyValue::CharacterString("State 1".into()),
1398                PropertyValue::CharacterString("State 2".into()),
1399            ])
1400        );
1401    }
1402
1403    #[test]
1404    fn mso_state_text_index_zero_returns_length() {
1405        let mso = MultiStateOutputObject::new(1, "MSO-1", 5).unwrap();
1406        let val = mso
1407            .read_property(PropertyIdentifier::STATE_TEXT, Some(0))
1408            .unwrap();
1409        assert_eq!(val, PropertyValue::Unsigned(5));
1410    }
1411
1412    #[test]
1413    fn mso_state_text_valid_index() {
1414        let mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
1415        let val = mso
1416            .read_property(PropertyIdentifier::STATE_TEXT, Some(3))
1417            .unwrap();
1418        assert_eq!(val, PropertyValue::CharacterString("State 3".into()));
1419    }
1420
1421    #[test]
1422    fn mso_state_text_invalid_index_error() {
1423        let mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
1424        assert!(mso
1425            .read_property(PropertyIdentifier::STATE_TEXT, Some(4))
1426            .is_err());
1427    }
1428
1429    #[test]
1430    fn mso_state_text_write_at_index() {
1431        let mut mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
1432        mso.write_property(
1433            PropertyIdentifier::STATE_TEXT,
1434            Some(1),
1435            PropertyValue::CharacterString("Low".into()),
1436            None,
1437        )
1438        .unwrap();
1439        assert_eq!(
1440            mso.read_property(PropertyIdentifier::STATE_TEXT, Some(1))
1441                .unwrap(),
1442            PropertyValue::CharacterString("Low".into())
1443        );
1444    }
1445
1446    #[test]
1447    fn mso_state_text_in_property_list() {
1448        let mso = MultiStateOutputObject::new(1, "MSO-1", 3).unwrap();
1449        assert!(mso
1450            .property_list()
1451            .contains(&PropertyIdentifier::STATE_TEXT));
1452    }
1453
1454    #[test]
1455    fn msv_state_text_defaults() {
1456        let msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1457        let val = msv
1458            .read_property(PropertyIdentifier::STATE_TEXT, None)
1459            .unwrap();
1460        assert_eq!(
1461            val,
1462            PropertyValue::List(vec![
1463                PropertyValue::CharacterString("State 1".into()),
1464                PropertyValue::CharacterString("State 2".into()),
1465                PropertyValue::CharacterString("State 3".into()),
1466            ])
1467        );
1468    }
1469
1470    #[test]
1471    fn msv_state_text_index_zero_returns_length() {
1472        let msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1473        let val = msv
1474            .read_property(PropertyIdentifier::STATE_TEXT, Some(0))
1475            .unwrap();
1476        assert_eq!(val, PropertyValue::Unsigned(3));
1477    }
1478
1479    #[test]
1480    fn msv_state_text_valid_index() {
1481        let msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1482        let val = msv
1483            .read_property(PropertyIdentifier::STATE_TEXT, Some(1))
1484            .unwrap();
1485        assert_eq!(val, PropertyValue::CharacterString("State 1".into()));
1486    }
1487
1488    #[test]
1489    fn msv_state_text_invalid_index_error() {
1490        let msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1491        assert!(msv
1492            .read_property(PropertyIdentifier::STATE_TEXT, Some(4))
1493            .is_err());
1494        assert!(msv
1495            .read_property(PropertyIdentifier::STATE_TEXT, Some(0xFF))
1496            .is_err());
1497    }
1498
1499    #[test]
1500    fn msv_state_text_write_at_index() {
1501        let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1502        msv.write_property(
1503            PropertyIdentifier::STATE_TEXT,
1504            Some(2),
1505            PropertyValue::CharacterString("Comfort".into()),
1506            None,
1507        )
1508        .unwrap();
1509        assert_eq!(
1510            msv.read_property(PropertyIdentifier::STATE_TEXT, Some(2))
1511                .unwrap(),
1512            PropertyValue::CharacterString("Comfort".into())
1513        );
1514    }
1515
1516    #[test]
1517    fn msv_state_text_write_bad_index_rejected() {
1518        let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1519        assert!(msv
1520            .write_property(
1521                PropertyIdentifier::STATE_TEXT,
1522                None,
1523                PropertyValue::CharacterString("X".into()),
1524                None,
1525            )
1526            .is_err());
1527        assert!(msv
1528            .write_property(
1529                PropertyIdentifier::STATE_TEXT,
1530                Some(0),
1531                PropertyValue::CharacterString("X".into()),
1532                None,
1533            )
1534            .is_err());
1535        assert!(msv
1536            .write_property(
1537                PropertyIdentifier::STATE_TEXT,
1538                Some(4),
1539                PropertyValue::CharacterString("X".into()),
1540                None,
1541            )
1542            .is_err());
1543    }
1544
1545    #[test]
1546    fn msv_state_text_write_wrong_type_rejected() {
1547        let mut msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1548        assert!(msv
1549            .write_property(
1550                PropertyIdentifier::STATE_TEXT,
1551                Some(1),
1552                PropertyValue::Unsigned(1),
1553                None,
1554            )
1555            .is_err());
1556    }
1557
1558    #[test]
1559    fn msv_state_text_in_property_list() {
1560        let msv = MultiStateValueObject::new(1, "MSV-1", 3).unwrap();
1561        assert!(msv
1562            .property_list()
1563            .contains(&PropertyIdentifier::STATE_TEXT));
1564    }
1565}