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