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