Skip to main content

bacnet_objects/
value_types.rs

1//! Extended value object types (ASHRAE 135-2020 Clause 12).
2//!
3//! The 12 "value" object types share a common structure: present_value, status_flags,
4//! out_of_service, reliability, and (for commandable types) a 16-level priority array.
5//!
6//! A `define_value_object!` macro generates the struct + BACnetObject impl for each type.
7
8use bacnet_types::enums::{ObjectType, PropertyIdentifier};
9use bacnet_types::error::Error;
10use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
11use std::borrow::Cow;
12
13use crate::common::{self, read_common_properties};
14use crate::traits::BACnetObject;
15
16// ---------------------------------------------------------------------------
17// Macro: define_value_object! (commandable variant)
18// ---------------------------------------------------------------------------
19
20/// Generate a commandable value object type with a 16-level priority array.
21///
22/// Copy types use the existing `recalculate_from_priority_array` helper;
23/// non-Copy types (String, Vec, tuples containing Vec) use a Clone-based
24/// inline recalculation.
25macro_rules! define_value_object_commandable {
26    (
27        name: $struct_name:ident,
28        doc: $doc:expr,
29        object_type: $obj_type:expr,
30        value_type: $val_type:ty,
31        default_value: $default:expr,
32        pv_to_property: $pv_to_prop:expr,
33        property_to_pv: $prop_to_pv:expr,
34        pa_wrap: $pa_wrap:expr,
35        rd_wrap: $rd_wrap:expr,
36        copy_type: $is_copy:tt
37        $(,)?
38    ) => {
39        #[doc = $doc]
40        pub struct $struct_name {
41            oid: ObjectIdentifier,
42            name: String,
43            description: String,
44            present_value: $val_type,
45            out_of_service: bool,
46            status_flags: StatusFlags,
47            reliability: u32,
48            /// 16-level priority array. `None` = no command at that level.
49            priority_array: [Option<$val_type>; 16],
50            relinquish_default: $val_type,
51        }
52
53        impl $struct_name {
54            /// Create a new instance of this value object.
55            pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
56                let oid = ObjectIdentifier::new($obj_type, instance)?;
57                Ok(Self {
58                    oid,
59                    name: name.into(),
60                    description: String::new(),
61                    present_value: $default,
62                    out_of_service: false,
63                    status_flags: StatusFlags::empty(),
64                    reliability: 0,
65                    priority_array: Default::default(),
66                    relinquish_default: $default,
67                })
68            }
69
70            /// Recalculate present_value from the priority array.
71            fn recalculate_present_value(&mut self) {
72                define_value_object_commandable!(@recalc self, $is_copy);
73            }
74        }
75
76        impl BACnetObject for $struct_name {
77            fn object_identifier(&self) -> ObjectIdentifier {
78                self.oid
79            }
80
81            fn object_name(&self) -> &str {
82                &self.name
83            }
84
85            fn read_property(
86                &self,
87                property: PropertyIdentifier,
88                array_index: Option<u32>,
89            ) -> Result<PropertyValue, Error> {
90                if let Some(result) = read_common_properties!(self, property, array_index) {
91                    return result;
92                }
93                match property {
94                    p if p == PropertyIdentifier::OBJECT_TYPE => {
95                        Ok(PropertyValue::Enumerated($obj_type.to_raw()))
96                    }
97                    p if p == PropertyIdentifier::PRESENT_VALUE => {
98                        Ok(($pv_to_prop)(&self.present_value))
99                    }
100                    p if p == PropertyIdentifier::PRIORITY_ARRAY => {
101                        define_value_object_commandable!(@read_pa self, array_index, $pa_wrap, $is_copy)
102                    }
103                    p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
104                        Ok(($rd_wrap)(&self.relinquish_default))
105                    }
106                    _ => Err(common::unknown_property_error()),
107                }
108            }
109
110            fn write_property(
111                &mut self,
112                property: PropertyIdentifier,
113                array_index: Option<u32>,
114                value: PropertyValue,
115                priority: Option<u8>,
116            ) -> Result<(), Error> {
117                // Handle PRIORITY_ARRAY direct writes
118                if property == PropertyIdentifier::PRIORITY_ARRAY {
119                    let idx = match array_index {
120                        Some(n) if (1..=16).contains(&n) => (n - 1) as usize,
121                        Some(_) => return Err(common::invalid_array_index_error()),
122                        None => {
123                            return Err(Error::Encoding(
124                                "PRIORITY_ARRAY requires array_index (1-16)".into(),
125                            ))
126                        }
127                    };
128                    match value {
129                        PropertyValue::Null => {
130                            self.priority_array[idx] = None;
131                        }
132                        other => {
133                            let extracted = ($prop_to_pv)(other)?;
134                            self.priority_array[idx] = Some(extracted);
135                        }
136                    }
137                    self.recalculate_present_value();
138                    return Ok(());
139                }
140                // Handle PRESENT_VALUE via priority array
141                if property == PropertyIdentifier::PRESENT_VALUE {
142                    let prio = priority.unwrap_or(16);
143                    if !(1..=16).contains(&prio) {
144                        return Err(common::value_out_of_range_error());
145                    }
146                    let idx = (prio - 1) as usize;
147                    match value {
148                        PropertyValue::Null => {
149                            self.priority_array[idx] = None;
150                        }
151                        other => {
152                            let extracted = ($prop_to_pv)(other)?;
153                            self.priority_array[idx] = Some(extracted);
154                        }
155                    }
156                    self.recalculate_present_value();
157                    return Ok(());
158                }
159                if let Some(result) =
160                    common::write_out_of_service(&mut self.out_of_service, property, &value)
161                {
162                    return result;
163                }
164                if let Some(result) =
165                    common::write_description(&mut self.description, property, &value)
166                {
167                    return result;
168                }
169                Err(common::write_access_denied_error())
170            }
171
172            fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
173                static PROPS: &[PropertyIdentifier] = &[
174                    PropertyIdentifier::OBJECT_IDENTIFIER,
175                    PropertyIdentifier::OBJECT_NAME,
176                    PropertyIdentifier::DESCRIPTION,
177                    PropertyIdentifier::OBJECT_TYPE,
178                    PropertyIdentifier::PRESENT_VALUE,
179                    PropertyIdentifier::STATUS_FLAGS,
180                    PropertyIdentifier::OUT_OF_SERVICE,
181                    PropertyIdentifier::RELIABILITY,
182                    PropertyIdentifier::PRIORITY_ARRAY,
183                    PropertyIdentifier::RELINQUISH_DEFAULT,
184                ];
185                Cow::Borrowed(PROPS)
186            }
187        }
188    };
189
190    // --- Internal helper arms ---
191
192    // Recalculate for Copy types: use the common helper.
193    (@recalc $self:ident, copy) => {
194        $self.present_value = common::recalculate_from_priority_array(
195            &$self.priority_array,
196            $self.relinquish_default,
197        );
198    };
199    // Recalculate for Clone (non-Copy) types.
200    (@recalc $self:ident, clone) => {
201        $self.present_value = $self
202            .priority_array
203            .iter()
204            .find_map(|slot| slot.as_ref().cloned())
205            .unwrap_or_else(|| $self.relinquish_default.clone());
206    };
207
208    // Read priority array for Copy types: use the common macro.
209    (@read_pa $self:ident, $array_index:ident, $wrap:expr, copy) => {{
210        common::read_priority_array!($self, $array_index, $wrap)
211    }};
212    // Read priority array for Clone (non-Copy) types.
213    (@read_pa $self:ident, $array_index:ident, $wrap:expr, clone) => {{
214        let wrap_fn = $wrap;
215        match $array_index {
216            None => {
217                let elements = $self
218                    .priority_array
219                    .iter()
220                    .map(|slot| match slot {
221                        Some(v) => wrap_fn(v),
222                        None => PropertyValue::Null,
223                    })
224                    .collect();
225                Ok(PropertyValue::List(elements))
226            }
227            Some(0) => Ok(PropertyValue::Unsigned(16)),
228            Some(idx) if (1..=16).contains(&idx) => {
229                match &$self.priority_array[(idx - 1) as usize] {
230                    Some(v) => Ok(wrap_fn(v)),
231                    None => Ok(PropertyValue::Null),
232                }
233            }
234            _ => Err(common::invalid_array_index_error()),
235        }
236    }};
237}
238
239// ---------------------------------------------------------------------------
240// Macro: define_value_object! (non-commandable variant)
241// ---------------------------------------------------------------------------
242
243/// Generate a non-commandable value object type (simple read/write PV).
244macro_rules! define_value_object_simple {
245    (
246        name: $struct_name:ident,
247        doc: $doc:expr,
248        object_type: $obj_type:expr,
249        value_type: $val_type:ty,
250        default_value: $default:expr,
251        pv_to_property: $pv_to_prop:expr,
252        property_to_pv: $prop_to_pv:expr
253        $(,)?
254    ) => {
255        #[doc = $doc]
256        pub struct $struct_name {
257            oid: ObjectIdentifier,
258            name: String,
259            description: String,
260            present_value: $val_type,
261            out_of_service: bool,
262            status_flags: StatusFlags,
263            reliability: u32,
264        }
265
266        impl $struct_name {
267            /// Create a new instance of this value object.
268            pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
269                let oid = ObjectIdentifier::new($obj_type, instance)?;
270                Ok(Self {
271                    oid,
272                    name: name.into(),
273                    description: String::new(),
274                    present_value: $default,
275                    out_of_service: false,
276                    status_flags: StatusFlags::empty(),
277                    reliability: 0,
278                })
279            }
280        }
281
282        impl BACnetObject for $struct_name {
283            fn object_identifier(&self) -> ObjectIdentifier {
284                self.oid
285            }
286
287            fn object_name(&self) -> &str {
288                &self.name
289            }
290
291            fn read_property(
292                &self,
293                property: PropertyIdentifier,
294                array_index: Option<u32>,
295            ) -> Result<PropertyValue, Error> {
296                if let Some(result) = read_common_properties!(self, property, array_index) {
297                    return result;
298                }
299                match property {
300                    p if p == PropertyIdentifier::OBJECT_TYPE => {
301                        Ok(PropertyValue::Enumerated($obj_type.to_raw()))
302                    }
303                    p if p == PropertyIdentifier::PRESENT_VALUE => {
304                        Ok(($pv_to_prop)(&self.present_value))
305                    }
306                    _ => Err(common::unknown_property_error()),
307                }
308            }
309
310            fn write_property(
311                &mut self,
312                property: PropertyIdentifier,
313                _array_index: Option<u32>,
314                value: PropertyValue,
315                _priority: Option<u8>,
316            ) -> Result<(), Error> {
317                if property == PropertyIdentifier::PRESENT_VALUE {
318                    let extracted = ($prop_to_pv)(value)?;
319                    self.present_value = extracted;
320                    return Ok(());
321                }
322                if let Some(result) =
323                    common::write_out_of_service(&mut self.out_of_service, property, &value)
324                {
325                    return result;
326                }
327                if let Some(result) =
328                    common::write_description(&mut self.description, property, &value)
329                {
330                    return result;
331                }
332                Err(common::write_access_denied_error())
333            }
334
335            fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
336                static PROPS: &[PropertyIdentifier] = &[
337                    PropertyIdentifier::OBJECT_IDENTIFIER,
338                    PropertyIdentifier::OBJECT_NAME,
339                    PropertyIdentifier::DESCRIPTION,
340                    PropertyIdentifier::OBJECT_TYPE,
341                    PropertyIdentifier::PRESENT_VALUE,
342                    PropertyIdentifier::STATUS_FLAGS,
343                    PropertyIdentifier::OUT_OF_SERVICE,
344                    PropertyIdentifier::RELIABILITY,
345                ];
346                Cow::Borrowed(PROPS)
347            }
348        }
349    };
350}
351
352// ---------------------------------------------------------------------------
353// Helper functions for value conversions
354// ---------------------------------------------------------------------------
355
356fn clone_string_to_pv(v: &str) -> PropertyValue {
357    PropertyValue::CharacterString(v.to_owned())
358}
359
360fn clone_octetstring_to_pv(v: &[u8]) -> PropertyValue {
361    PropertyValue::OctetString(v.to_owned())
362}
363
364fn clone_bitstring_to_pv(v: &(u8, Vec<u8>)) -> PropertyValue {
365    PropertyValue::BitString {
366        unused_bits: v.0,
367        data: v.1.clone(),
368    }
369}
370
371fn datetime_copy_to_pv(v: (Date, Time)) -> PropertyValue {
372    PropertyValue::List(vec![PropertyValue::Date(v.0), PropertyValue::Time(v.1)])
373}
374
375fn datetime_to_pv(dt: &(Date, Time)) -> PropertyValue {
376    PropertyValue::List(vec![PropertyValue::Date(dt.0), PropertyValue::Time(dt.1)])
377}
378
379fn pv_to_date(v: PropertyValue) -> Result<Date, Error> {
380    if let PropertyValue::Date(d) = v {
381        Ok(d)
382    } else {
383        Err(common::invalid_data_type_error())
384    }
385}
386
387fn pv_to_time(v: PropertyValue) -> Result<Time, Error> {
388    if let PropertyValue::Time(t) = v {
389        Ok(t)
390    } else {
391        Err(common::invalid_data_type_error())
392    }
393}
394
395fn pv_to_datetime(v: PropertyValue) -> Result<(Date, Time), Error> {
396    if let PropertyValue::List(items) = v {
397        if items.len() == 2 {
398            let d = if let PropertyValue::Date(d) = &items[0] {
399                *d
400            } else {
401                return Err(common::invalid_data_type_error());
402            };
403            let t = if let PropertyValue::Time(t) = &items[1] {
404                *t
405            } else {
406                return Err(common::invalid_data_type_error());
407            };
408            Ok((d, t))
409        } else {
410            Err(common::invalid_data_type_error())
411        }
412    } else {
413        Err(common::invalid_data_type_error())
414    }
415}
416
417// ---------------------------------------------------------------------------
418// 9 Commandable value objects
419// ---------------------------------------------------------------------------
420
421define_value_object_commandable! {
422    name: IntegerValueObject,
423    doc: "BACnet Integer Value object (type 45).",
424    object_type: ObjectType::INTEGER_VALUE,
425    value_type: i32,
426    default_value: 0,
427    pv_to_property: (|v: &i32| PropertyValue::Signed(*v)),
428    property_to_pv: (|v: PropertyValue| -> Result<i32, Error> {
429        if let PropertyValue::Signed(n) = v { Ok(n) }
430        else { Err(common::invalid_data_type_error()) }
431    }),
432    pa_wrap: PropertyValue::Signed,
433    rd_wrap: (|v: &i32| PropertyValue::Signed(*v)),
434    copy_type: copy,
435}
436
437define_value_object_commandable! {
438    name: PositiveIntegerValueObject,
439    doc: "BACnet Positive Integer Value object (type 48).",
440    object_type: ObjectType::POSITIVE_INTEGER_VALUE,
441    value_type: u64,
442    default_value: 0,
443    pv_to_property: (|v: &u64| PropertyValue::Unsigned(*v)),
444    property_to_pv: (|v: PropertyValue| -> Result<u64, Error> {
445        if let PropertyValue::Unsigned(n) = v { Ok(n) }
446        else { Err(common::invalid_data_type_error()) }
447    }),
448    pa_wrap: PropertyValue::Unsigned,
449    rd_wrap: (|v: &u64| PropertyValue::Unsigned(*v)),
450    copy_type: copy,
451}
452
453define_value_object_commandable! {
454    name: LargeAnalogValueObject,
455    doc: "BACnet Large Analog Value object (type 46).",
456    object_type: ObjectType::LARGE_ANALOG_VALUE,
457    value_type: f64,
458    default_value: 0.0,
459    pv_to_property: (|v: &f64| PropertyValue::Double(*v)),
460    property_to_pv: (|v: PropertyValue| -> Result<f64, Error> {
461        if let PropertyValue::Double(n) = v { Ok(n) }
462        else { Err(common::invalid_data_type_error()) }
463    }),
464    pa_wrap: PropertyValue::Double,
465    rd_wrap: (|v: &f64| PropertyValue::Double(*v)),
466    copy_type: copy,
467}
468
469define_value_object_commandable! {
470    name: CharacterStringValueObject,
471    doc: "BACnet CharacterString Value object (type 40).",
472    object_type: ObjectType::CHARACTERSTRING_VALUE,
473    value_type: String,
474    default_value: String::new(),
475    pv_to_property: (|v: &String| PropertyValue::CharacterString(v.clone())),
476    property_to_pv: (|v: PropertyValue| -> Result<String, Error> {
477        if let PropertyValue::CharacterString(s) = v { Ok(s) }
478        else { Err(common::invalid_data_type_error()) }
479    }),
480    pa_wrap: clone_string_to_pv,
481    rd_wrap: (|v: &String| PropertyValue::CharacterString(v.clone())),
482    copy_type: clone,
483}
484
485define_value_object_commandable! {
486    name: OctetStringValueObject,
487    doc: "BACnet OctetString Value object (type 47).",
488    object_type: ObjectType::OCTETSTRING_VALUE,
489    value_type: Vec<u8>,
490    default_value: Vec::new(),
491    pv_to_property: (|v: &Vec<u8>| PropertyValue::OctetString(v.clone())),
492    property_to_pv: (|v: PropertyValue| -> Result<Vec<u8>, Error> {
493        if let PropertyValue::OctetString(b) = v { Ok(b) }
494        else { Err(common::invalid_data_type_error()) }
495    }),
496    pa_wrap: clone_octetstring_to_pv,
497    rd_wrap: (|v: &Vec<u8>| PropertyValue::OctetString(v.clone())),
498    copy_type: clone,
499}
500
501define_value_object_commandable! {
502    name: BitStringValueObject,
503    doc: "BACnet BitString Value object (type 39).",
504    object_type: ObjectType::BITSTRING_VALUE,
505    value_type: (u8, Vec<u8>),
506    default_value: (0, Vec::new()),
507    pv_to_property: (|v: &(u8, Vec<u8>)| PropertyValue::BitString {
508        unused_bits: v.0,
509        data: v.1.clone(),
510    }),
511    property_to_pv: (|v: PropertyValue| -> Result<(u8, Vec<u8>), Error> {
512        if let PropertyValue::BitString { unused_bits, data } = v { Ok((unused_bits, data)) }
513        else { Err(common::invalid_data_type_error()) }
514    }),
515    pa_wrap: clone_bitstring_to_pv,
516    rd_wrap: (|v: &(u8, Vec<u8>)| PropertyValue::BitString {
517        unused_bits: v.0,
518        data: v.1.clone(),
519    }),
520    copy_type: clone,
521}
522
523define_value_object_commandable! {
524    name: DateValueObject,
525    doc: "BACnet Date Value object (type 42).",
526    object_type: ObjectType::DATE_VALUE,
527    value_type: Date,
528    default_value: Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
529    pv_to_property: (|v: &Date| PropertyValue::Date(*v)),
530    property_to_pv: pv_to_date,
531    pa_wrap: PropertyValue::Date,
532    rd_wrap: (|v: &Date| PropertyValue::Date(*v)),
533    copy_type: copy,
534}
535
536define_value_object_commandable! {
537    name: TimeValueObject,
538    doc: "BACnet Time Value object (type 50).",
539    object_type: ObjectType::TIME_VALUE,
540    value_type: Time,
541    default_value: Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
542    pv_to_property: (|v: &Time| PropertyValue::Time(*v)),
543    property_to_pv: pv_to_time,
544    pa_wrap: PropertyValue::Time,
545    rd_wrap: (|v: &Time| PropertyValue::Time(*v)),
546    copy_type: copy,
547}
548
549define_value_object_commandable! {
550    name: DateTimeValueObject,
551    doc: "BACnet DateTime Value object (type 44).",
552    object_type: ObjectType::DATETIME_VALUE,
553    value_type: (Date, Time),
554    default_value: (
555        Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
556        Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
557    ),
558    pv_to_property: (|v: &(Date, Time)| datetime_to_pv(v)),
559    property_to_pv: pv_to_datetime,
560    pa_wrap: datetime_copy_to_pv,
561    rd_wrap: (|v: &(Date, Time)| datetime_to_pv(v)),
562    copy_type: copy,
563}
564
565// ---------------------------------------------------------------------------
566// 3 Non-commandable pattern value objects
567// ---------------------------------------------------------------------------
568
569define_value_object_simple! {
570    name: DatePatternValueObject,
571    doc: "BACnet Date Pattern Value object (type 41).",
572    object_type: ObjectType::DATEPATTERN_VALUE,
573    value_type: Date,
574    default_value: Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
575    pv_to_property: (|v: &Date| PropertyValue::Date(*v)),
576    property_to_pv: pv_to_date,
577}
578
579define_value_object_simple! {
580    name: TimePatternValueObject,
581    doc: "BACnet Time Pattern Value object (type 49).",
582    object_type: ObjectType::TIMEPATTERN_VALUE,
583    value_type: Time,
584    default_value: Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
585    pv_to_property: (|v: &Time| PropertyValue::Time(*v)),
586    property_to_pv: pv_to_time,
587}
588
589define_value_object_simple! {
590    name: DateTimePatternValueObject,
591    doc: "BACnet DateTime Pattern Value object (type 43).",
592    object_type: ObjectType::DATETIMEPATTERN_VALUE,
593    value_type: (Date, Time),
594    default_value: (
595        Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
596        Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
597    ),
598    pv_to_property: (|v: &(Date, Time)| datetime_to_pv(v)),
599    property_to_pv: pv_to_datetime,
600}
601
602// ---------------------------------------------------------------------------
603// Tests
604// ---------------------------------------------------------------------------
605
606#[cfg(test)]
607mod tests {
608    use super::*;
609    use bacnet_types::enums::ObjectType;
610
611    // -----------------------------------------------------------------------
612    // IntegerValueObject
613    // -----------------------------------------------------------------------
614
615    #[test]
616    fn integer_value_construct_and_read_object_type() {
617        let obj = IntegerValueObject::new(1, "IV-1").unwrap();
618        let ot = obj
619            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
620            .unwrap();
621        assert_eq!(
622            ot,
623            PropertyValue::Enumerated(ObjectType::INTEGER_VALUE.to_raw())
624        );
625    }
626
627    #[test]
628    fn integer_value_read_write_pv() {
629        let mut obj = IntegerValueObject::new(1, "IV-1").unwrap();
630        // Default PV is 0
631        let pv = obj
632            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
633            .unwrap();
634        assert_eq!(pv, PropertyValue::Signed(0));
635
636        // Write via priority 8
637        obj.write_property(
638            PropertyIdentifier::PRESENT_VALUE,
639            None,
640            PropertyValue::Signed(-42),
641            Some(8),
642        )
643        .unwrap();
644        let pv = obj
645            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
646            .unwrap();
647        assert_eq!(pv, PropertyValue::Signed(-42));
648    }
649
650    #[test]
651    fn integer_value_priority_array() {
652        let mut obj = IntegerValueObject::new(1, "IV-1").unwrap();
653        // Write at priority 10
654        obj.write_property(
655            PropertyIdentifier::PRESENT_VALUE,
656            None,
657            PropertyValue::Signed(100),
658            Some(10),
659        )
660        .unwrap();
661        // Write at priority 5 (should win)
662        obj.write_property(
663            PropertyIdentifier::PRESENT_VALUE,
664            None,
665            PropertyValue::Signed(50),
666            Some(5),
667        )
668        .unwrap();
669        let pv = obj
670            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
671            .unwrap();
672        assert_eq!(pv, PropertyValue::Signed(50));
673
674        // Relinquish priority 5 — priority 10 takes over
675        obj.write_property(
676            PropertyIdentifier::PRESENT_VALUE,
677            None,
678            PropertyValue::Null,
679            Some(5),
680        )
681        .unwrap();
682        let pv = obj
683            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
684            .unwrap();
685        assert_eq!(pv, PropertyValue::Signed(100));
686
687        // Read priority array size via array_index 0
688        let pa_size = obj
689            .read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(0))
690            .unwrap();
691        assert_eq!(pa_size, PropertyValue::Unsigned(16));
692    }
693
694    #[test]
695    fn integer_value_invalid_data_type() {
696        let mut obj = IntegerValueObject::new(1, "IV-1").unwrap();
697        let result = obj.write_property(
698            PropertyIdentifier::PRESENT_VALUE,
699            None,
700            PropertyValue::CharacterString("bad".into()),
701            Some(16),
702        );
703        assert!(result.is_err());
704    }
705
706    // -----------------------------------------------------------------------
707    // PositiveIntegerValueObject
708    // -----------------------------------------------------------------------
709
710    #[test]
711    fn positive_integer_value_read_write() {
712        let mut obj = PositiveIntegerValueObject::new(1, "PIV-1").unwrap();
713        let pv = obj
714            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
715            .unwrap();
716        assert_eq!(pv, PropertyValue::Unsigned(0));
717
718        obj.write_property(
719            PropertyIdentifier::PRESENT_VALUE,
720            None,
721            PropertyValue::Unsigned(9999),
722            Some(16),
723        )
724        .unwrap();
725        let pv = obj
726            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
727            .unwrap();
728        assert_eq!(pv, PropertyValue::Unsigned(9999));
729    }
730
731    #[test]
732    fn positive_integer_value_object_type() {
733        let obj = PositiveIntegerValueObject::new(1, "PIV-1").unwrap();
734        let ot = obj
735            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
736            .unwrap();
737        assert_eq!(
738            ot,
739            PropertyValue::Enumerated(ObjectType::POSITIVE_INTEGER_VALUE.to_raw())
740        );
741    }
742
743    // -----------------------------------------------------------------------
744    // LargeAnalogValueObject
745    // -----------------------------------------------------------------------
746
747    #[test]
748    fn large_analog_value_read_write() {
749        let mut obj = LargeAnalogValueObject::new(1, "LAV-1").unwrap();
750        obj.write_property(
751            PropertyIdentifier::PRESENT_VALUE,
752            None,
753            PropertyValue::Double(1.23456789012345),
754            Some(16),
755        )
756        .unwrap();
757        let pv = obj
758            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
759            .unwrap();
760        assert_eq!(pv, PropertyValue::Double(1.23456789012345));
761    }
762
763    #[test]
764    fn large_analog_value_object_type() {
765        let obj = LargeAnalogValueObject::new(1, "LAV-1").unwrap();
766        let ot = obj
767            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
768            .unwrap();
769        assert_eq!(
770            ot,
771            PropertyValue::Enumerated(ObjectType::LARGE_ANALOG_VALUE.to_raw())
772        );
773    }
774
775    // -----------------------------------------------------------------------
776    // CharacterStringValueObject
777    // -----------------------------------------------------------------------
778
779    #[test]
780    fn characterstring_value_read_write() {
781        let mut obj = CharacterStringValueObject::new(1, "CSV-1").unwrap();
782        let pv = obj
783            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
784            .unwrap();
785        assert_eq!(pv, PropertyValue::CharacterString(String::new()));
786
787        obj.write_property(
788            PropertyIdentifier::PRESENT_VALUE,
789            None,
790            PropertyValue::CharacterString("hello world".into()),
791            Some(16),
792        )
793        .unwrap();
794        let pv = obj
795            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
796            .unwrap();
797        assert_eq!(pv, PropertyValue::CharacterString("hello world".into()));
798    }
799
800    #[test]
801    fn characterstring_value_priority_array() {
802        let mut obj = CharacterStringValueObject::new(1, "CSV-1").unwrap();
803        obj.write_property(
804            PropertyIdentifier::PRESENT_VALUE,
805            None,
806            PropertyValue::CharacterString("low".into()),
807            Some(16),
808        )
809        .unwrap();
810        obj.write_property(
811            PropertyIdentifier::PRESENT_VALUE,
812            None,
813            PropertyValue::CharacterString("high".into()),
814            Some(1),
815        )
816        .unwrap();
817        let pv = obj
818            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
819            .unwrap();
820        assert_eq!(pv, PropertyValue::CharacterString("high".into()));
821
822        // Relinquish priority 1 — low takes over
823        obj.write_property(
824            PropertyIdentifier::PRESENT_VALUE,
825            None,
826            PropertyValue::Null,
827            Some(1),
828        )
829        .unwrap();
830        let pv = obj
831            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
832            .unwrap();
833        assert_eq!(pv, PropertyValue::CharacterString("low".into()));
834    }
835
836    // -----------------------------------------------------------------------
837    // OctetStringValueObject
838    // -----------------------------------------------------------------------
839
840    #[test]
841    fn octetstring_value_read_write() {
842        let mut obj = OctetStringValueObject::new(1, "OSV-1").unwrap();
843        obj.write_property(
844            PropertyIdentifier::PRESENT_VALUE,
845            None,
846            PropertyValue::OctetString(vec![0xDE, 0xAD, 0xBE, 0xEF]),
847            Some(16),
848        )
849        .unwrap();
850        let pv = obj
851            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
852            .unwrap();
853        assert_eq!(pv, PropertyValue::OctetString(vec![0xDE, 0xAD, 0xBE, 0xEF]));
854    }
855
856    #[test]
857    fn octetstring_value_object_type() {
858        let obj = OctetStringValueObject::new(1, "OSV-1").unwrap();
859        let ot = obj
860            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
861            .unwrap();
862        assert_eq!(
863            ot,
864            PropertyValue::Enumerated(ObjectType::OCTETSTRING_VALUE.to_raw())
865        );
866    }
867
868    // -----------------------------------------------------------------------
869    // BitStringValueObject
870    // -----------------------------------------------------------------------
871
872    #[test]
873    fn bitstring_value_read_write() {
874        let mut obj = BitStringValueObject::new(1, "BSV-1").unwrap();
875        obj.write_property(
876            PropertyIdentifier::PRESENT_VALUE,
877            None,
878            PropertyValue::BitString {
879                unused_bits: 3,
880                data: vec![0b11010000],
881            },
882            Some(16),
883        )
884        .unwrap();
885        let pv = obj
886            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
887            .unwrap();
888        assert_eq!(
889            pv,
890            PropertyValue::BitString {
891                unused_bits: 3,
892                data: vec![0b11010000],
893            }
894        );
895    }
896
897    #[test]
898    fn bitstring_value_object_type() {
899        let obj = BitStringValueObject::new(1, "BSV-1").unwrap();
900        let ot = obj
901            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
902            .unwrap();
903        assert_eq!(
904            ot,
905            PropertyValue::Enumerated(ObjectType::BITSTRING_VALUE.to_raw())
906        );
907    }
908
909    // -----------------------------------------------------------------------
910    // DateValueObject
911    // -----------------------------------------------------------------------
912
913    #[test]
914    fn date_value_read_write() {
915        let mut obj = DateValueObject::new(1, "DV-1").unwrap();
916        let d = Date {
917            year: 124,
918            month: 3,
919            day: 15,
920            day_of_week: 5,
921        };
922        obj.write_property(
923            PropertyIdentifier::PRESENT_VALUE,
924            None,
925            PropertyValue::Date(d),
926            Some(16),
927        )
928        .unwrap();
929        let pv = obj
930            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
931            .unwrap();
932        assert_eq!(pv, PropertyValue::Date(d));
933    }
934
935    #[test]
936    fn date_value_priority_array() {
937        let mut obj = DateValueObject::new(1, "DV-1").unwrap();
938        let d1 = Date {
939            year: 124,
940            month: 1,
941            day: 1,
942            day_of_week: 1,
943        };
944        let d2 = Date {
945            year: 124,
946            month: 12,
947            day: 25,
948            day_of_week: 3,
949        };
950        obj.write_property(
951            PropertyIdentifier::PRESENT_VALUE,
952            None,
953            PropertyValue::Date(d1),
954            Some(16),
955        )
956        .unwrap();
957        obj.write_property(
958            PropertyIdentifier::PRESENT_VALUE,
959            None,
960            PropertyValue::Date(d2),
961            Some(8),
962        )
963        .unwrap();
964        let pv = obj
965            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
966            .unwrap();
967        assert_eq!(pv, PropertyValue::Date(d2));
968    }
969
970    // -----------------------------------------------------------------------
971    // TimeValueObject
972    // -----------------------------------------------------------------------
973
974    #[test]
975    fn time_value_read_write() {
976        let mut obj = TimeValueObject::new(1, "TV-1").unwrap();
977        let t = Time {
978            hour: 14,
979            minute: 30,
980            second: 0,
981            hundredths: 0,
982        };
983        obj.write_property(
984            PropertyIdentifier::PRESENT_VALUE,
985            None,
986            PropertyValue::Time(t),
987            Some(16),
988        )
989        .unwrap();
990        let pv = obj
991            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
992            .unwrap();
993        assert_eq!(pv, PropertyValue::Time(t));
994    }
995
996    #[test]
997    fn time_value_object_type() {
998        let obj = TimeValueObject::new(1, "TV-1").unwrap();
999        let ot = obj
1000            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
1001            .unwrap();
1002        assert_eq!(
1003            ot,
1004            PropertyValue::Enumerated(ObjectType::TIME_VALUE.to_raw())
1005        );
1006    }
1007
1008    // -----------------------------------------------------------------------
1009    // DateTimeValueObject
1010    // -----------------------------------------------------------------------
1011
1012    #[test]
1013    fn datetime_value_read_write() {
1014        let mut obj = DateTimeValueObject::new(1, "DTV-1").unwrap();
1015        let d = Date {
1016            year: 124,
1017            month: 6,
1018            day: 15,
1019            day_of_week: 6,
1020        };
1021        let t = Time {
1022            hour: 12,
1023            minute: 0,
1024            second: 0,
1025            hundredths: 0,
1026        };
1027        obj.write_property(
1028            PropertyIdentifier::PRESENT_VALUE,
1029            None,
1030            PropertyValue::List(vec![PropertyValue::Date(d), PropertyValue::Time(t)]),
1031            Some(16),
1032        )
1033        .unwrap();
1034        let pv = obj
1035            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1036            .unwrap();
1037        assert_eq!(
1038            pv,
1039            PropertyValue::List(vec![PropertyValue::Date(d), PropertyValue::Time(t)])
1040        );
1041    }
1042
1043    #[test]
1044    fn datetime_value_object_type() {
1045        let obj = DateTimeValueObject::new(1, "DTV-1").unwrap();
1046        let ot = obj
1047            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
1048            .unwrap();
1049        assert_eq!(
1050            ot,
1051            PropertyValue::Enumerated(ObjectType::DATETIME_VALUE.to_raw())
1052        );
1053    }
1054
1055    #[test]
1056    fn datetime_value_priority_array() {
1057        let mut obj = DateTimeValueObject::new(1, "DTV-1").unwrap();
1058        let d1 = Date {
1059            year: 124,
1060            month: 1,
1061            day: 1,
1062            day_of_week: 1,
1063        };
1064        let t1 = Time {
1065            hour: 0,
1066            minute: 0,
1067            second: 0,
1068            hundredths: 0,
1069        };
1070        let d2 = Date {
1071            year: 124,
1072            month: 12,
1073            day: 31,
1074            day_of_week: 2,
1075        };
1076        let t2 = Time {
1077            hour: 23,
1078            minute: 59,
1079            second: 59,
1080            hundredths: 99,
1081        };
1082        obj.write_property(
1083            PropertyIdentifier::PRESENT_VALUE,
1084            None,
1085            PropertyValue::List(vec![PropertyValue::Date(d1), PropertyValue::Time(t1)]),
1086            Some(16),
1087        )
1088        .unwrap();
1089        obj.write_property(
1090            PropertyIdentifier::PRESENT_VALUE,
1091            None,
1092            PropertyValue::List(vec![PropertyValue::Date(d2), PropertyValue::Time(t2)]),
1093            Some(4),
1094        )
1095        .unwrap();
1096        let pv = obj
1097            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1098            .unwrap();
1099        assert_eq!(
1100            pv,
1101            PropertyValue::List(vec![PropertyValue::Date(d2), PropertyValue::Time(t2)])
1102        );
1103    }
1104
1105    // -----------------------------------------------------------------------
1106    // DatePatternValueObject (non-commandable)
1107    // -----------------------------------------------------------------------
1108
1109    #[test]
1110    fn date_pattern_value_read_write() {
1111        let mut obj = DatePatternValueObject::new(1, "DPV-1").unwrap();
1112        let d = Date {
1113            year: 0xFF,
1114            month: 0xFF,
1115            day: 25,
1116            day_of_week: 0xFF,
1117        };
1118        obj.write_property(
1119            PropertyIdentifier::PRESENT_VALUE,
1120            None,
1121            PropertyValue::Date(d),
1122            None,
1123        )
1124        .unwrap();
1125        let pv = obj
1126            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1127            .unwrap();
1128        assert_eq!(pv, PropertyValue::Date(d));
1129    }
1130
1131    #[test]
1132    fn date_pattern_value_object_type() {
1133        let obj = DatePatternValueObject::new(1, "DPV-1").unwrap();
1134        let ot = obj
1135            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
1136            .unwrap();
1137        assert_eq!(
1138            ot,
1139            PropertyValue::Enumerated(ObjectType::DATEPATTERN_VALUE.to_raw())
1140        );
1141    }
1142
1143    #[test]
1144    fn date_pattern_value_no_priority_array() {
1145        let obj = DatePatternValueObject::new(1, "DPV-1").unwrap();
1146        let props = obj.property_list();
1147        assert!(!props.contains(&PropertyIdentifier::PRIORITY_ARRAY));
1148        assert!(!props.contains(&PropertyIdentifier::RELINQUISH_DEFAULT));
1149    }
1150
1151    // -----------------------------------------------------------------------
1152    // TimePatternValueObject (non-commandable)
1153    // -----------------------------------------------------------------------
1154
1155    #[test]
1156    fn time_pattern_value_read_write() {
1157        let mut obj = TimePatternValueObject::new(1, "TPV-1").unwrap();
1158        let t = Time {
1159            hour: 12,
1160            minute: 0xFF,
1161            second: 0xFF,
1162            hundredths: 0xFF,
1163        };
1164        obj.write_property(
1165            PropertyIdentifier::PRESENT_VALUE,
1166            None,
1167            PropertyValue::Time(t),
1168            None,
1169        )
1170        .unwrap();
1171        let pv = obj
1172            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1173            .unwrap();
1174        assert_eq!(pv, PropertyValue::Time(t));
1175    }
1176
1177    #[test]
1178    fn time_pattern_value_object_type() {
1179        let obj = TimePatternValueObject::new(1, "TPV-1").unwrap();
1180        let ot = obj
1181            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
1182            .unwrap();
1183        assert_eq!(
1184            ot,
1185            PropertyValue::Enumerated(ObjectType::TIMEPATTERN_VALUE.to_raw())
1186        );
1187    }
1188
1189    // -----------------------------------------------------------------------
1190    // DateTimePatternValueObject (non-commandable)
1191    // -----------------------------------------------------------------------
1192
1193    #[test]
1194    fn datetime_pattern_value_read_write() {
1195        let mut obj = DateTimePatternValueObject::new(1, "DTPV-1").unwrap();
1196        let d = Date {
1197            year: 0xFF,
1198            month: 12,
1199            day: 25,
1200            day_of_week: 0xFF,
1201        };
1202        let t = Time {
1203            hour: 0xFF,
1204            minute: 0xFF,
1205            second: 0xFF,
1206            hundredths: 0xFF,
1207        };
1208        obj.write_property(
1209            PropertyIdentifier::PRESENT_VALUE,
1210            None,
1211            PropertyValue::List(vec![PropertyValue::Date(d), PropertyValue::Time(t)]),
1212            None,
1213        )
1214        .unwrap();
1215        let pv = obj
1216            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1217            .unwrap();
1218        assert_eq!(
1219            pv,
1220            PropertyValue::List(vec![PropertyValue::Date(d), PropertyValue::Time(t)])
1221        );
1222    }
1223
1224    #[test]
1225    fn datetime_pattern_value_object_type() {
1226        let obj = DateTimePatternValueObject::new(1, "DTPV-1").unwrap();
1227        let ot = obj
1228            .read_property(PropertyIdentifier::OBJECT_TYPE, None)
1229            .unwrap();
1230        assert_eq!(
1231            ot,
1232            PropertyValue::Enumerated(ObjectType::DATETIMEPATTERN_VALUE.to_raw())
1233        );
1234    }
1235
1236    #[test]
1237    fn datetime_pattern_value_no_priority_array() {
1238        let obj = DateTimePatternValueObject::new(1, "DTPV-1").unwrap();
1239        let props = obj.property_list();
1240        assert!(!props.contains(&PropertyIdentifier::PRIORITY_ARRAY));
1241        assert!(!props.contains(&PropertyIdentifier::RELINQUISH_DEFAULT));
1242    }
1243
1244    // -----------------------------------------------------------------------
1245    // Common property tests (using IntegerValue as representative)
1246    // -----------------------------------------------------------------------
1247
1248    #[test]
1249    fn value_object_read_common_properties() {
1250        let obj = IntegerValueObject::new(42, "TestObj").unwrap();
1251
1252        // OBJECT_NAME
1253        let name = obj
1254            .read_property(PropertyIdentifier::OBJECT_NAME, None)
1255            .unwrap();
1256        assert_eq!(name, PropertyValue::CharacterString("TestObj".into()));
1257
1258        // OBJECT_IDENTIFIER
1259        let oid = obj
1260            .read_property(PropertyIdentifier::OBJECT_IDENTIFIER, None)
1261            .unwrap();
1262        assert!(matches!(oid, PropertyValue::ObjectIdentifier(_)));
1263
1264        // STATUS_FLAGS
1265        let sf = obj
1266            .read_property(PropertyIdentifier::STATUS_FLAGS, None)
1267            .unwrap();
1268        assert!(matches!(sf, PropertyValue::BitString { .. }));
1269
1270        // OUT_OF_SERVICE
1271        let oos = obj
1272            .read_property(PropertyIdentifier::OUT_OF_SERVICE, None)
1273            .unwrap();
1274        assert_eq!(oos, PropertyValue::Boolean(false));
1275
1276        // RELIABILITY
1277        let rel = obj
1278            .read_property(PropertyIdentifier::RELIABILITY, None)
1279            .unwrap();
1280        assert_eq!(rel, PropertyValue::Enumerated(0));
1281    }
1282
1283    #[test]
1284    fn value_object_write_description() {
1285        let mut obj = IntegerValueObject::new(1, "IV-1").unwrap();
1286        obj.write_property(
1287            PropertyIdentifier::DESCRIPTION,
1288            None,
1289            PropertyValue::CharacterString("A test integer".into()),
1290            None,
1291        )
1292        .unwrap();
1293        let desc = obj
1294            .read_property(PropertyIdentifier::DESCRIPTION, None)
1295            .unwrap();
1296        assert_eq!(
1297            desc,
1298            PropertyValue::CharacterString("A test integer".into())
1299        );
1300    }
1301
1302    #[test]
1303    fn value_object_write_out_of_service() {
1304        let mut obj = IntegerValueObject::new(1, "IV-1").unwrap();
1305        obj.write_property(
1306            PropertyIdentifier::OUT_OF_SERVICE,
1307            None,
1308            PropertyValue::Boolean(true),
1309            None,
1310        )
1311        .unwrap();
1312        let oos = obj
1313            .read_property(PropertyIdentifier::OUT_OF_SERVICE, None)
1314            .unwrap();
1315        assert_eq!(oos, PropertyValue::Boolean(true));
1316    }
1317
1318    #[test]
1319    fn value_object_relinquish_default() {
1320        let obj = IntegerValueObject::new(1, "IV-1").unwrap();
1321        let rd = obj
1322            .read_property(PropertyIdentifier::RELINQUISH_DEFAULT, None)
1323            .unwrap();
1324        assert_eq!(rd, PropertyValue::Signed(0));
1325    }
1326
1327    #[test]
1328    fn value_object_priority_array_direct_write() {
1329        let mut obj = IntegerValueObject::new(1, "IV-1").unwrap();
1330
1331        // Write directly to priority array slot 5
1332        obj.write_property(
1333            PropertyIdentifier::PRIORITY_ARRAY,
1334            Some(5),
1335            PropertyValue::Signed(77),
1336            None,
1337        )
1338        .unwrap();
1339
1340        // Read back slot 5
1341        let slot = obj
1342            .read_property(PropertyIdentifier::PRIORITY_ARRAY, Some(5))
1343            .unwrap();
1344        assert_eq!(slot, PropertyValue::Signed(77));
1345
1346        // PV should reflect it
1347        let pv = obj
1348            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1349            .unwrap();
1350        assert_eq!(pv, PropertyValue::Signed(77));
1351
1352        // Relinquish slot 5
1353        obj.write_property(
1354            PropertyIdentifier::PRIORITY_ARRAY,
1355            Some(5),
1356            PropertyValue::Null,
1357            None,
1358        )
1359        .unwrap();
1360
1361        // PV falls back to relinquish default
1362        let pv = obj
1363            .read_property(PropertyIdentifier::PRESENT_VALUE, None)
1364            .unwrap();
1365        assert_eq!(pv, PropertyValue::Signed(0));
1366    }
1367
1368    #[test]
1369    fn value_object_unknown_property() {
1370        let obj = IntegerValueObject::new(1, "IV-1").unwrap();
1371        let result = obj.read_property(PropertyIdentifier::UNITS, None);
1372        assert!(result.is_err());
1373    }
1374
1375    #[test]
1376    fn value_object_write_access_denied() {
1377        let mut obj = IntegerValueObject::new(1, "IV-1").unwrap();
1378        let result = obj.write_property(
1379            PropertyIdentifier::OBJECT_NAME,
1380            None,
1381            PropertyValue::CharacterString("new-name".into()),
1382            None,
1383        );
1384        assert!(result.is_err());
1385    }
1386}