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