Skip to main content

bacnet_objects/
binary.rs

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