Skip to main content

bacnet_objects/value_types/
mod.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::property_metadata::{
15    PropertyConformance, PropertyMetadata, PropertyPresenceCondition, PropertyWriteCapability,
16};
17use crate::traits::BACnetObject;
18
19// ---------------------------------------------------------------------------
20// Macro: define_value_object! (commandable variant)
21// ---------------------------------------------------------------------------
22
23/// Generate a commandable value object type with a 16-level priority array.
24///
25/// Copy types use the existing `recalculate_from_priority_array` helper;
26/// non-Copy types (String, Vec, tuples containing Vec) use a Clone-based
27/// inline recalculation.
28///
29/// `rd_validate` performs any post-extraction validation for a
30/// Relinquish_Default value (#270); `rd_access` selects whether the
31/// `RELINQUISH_DEFAULT` network write arm exists (`writable`) or remains
32/// denied (`readonly` — no current users; kept for a type whose wire form
33/// needs something the service decode cannot deliver). The local
34/// `set_relinquish_default` setter is generated either way.
35macro_rules! define_value_object_commandable {
36    (
37        name: $struct_name:ident,
38        doc: $doc:expr,
39        object_type: $obj_type:expr,
40        value_type: $val_type:ty,
41        default_value: $default:expr,
42        pv_to_property: $pv_to_prop:expr,
43        property_to_pv: $prop_to_pv:expr,
44        pa_wrap: $pa_wrap:expr,
45        rd_wrap: $rd_wrap:expr,
46        rd_validate: $rd_validate:expr,
47        rd_access: $rd_access:ident,
48        copy_type: $is_copy:tt
49        $(, property_metadata: $property_metadata:expr)?
50        $(,)?
51    ) => {
52        #[doc = $doc]
53        pub struct $struct_name {
54            oid: ObjectIdentifier,
55            name: String,
56            description: String,
57            present_value: $val_type,
58            out_of_service: bool,
59            status_flags: StatusFlags,
60            reliability: u32,
61            /// 16-level priority array. `None` = no command at that level.
62            priority_array: [Option<$val_type>; 16],
63            relinquish_default: $val_type,
64        }
65
66        impl $struct_name {
67            /// Create a new instance of this value object.
68            pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
69                let oid = ObjectIdentifier::new($obj_type, instance)?;
70                Ok(Self {
71                    oid,
72                    name: name.into(),
73                    description: String::new(),
74                    present_value: $default,
75                    out_of_service: false,
76                    status_flags: StatusFlags::empty(),
77                    reliability: 0,
78                    priority_array: Default::default(),
79                    relinquish_default: $default,
80                })
81            }
82
83            /// Recalculate present_value from the priority array.
84            fn recalculate_present_value(&mut self) {
85                define_value_object_commandable!(@recalc self, $is_copy);
86            }
87
88            /// Set the Relinquish_Default (#270).
89            ///
90            /// Validated the same way a commanded Present_Value is, then
91            /// Present_Value is resolved anew from the priority array so an
92            /// empty array falls back to the new default immediately. The
93            /// standard permits Relinquish_Default writability on commandable
94            /// types; the service decode's multi-element support (#182) makes
95            /// the datetime-paired wire form (a date+time pair) deliverable,
96            /// so every commandable value type takes network writes now.
97            pub fn set_relinquish_default(&mut self, value: $val_type) -> Result<(), Error> {
98                ($rd_validate)(&value)?;
99                self.relinquish_default = value;
100                self.recalculate_present_value();
101                Ok(())
102            }
103        }
104
105        impl BACnetObject for $struct_name {
106            fn object_identifier(&self) -> ObjectIdentifier {
107                self.oid
108            }
109
110            fn object_name(&self) -> &str {
111                &self.name
112            }
113
114            fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
115                define_value_object_commandable!(@property_metadata $($property_metadata)?)
116            }
117
118            fn read_property(
119                &self,
120                property: PropertyIdentifier,
121                array_index: Option<u32>,
122            ) -> Result<PropertyValue, Error> {
123                if let Some(result) = read_common_properties!(self, property, array_index) {
124                    return result;
125                }
126                match property {
127                    p if p == PropertyIdentifier::OBJECT_TYPE => {
128                        Ok(PropertyValue::Enumerated($obj_type.to_raw()))
129                    }
130                    p if p == PropertyIdentifier::PRESENT_VALUE => {
131                        Ok(($pv_to_prop)(&self.present_value))
132                    }
133                    p if p == PropertyIdentifier::PRIORITY_ARRAY => {
134                        define_value_object_commandable!(@read_pa self, array_index, $pa_wrap, $is_copy)
135                    }
136                    p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
137                        Ok(($rd_wrap)(&self.relinquish_default))
138                    }
139                    _ => Err(common::unknown_property_error()),
140                }
141            }
142
143            fn write_property(
144                &mut self,
145                property: PropertyIdentifier,
146                array_index: Option<u32>,
147                value: PropertyValue,
148                priority: Option<u8>,
149            ) -> Result<(), Error> {
150                // Handle PRIORITY_ARRAY direct writes. Index validation
151                // follows Clause 12.1.5.1: an omitted index means whole-array
152                // access, and whole-array writes are not supported here, so
153                // it is PROPERTY / WRITE_ACCESS_DENIED (Clause 15.9.1.3).
154                if property == PropertyIdentifier::PRIORITY_ARRAY {
155                    let idx = match array_index {
156                        Some(n) if (1..=16).contains(&n) => (n - 1) as usize,
157                        Some(_) => return Err(common::invalid_array_index_error()),
158                        None => return Err(common::write_access_denied_error()),
159                    };
160                    match value {
161                        PropertyValue::Null => {
162                            self.priority_array[idx] = None;
163                        }
164                        other => {
165                            let extracted = ($prop_to_pv)(other)?;
166                            self.priority_array[idx] = Some(extracted);
167                        }
168                    }
169                    self.recalculate_present_value();
170                    return Ok(());
171                }
172                // Handle PRESENT_VALUE via priority array
173                if property == PropertyIdentifier::PRESENT_VALUE {
174                    let prio = priority.unwrap_or(16);
175                    if !(1..=16).contains(&prio) {
176                        return Err(common::value_out_of_range_error());
177                    }
178                    let idx = (prio - 1) as usize;
179                    match value {
180                        PropertyValue::Null => {
181                            self.priority_array[idx] = None;
182                        }
183                        other => {
184                            let extracted = ($prop_to_pv)(other)?;
185                            self.priority_array[idx] = Some(extracted);
186                        }
187                    }
188                    self.recalculate_present_value();
189                    return Ok(());
190                }
191                // RELINQUISH_DEFAULT — network-writable only for the types
192                // that select `rd_access: writable` (#270).
193                define_value_object_commandable!(@rd_write self, property, value, $prop_to_pv, $rd_access);
194                if let Some(result) =
195                    common::write_out_of_service(&mut self.out_of_service, property, &value)
196                {
197                    return result;
198                }
199                if let Some(result) =
200                    common::write_object_name(&mut self.name, property, &value)
201                {
202                    return result;
203                }
204                if let Some(result) =
205                    common::write_description(&mut self.description, property, &value)
206                {
207                    return result;
208                }
209                Err(common::write_access_denied_error())
210            }
211
212            fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
213                static PROPS: &[PropertyIdentifier] = &[
214                    PropertyIdentifier::OBJECT_IDENTIFIER,
215                    PropertyIdentifier::OBJECT_NAME,
216                    PropertyIdentifier::DESCRIPTION,
217                    PropertyIdentifier::OBJECT_TYPE,
218                    PropertyIdentifier::PRESENT_VALUE,
219                    PropertyIdentifier::STATUS_FLAGS,
220                    PropertyIdentifier::OUT_OF_SERVICE,
221                    PropertyIdentifier::RELIABILITY,
222                    PropertyIdentifier::PRIORITY_ARRAY,
223                    PropertyIdentifier::RELINQUISH_DEFAULT,
224                ];
225                let metadata = self.property_metadata();
226                if metadata.is_empty() {
227                    Cow::Borrowed(PROPS)
228                } else {
229                    crate::property_metadata::property_list_from_metadata(metadata.as_ref())
230                }
231            }
232
233            fn supports_cov(&self) -> bool {
234                true
235            }
236
237            define_value_object_commandable!(@is_writable $rd_access, $($property_metadata)?);
238        }
239    };
240
241    (@property_metadata) => {
242        Cow::Borrowed(&[])
243    };
244    (@property_metadata $metadata:expr) => {
245        Cow::Borrowed($metadata)
246    };
247
248    // RELINQUISH_DEFAULT write arm for `rd_access: writable`: extract through
249    // the shared closure, then reuse the validated setter (#270).
250    (@rd_write $self:ident, $property:ident, $value:ident, $prop_to_pv:expr, writable) => {
251        if $property == PropertyIdentifier::RELINQUISH_DEFAULT {
252            let extracted = ($prop_to_pv)($value)?;
253            return $self.set_relinquish_default(extracted);
254        }
255    };
256    // `rd_access: readonly`: no arm — the default WRITE_ACCESS_DENIED at the
257    // end of write_property stands (no current users).
258    (@rd_write $self:ident, $property:ident, $value:ident, $prop_to_pv:expr, readonly) => {};
259
260    // PICS writability mirrors the arms: common + commandable for every
261    // commandable value type, RELINQUISH_DEFAULT only when the arm exists.
262    (@is_writable writable,) => {
263        fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
264            common::is_common_writable(property)
265                || property == PropertyIdentifier::PRESENT_VALUE
266                || property == PropertyIdentifier::PRIORITY_ARRAY
267                || property == PropertyIdentifier::RELINQUISH_DEFAULT
268        }
269    };
270    (@is_writable readonly,) => {
271        fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
272            common::is_common_writable(property)
273                || property == PropertyIdentifier::PRESENT_VALUE
274                || property == PropertyIdentifier::PRIORITY_ARRAY
275        }
276    };
277    (@is_writable $rd_access:ident, $metadata:expr) => {
278        fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
279            crate::property_metadata::is_writable_in_metadata($metadata, property)
280        }
281    };
282
283    // --- Internal helper arms ---
284
285    // Recalculate for Copy types: use the common helper.
286    (@recalc $self:ident, copy) => {
287        $self.present_value = common::recalculate_from_priority_array(
288            &$self.priority_array,
289            $self.relinquish_default,
290        );
291    };
292    // Recalculate for Clone (non-Copy) types.
293    (@recalc $self:ident, clone) => {
294        $self.present_value = $self
295            .priority_array
296            .iter()
297            .find_map(|slot| slot.as_ref().cloned())
298            .unwrap_or_else(|| $self.relinquish_default.clone());
299    };
300
301    // Read priority array for Copy types: use the common macro.
302    (@read_pa $self:ident, $array_index:ident, $wrap:expr, copy) => {{
303        common::read_priority_array!($self, $array_index, $wrap)
304    }};
305    // Read priority array for Clone (non-Copy) types.
306    (@read_pa $self:ident, $array_index:ident, $wrap:expr, clone) => {{
307        let wrap_fn = $wrap;
308        match $array_index {
309            None => {
310                let elements = $self
311                    .priority_array
312                    .iter()
313                    .map(|slot| match slot {
314                        Some(v) => wrap_fn(v),
315                        None => PropertyValue::Null,
316                    })
317                    .collect();
318                Ok(PropertyValue::List(elements))
319            }
320            Some(0) => Ok(PropertyValue::Unsigned(16)),
321            Some(idx) if (1..=16).contains(&idx) => {
322                match &$self.priority_array[(idx - 1) as usize] {
323                    Some(v) => Ok(wrap_fn(v)),
324                    None => Ok(PropertyValue::Null),
325                }
326            }
327            _ => Err(common::invalid_array_index_error()),
328        }
329    }};
330}
331
332// ---------------------------------------------------------------------------
333// Macro: define_value_object! (non-commandable variant)
334// ---------------------------------------------------------------------------
335
336/// Generate a non-commandable value object type (simple read/write PV).
337/// Currently unused — all value types are commandable.
338#[allow(unused_macros)]
339macro_rules! define_value_object_simple {
340    (
341        name: $struct_name:ident,
342        doc: $doc:expr,
343        object_type: $obj_type:expr,
344        value_type: $val_type:ty,
345        default_value: $default:expr,
346        pv_to_property: $pv_to_prop:expr,
347        property_to_pv: $prop_to_pv:expr
348        $(,)?
349    ) => {
350        #[doc = $doc]
351        pub struct $struct_name {
352            oid: ObjectIdentifier,
353            name: String,
354            description: String,
355            present_value: $val_type,
356            out_of_service: bool,
357            status_flags: StatusFlags,
358            reliability: u32,
359        }
360
361        impl $struct_name {
362            /// Create a new instance of this value object.
363            pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
364                let oid = ObjectIdentifier::new($obj_type, instance)?;
365                Ok(Self {
366                    oid,
367                    name: name.into(),
368                    description: String::new(),
369                    present_value: $default,
370                    out_of_service: false,
371                    status_flags: StatusFlags::empty(),
372                    reliability: 0,
373                })
374            }
375        }
376
377        impl BACnetObject for $struct_name {
378            fn object_identifier(&self) -> ObjectIdentifier {
379                self.oid
380            }
381
382            fn object_name(&self) -> &str {
383                &self.name
384            }
385
386            fn read_property(
387                &self,
388                property: PropertyIdentifier,
389                array_index: Option<u32>,
390            ) -> Result<PropertyValue, Error> {
391                if let Some(result) = read_common_properties!(self, property, array_index) {
392                    return result;
393                }
394                match property {
395                    p if p == PropertyIdentifier::OBJECT_TYPE => {
396                        Ok(PropertyValue::Enumerated($obj_type.to_raw()))
397                    }
398                    p if p == PropertyIdentifier::PRESENT_VALUE => {
399                        Ok(($pv_to_prop)(&self.present_value))
400                    }
401                    _ => Err(common::unknown_property_error()),
402                }
403            }
404
405            fn write_property(
406                &mut self,
407                property: PropertyIdentifier,
408                _array_index: Option<u32>,
409                value: PropertyValue,
410                _priority: Option<u8>,
411            ) -> Result<(), Error> {
412                if property == PropertyIdentifier::PRESENT_VALUE {
413                    let extracted = ($prop_to_pv)(value)?;
414                    self.present_value = extracted;
415                    return Ok(());
416                }
417                if let Some(result) =
418                    common::write_out_of_service(&mut self.out_of_service, property, &value)
419                {
420                    return result;
421                }
422                if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
423                    return result;
424                }
425                if let Some(result) =
426                    common::write_description(&mut self.description, property, &value)
427                {
428                    return result;
429                }
430                Err(common::write_access_denied_error())
431            }
432
433            fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
434                static PROPS: &[PropertyIdentifier] = &[
435                    PropertyIdentifier::OBJECT_IDENTIFIER,
436                    PropertyIdentifier::OBJECT_NAME,
437                    PropertyIdentifier::DESCRIPTION,
438                    PropertyIdentifier::OBJECT_TYPE,
439                    PropertyIdentifier::PRESENT_VALUE,
440                    PropertyIdentifier::STATUS_FLAGS,
441                    PropertyIdentifier::OUT_OF_SERVICE,
442                    PropertyIdentifier::RELIABILITY,
443                ];
444                Cow::Borrowed(PROPS)
445            }
446
447            fn supports_cov(&self) -> bool {
448                true
449            }
450        }
451    };
452}
453
454// ---------------------------------------------------------------------------
455// Helper functions for value conversions
456// ---------------------------------------------------------------------------
457
458fn clone_string_to_pv(v: &str) -> PropertyValue {
459    PropertyValue::CharacterString(v.to_owned())
460}
461
462fn clone_octetstring_to_pv(v: &[u8]) -> PropertyValue {
463    PropertyValue::OctetString(v.to_owned())
464}
465
466fn clone_bitstring_to_pv(v: &(u8, Vec<u8>)) -> PropertyValue {
467    PropertyValue::BitString {
468        unused_bits: v.0,
469        data: v.1.clone(),
470    }
471}
472
473fn datetime_copy_to_pv(v: (Date, Time)) -> PropertyValue {
474    PropertyValue::List(vec![PropertyValue::Date(v.0), PropertyValue::Time(v.1)])
475}
476
477fn datetime_to_pv(dt: &(Date, Time)) -> PropertyValue {
478    PropertyValue::List(vec![PropertyValue::Date(dt.0), PropertyValue::Time(dt.1)])
479}
480
481fn pv_to_date(v: PropertyValue) -> Result<Date, Error> {
482    if let PropertyValue::Date(d) = v {
483        Ok(d)
484    } else {
485        Err(common::invalid_data_type_error())
486    }
487}
488
489fn pv_to_time(v: PropertyValue) -> Result<Time, Error> {
490    if let PropertyValue::Time(t) = v {
491        Ok(t)
492    } else {
493        Err(common::invalid_data_type_error())
494    }
495}
496
497fn pv_to_datetime(v: PropertyValue) -> Result<(Date, Time), Error> {
498    if let PropertyValue::List(items) = v {
499        if items.len() == 2 {
500            let d = if let PropertyValue::Date(d) = &items[0] {
501                *d
502            } else {
503                return Err(common::invalid_data_type_error());
504            };
505            let t = if let PropertyValue::Time(t) = &items[1] {
506                *t
507            } else {
508                return Err(common::invalid_data_type_error());
509            };
510            Ok((d, t))
511        } else {
512            Err(common::invalid_data_type_error())
513        }
514    } else {
515        Err(common::invalid_data_type_error())
516    }
517}
518
519// ---------------------------------------------------------------------------
520// 9 Commandable value objects
521// ---------------------------------------------------------------------------
522
523define_value_object_commandable! {
524    name: IntegerValueObject,
525    doc: "BACnet Integer Value object (type 45).",
526    object_type: ObjectType::INTEGER_VALUE,
527    value_type: i32,
528    default_value: 0,
529    pv_to_property: (|v: &i32| PropertyValue::Signed(*v)),
530    property_to_pv: (|v: PropertyValue| -> Result<i32, Error> {
531        if let PropertyValue::Signed(n) = v { Ok(n) }
532        else { Err(common::invalid_data_type_error()) }
533    }),
534    pa_wrap: PropertyValue::Signed,
535    rd_wrap: (|v: &i32| PropertyValue::Signed(*v)),
536    rd_validate: (|_: &i32| -> Result<(), Error> { Ok(()) }),
537    rd_access: writable,
538    copy_type: copy,
539}
540
541define_value_object_commandable! {
542    name: PositiveIntegerValueObject,
543    doc: "BACnet Positive Integer Value object (type 48).",
544    object_type: ObjectType::POSITIVE_INTEGER_VALUE,
545    value_type: u64,
546    default_value: 0,
547    pv_to_property: (|v: &u64| PropertyValue::Unsigned(*v)),
548    property_to_pv: (|v: PropertyValue| -> Result<u64, Error> {
549        if let PropertyValue::Unsigned(n) = v { Ok(n) }
550        else { Err(common::invalid_data_type_error()) }
551    }),
552    pa_wrap: PropertyValue::Unsigned,
553    rd_wrap: (|v: &u64| PropertyValue::Unsigned(*v)),
554    rd_validate: (|_: &u64| -> Result<(), Error> { Ok(()) }),
555    rd_access: writable,
556    copy_type: copy,
557}
558
559define_value_object_commandable! {
560    name: LargeAnalogValueObject,
561    doc: "BACnet Large Analog Value object (type 46).",
562    object_type: ObjectType::LARGE_ANALOG_VALUE,
563    value_type: f64,
564    default_value: 0.0,
565    pv_to_property: (|v: &f64| PropertyValue::Double(*v)),
566    property_to_pv: (|v: PropertyValue| -> Result<f64, Error> {
567        if let PropertyValue::Double(n) = v { Ok(n) }
568        else { Err(common::invalid_data_type_error()) }
569    }),
570    pa_wrap: PropertyValue::Double,
571    rd_wrap: (|v: &f64| PropertyValue::Double(*v)),
572    rd_validate: (|v: &f64| -> Result<(), Error> {
573        if v.is_finite() {
574            Ok(())
575        } else {
576            Err(common::value_out_of_range_error())
577        }
578    }),
579    rd_access: writable,
580    copy_type: copy,
581}
582
583define_value_object_commandable! {
584    name: CharacterStringValueObject,
585    doc: "BACnet CharacterString Value object (type 40).",
586    object_type: ObjectType::CHARACTERSTRING_VALUE,
587    value_type: String,
588    default_value: String::new(),
589    pv_to_property: (|v: &String| PropertyValue::CharacterString(v.clone())),
590    property_to_pv: (|v: PropertyValue| -> Result<String, Error> {
591        if let PropertyValue::CharacterString(s) = v { Ok(s) }
592        else { Err(common::invalid_data_type_error()) }
593    }),
594    pa_wrap: clone_string_to_pv,
595    rd_wrap: (|v: &String| PropertyValue::CharacterString(v.clone())),
596    rd_validate: (|_: &String| -> Result<(), Error> { Ok(()) }),
597    rd_access: writable,
598    copy_type: clone,
599}
600
601define_value_object_commandable! {
602    name: OctetStringValueObject,
603    doc: "BACnet OctetString Value object (type 47).",
604    object_type: ObjectType::OCTETSTRING_VALUE,
605    value_type: Vec<u8>,
606    default_value: Vec::new(),
607    pv_to_property: (|v: &Vec<u8>| PropertyValue::OctetString(v.clone())),
608    property_to_pv: (|v: PropertyValue| -> Result<Vec<u8>, Error> {
609        if let PropertyValue::OctetString(b) = v { Ok(b) }
610        else { Err(common::invalid_data_type_error()) }
611    }),
612    pa_wrap: clone_octetstring_to_pv,
613    rd_wrap: (|v: &Vec<u8>| PropertyValue::OctetString(v.clone())),
614    rd_validate: (|_: &Vec<u8>| -> Result<(), Error> { Ok(()) }),
615    rd_access: writable,
616    copy_type: clone,
617}
618
619define_value_object_commandable! {
620    name: BitStringValueObject,
621    doc: "BACnet BitString Value object (type 39).",
622    object_type: ObjectType::BITSTRING_VALUE,
623    value_type: (u8, Vec<u8>),
624    default_value: (0, Vec::new()),
625    pv_to_property: (|v: &(u8, Vec<u8>)| PropertyValue::BitString {
626        unused_bits: v.0,
627        data: v.1.clone(),
628    }),
629    property_to_pv: (|v: PropertyValue| -> Result<(u8, Vec<u8>), Error> {
630        if let PropertyValue::BitString { unused_bits, data } = v { Ok((unused_bits, data)) }
631        else { Err(common::invalid_data_type_error()) }
632    }),
633    pa_wrap: clone_bitstring_to_pv,
634    rd_wrap: (|v: &(u8, Vec<u8>)| PropertyValue::BitString {
635        unused_bits: v.0,
636        data: v.1.clone(),
637    }),
638    rd_validate: (|_: &(u8, Vec<u8>)| -> Result<(), Error> { Ok(()) }),
639    rd_access: writable,
640    copy_type: clone,
641}
642
643define_value_object_commandable! {
644    name: DateValueObject,
645    doc: "BACnet Date Value object (type 42).",
646    object_type: ObjectType::DATE_VALUE,
647    value_type: Date,
648    default_value: Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
649    pv_to_property: (|v: &Date| PropertyValue::Date(*v)),
650    property_to_pv: pv_to_date,
651    pa_wrap: PropertyValue::Date,
652    rd_wrap: (|v: &Date| PropertyValue::Date(*v)),
653    rd_validate: (|_: &Date| -> Result<(), Error> { Ok(()) }),
654    rd_access: writable,
655    copy_type: copy,
656}
657
658const TIME_VALUE_PROPERTY_METADATA: &[PropertyMetadata] = &[
659    PropertyMetadata::new(
660        PropertyIdentifier::OBJECT_IDENTIFIER,
661        PropertyConformance::RequiredRead,
662        None,
663        PropertyWriteCapability::ReadOnly,
664    ),
665    PropertyMetadata::new(
666        PropertyIdentifier::OBJECT_NAME,
667        PropertyConformance::RequiredRead,
668        None,
669        PropertyWriteCapability::Always,
670    ),
671    PropertyMetadata::new(
672        PropertyIdentifier::DESCRIPTION,
673        PropertyConformance::Optional,
674        None,
675        PropertyWriteCapability::Always,
676    ),
677    PropertyMetadata::new(
678        PropertyIdentifier::OBJECT_TYPE,
679        PropertyConformance::RequiredRead,
680        None,
681        PropertyWriteCapability::ReadOnly,
682    ),
683    PropertyMetadata::new(
684        PropertyIdentifier::PRESENT_VALUE,
685        PropertyConformance::RequiredRead,
686        None,
687        PropertyWriteCapability::Always,
688    ),
689    PropertyMetadata::new(
690        PropertyIdentifier::STATUS_FLAGS,
691        PropertyConformance::RequiredRead,
692        None,
693        PropertyWriteCapability::ReadOnly,
694    ),
695    PropertyMetadata::new(
696        PropertyIdentifier::OUT_OF_SERVICE,
697        PropertyConformance::Optional,
698        None,
699        PropertyWriteCapability::Always,
700    ),
701    PropertyMetadata::new(
702        PropertyIdentifier::RELIABILITY,
703        PropertyConformance::Optional,
704        None,
705        PropertyWriteCapability::ReadOnly,
706    ),
707    PropertyMetadata::new(
708        PropertyIdentifier::PRIORITY_ARRAY,
709        PropertyConformance::Optional,
710        Some(PropertyPresenceCondition::Commandable),
711        PropertyWriteCapability::Always,
712    ),
713    PropertyMetadata::new(
714        PropertyIdentifier::RELINQUISH_DEFAULT,
715        PropertyConformance::Optional,
716        Some(PropertyPresenceCondition::Commandable),
717        PropertyWriteCapability::Always,
718    ),
719    PropertyMetadata::new(
720        PropertyIdentifier::PROPERTY_LIST,
721        PropertyConformance::RequiredRead,
722        None,
723        PropertyWriteCapability::ReadOnly,
724    ),
725];
726
727define_value_object_commandable! {
728    name: TimeValueObject,
729    doc: "BACnet Time Value object (type 50).",
730    object_type: ObjectType::TIME_VALUE,
731    value_type: Time,
732    default_value: Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
733    pv_to_property: (|v: &Time| PropertyValue::Time(*v)),
734    property_to_pv: pv_to_time,
735    pa_wrap: PropertyValue::Time,
736    rd_wrap: (|v: &Time| PropertyValue::Time(*v)),
737    rd_validate: (|_: &Time| -> Result<(), Error> { Ok(()) }),
738    rd_access: writable,
739    copy_type: copy,
740    property_metadata: TIME_VALUE_PROPERTY_METADATA,
741}
742
743define_value_object_commandable! {
744    name: DateTimeValueObject,
745    doc: "BACnet DateTime Value object (type 44).",
746    object_type: ObjectType::DATETIME_VALUE,
747    value_type: (Date, Time),
748    default_value: (
749        Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
750        Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
751    ),
752    pv_to_property: (|v: &(Date, Time)| datetime_to_pv(v)),
753    property_to_pv: pv_to_datetime,
754    pa_wrap: datetime_copy_to_pv,
755    rd_wrap: (|v: &(Date, Time)| datetime_to_pv(v)),
756    rd_validate: (|_: &(Date, Time)| -> Result<(), Error> { Ok(()) }),
757    rd_access: writable,
758    copy_type: copy,
759}
760
761// ---------------------------------------------------------------------------
762// 3 Commandable pattern value objects (with priority array)
763// ---------------------------------------------------------------------------
764
765define_value_object_commandable! {
766    name: DatePatternValueObject,
767    doc: "BACnet Date Pattern Value object (type 41).",
768    object_type: ObjectType::DATEPATTERN_VALUE,
769    value_type: Date,
770    default_value: Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
771    pv_to_property: (|v: &Date| PropertyValue::Date(*v)),
772    property_to_pv: pv_to_date,
773    pa_wrap: PropertyValue::Date,
774    rd_wrap: (|v: &Date| PropertyValue::Date(*v)),
775    rd_validate: (|_: &Date| -> Result<(), Error> { Ok(()) }),
776    rd_access: writable,
777    copy_type: copy,
778}
779
780define_value_object_commandable! {
781    name: TimePatternValueObject,
782    doc: "BACnet Time Pattern Value object (type 49).",
783    object_type: ObjectType::TIMEPATTERN_VALUE,
784    value_type: Time,
785    default_value: Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
786    pv_to_property: (|v: &Time| PropertyValue::Time(*v)),
787    property_to_pv: pv_to_time,
788    pa_wrap: PropertyValue::Time,
789    rd_wrap: (|v: &Time| PropertyValue::Time(*v)),
790    rd_validate: (|_: &Time| -> Result<(), Error> { Ok(()) }),
791    rd_access: writable,
792    copy_type: copy,
793}
794
795define_value_object_commandable! {
796    name: DateTimePatternValueObject,
797    doc: "BACnet DateTime Pattern Value object (type 43).",
798    object_type: ObjectType::DATETIMEPATTERN_VALUE,
799    value_type: (Date, Time),
800    default_value: (
801        Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
802        Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
803    ),
804    pv_to_property: (|v: &(Date, Time)| datetime_to_pv(v)),
805    property_to_pv: pv_to_datetime,
806    pa_wrap: datetime_copy_to_pv,
807    rd_wrap: (|v: &(Date, Time)| datetime_to_pv(v)),
808    rd_validate: (|_: &(Date, Time)| -> Result<(), Error> { Ok(()) }),
809    rd_access: writable,
810    copy_type: copy,
811}
812
813// ---------------------------------------------------------------------------
814// Tests
815// ---------------------------------------------------------------------------
816
817#[cfg(test)]
818mod tests;