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 crate::prelude::*;
9use alloc::borrow::Cow;
10use bacnet_types::enums::{EngineeringUnits, ObjectType, PropertyIdentifier, Reliability};
11use bacnet_types::error::Error;
12use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
13
14use crate::common::{self, read_common_properties};
15use crate::property_metadata::{
16    PropertyConformance, PropertyMetadata, PropertyPresenceCondition, PropertyWriteCapability,
17};
18use crate::traits::BACnetObject;
19use cov_increment::CovIncrement;
20
21mod cov_increment;
22mod metadata;
23
24// ---------------------------------------------------------------------------
25// Macro: define_value_object! (commandable variant)
26// ---------------------------------------------------------------------------
27
28/// Generate a commandable value object type with a 16-level priority array.
29///
30/// Copy types use the existing `recalculate_from_priority_array` helper;
31/// non-Copy types (String, Vec, tuples containing Vec) use a Clone-based
32/// inline recalculation.
33///
34/// `rd_validate` performs post-extraction validation for Relinquish_Default.
35/// Every generated type exposes its validated local setter and network write;
36/// required property metadata supplies property presence and writability.
37///
38/// The optional `units: EngineeringUnits` entry gives the type a Units
39/// property (the numeric value types, whose tables require it): a field that
40/// starts at NO_UNITS and reads as Enumerated, a `units` getter and a
41/// validated `set_units` setter. Units has no network write route.
42///
43/// The optional `cov_increment` entry names the COV_Increment datatype of the
44/// numeric value types (`u64` for Unsigned, `f64` for Double). It adds a
45/// writable COV_Increment row starting at 0, a validated `set_cov_increment`
46/// setter, and the `cov_increment` answer the server's COV change detection
47/// compares Present_Value moves against.
48///
49/// Every generated type serves Current_Command_Priority from its priority
50/// array: the 1-based slot Present_Value comes from, or NULL when
51/// Relinquish_Default is in effect.
52macro_rules! define_value_object_commandable {
53    (
54        name: $struct_name:ident,
55        doc: $doc:expr_2021,
56        object_type: $obj_type:expr_2021,
57        value_type: $val_type:ty,
58        default_value: $default:expr_2021,
59        pv_to_property: $pv_to_prop:expr_2021,
60        property_to_pv: $prop_to_pv:expr_2021,
61        pa_wrap: $pa_wrap:expr_2021,
62        rd_wrap: $rd_wrap:expr_2021,
63        rd_validate: $rd_validate:expr_2021,
64        copy_type: $is_copy:tt,
65        $(units: $units_ty:ty,)?
66        $(cov_increment: $cov_ty:ty,)?
67        property_metadata: $property_metadata:expr_2021
68        $(,)?
69    ) => {
70        #[doc = $doc]
71        pub struct $struct_name {
72            oid: ObjectIdentifier,
73            name: String,
74            description: String,
75            present_value: $val_type,
76            out_of_service: bool,
77            status_flags: StatusFlags,
78            reliability: Reliability,
79            /// 16-level priority array. `None` = no command at that level.
80            priority_array: [Option<$val_type>; 16],
81            relinquish_default: $val_type,
82            $(units: $units_ty,)?
83            $(
84            /// COV_Increment in the table's datatype.
85            cov_increment: $cov_ty,
86            )?
87        }
88
89        impl $struct_name {
90            /// Create a new instance of this value object.
91            pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
92                let oid = ObjectIdentifier::new($obj_type, instance)?;
93                Ok(Self {
94                    oid,
95                    name: name.into(),
96                    description: String::new(),
97                    present_value: $default,
98                    out_of_service: false,
99                    status_flags: StatusFlags::empty(),
100                    reliability: Reliability::NO_FAULT_DETECTED,
101                    priority_array: Default::default(),
102                    relinquish_default: $default,
103                    $(units: <$units_ty>::NO_UNITS,)?
104                    $(cov_increment: <$cov_ty as CovIncrement>::ZERO,)?
105                })
106            }
107
108            /// Recalculate present_value from the priority array.
109            fn recalculate_present_value(&mut self) {
110                define_value_object_commandable!(@recalc self, $is_copy);
111            }
112
113            /// Set the Relinquish_Default (#270).
114            ///
115            /// Validated the same way a commanded Present_Value is, then
116            /// Present_Value is resolved anew from the priority array so an
117            /// empty array falls back to the new default immediately. The
118            /// standard permits Relinquish_Default writability on commandable
119            /// types; the service decode's multi-element support (#182) makes
120            /// the datetime-paired wire form (a date+time pair) deliverable,
121            /// so every commandable value type takes network writes now.
122            pub fn set_relinquish_default(&mut self, value: $val_type) -> Result<(), Error> {
123                ($rd_validate)(&value)?;
124                self.relinquish_default = value;
125                self.recalculate_present_value();
126                Ok(())
127            }
128
129            $(
130            /// The engineering units of Present_Value, served as Units.
131            pub fn units(&self) -> $units_ty {
132                self.units
133            }
134
135            /// Set the engineering units of Present_Value. A new object uses
136            /// NO_UNITS.
137            ///
138            /// Units is read-only over the network. A value above 65535,
139            /// outside BACnetEngineeringUnits, is refused with
140            /// VALUE_OUT_OF_RANGE and the property is left unchanged.
141            pub fn set_units(&mut self, units: $units_ty) -> Result<(), Error> {
142                if units.to_raw() > 65_535 {
143                    return Err(common::value_out_of_range_error());
144                }
145                self.units = units;
146                Ok(())
147            }
148            )?
149
150            $(
151            /// Set COV_Increment, the smallest Present_Value change that
152            /// sends a COV notification. A new object uses 0, so any change
153            /// notifies.
154            ///
155            /// WriteProperty goes through the same check. A Large Analog
156            /// Value refuses a negative or non-finite increment with
157            /// VALUE_OUT_OF_RANGE and keeps the old one; every Unsigned value
158            /// is a valid increment.
159            pub fn set_cov_increment(&mut self, increment: $cov_ty) -> Result<(), Error> {
160                self.cov_increment = <$cov_ty as CovIncrement>::validate(increment)?;
161                Ok(())
162            }
163            )?
164        }
165
166        impl BACnetObject for $struct_name {
167            fn object_identifier(&self) -> ObjectIdentifier {
168                self.oid
169            }
170
171            fn object_name(&self) -> &str {
172                &self.name
173            }
174
175            fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
176                Cow::Borrowed($property_metadata)
177            }
178
179            fn read_property(
180                &self,
181                property: PropertyIdentifier,
182                array_index: Option<u32>,
183            ) -> Result<PropertyValue, Error> {
184                if let Some(result) = read_common_properties!(self, property, array_index) {
185                    return result;
186                }
187                match property {
188                    p if p == PropertyIdentifier::OBJECT_TYPE => {
189                        Ok(PropertyValue::Enumerated($obj_type.to_raw()))
190                    }
191                    p if p == PropertyIdentifier::PRESENT_VALUE => {
192                        Ok(($pv_to_prop)(&self.present_value))
193                    }
194                    p if p == PropertyIdentifier::PRIORITY_ARRAY => {
195                        define_value_object_commandable!(@read_pa self, array_index, $pa_wrap, $is_copy)
196                    }
197                    p if p == PropertyIdentifier::RELINQUISH_DEFAULT => {
198                        Ok(($rd_wrap)(&self.relinquish_default))
199                    }
200                    p if p == PropertyIdentifier::CURRENT_COMMAND_PRIORITY => {
201                        Ok(common::current_command_priority(&self.priority_array))
202                    }
203                    $(
204                    p if p == PropertyIdentifier::UNITS => {
205                        Ok(PropertyValue::Enumerated(<$units_ty>::to_raw(self.units)))
206                    }
207                    )?
208                    $(
209                    p if p == PropertyIdentifier::COV_INCREMENT => {
210                        Ok(<$cov_ty as CovIncrement>::to_property(self.cov_increment))
211                    }
212                    )?
213                    _ => Err(common::unknown_property_error()),
214                }
215            }
216
217            fn write_property(
218                &mut self,
219                property: PropertyIdentifier,
220                _array_index: Option<u32>,
221                value: PropertyValue,
222                priority: Option<u8>,
223            ) -> Result<(), Error> {
224                // Handle PRESENT_VALUE via priority array
225                if property == PropertyIdentifier::PRESENT_VALUE {
226                    let prio = priority.unwrap_or(16);
227                    if !(1..=16).contains(&prio) {
228                        return Err(common::value_out_of_range_error());
229                    }
230                    let idx = (prio - 1) as usize;
231                    match value {
232                        PropertyValue::Null => {
233                            self.priority_array[idx] = None;
234                        }
235                        other => {
236                            let extracted = ($prop_to_pv)(other)?;
237                            self.priority_array[idx] = Some(extracted);
238                        }
239                    }
240                    self.recalculate_present_value();
241                    return Ok(());
242                }
243                // Extract through the per-type closure, then validate and recapture.
244                if property == PropertyIdentifier::RELINQUISH_DEFAULT {
245                    let extracted = ($prop_to_pv)(value)?;
246                    return self.set_relinquish_default(extracted);
247                }
248                $(
249                if property == PropertyIdentifier::COV_INCREMENT {
250                    let increment = <$cov_ty as CovIncrement>::from_property(value)?;
251                    return self.set_cov_increment(increment);
252                }
253                )?
254                if let Some(result) =
255                    common::write_out_of_service(&mut self.out_of_service, property, &value)
256                {
257                    return result;
258                }
259                if let Some(result) =
260                    common::write_object_name(&mut self.name, property, &value)
261                {
262                    return result;
263                }
264                if let Some(result) =
265                    common::write_description(&mut self.description, property, &value)
266                {
267                    return result;
268                }
269                Err(crate::common::unhandled_write_error(self.property_metadata().as_ref(), property, _array_index))
270            }
271
272            fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
273                crate::property_metadata::property_list_from_metadata($property_metadata)
274            }
275
276            fn supports_cov(&self) -> bool {
277                true
278            }
279
280            $(
281            fn cov_increment(&self) -> Option<f64> {
282                Some(<$cov_ty as CovIncrement>::as_f64(self.cov_increment))
283            }
284            )?
285
286            fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
287                crate::property_metadata::is_writable_in_metadata($property_metadata, property)
288            }
289        }
290    };
291
292    // --- Internal helper arms ---
293
294    // Recalculate for Copy types: use the common helper.
295    (@recalc $self:ident, copy) => {
296        $self.present_value = common::recalculate_from_priority_array(
297            &$self.priority_array,
298            $self.relinquish_default,
299        );
300    };
301    // Recalculate for Clone (non-Copy) types.
302    (@recalc $self:ident, clone) => {
303        $self.present_value = $self
304            .priority_array
305            .iter()
306            .find_map(|slot| slot.as_ref().cloned())
307            .unwrap_or_else(|| $self.relinquish_default.clone());
308    };
309
310    // Read priority array for Copy types: use the common macro.
311    (@read_pa $self:ident, $array_index:ident, $wrap:expr_2021, copy) => {{
312        common::read_priority_array!($self, $array_index, $wrap)
313    }};
314    // Read priority array for Clone (non-Copy) types.
315    (@read_pa $self:ident, $array_index:ident, $wrap:expr_2021, clone) => {{
316        let wrap_fn = $wrap;
317        match $array_index {
318            None => {
319                let elements = $self
320                    .priority_array
321                    .iter()
322                    .map(|slot| match slot {
323                        Some(v) => wrap_fn(v),
324                        None => PropertyValue::Null,
325                    })
326                    .collect();
327                Ok(PropertyValue::List(elements))
328            }
329            Some(0) => Ok(PropertyValue::Unsigned(16)),
330            Some(idx) if (1..=16).contains(&idx) => {
331                match &$self.priority_array[(idx - 1) as usize] {
332                    Some(v) => Ok(wrap_fn(v)),
333                    None => Ok(PropertyValue::Null),
334                }
335            }
336            _ => Err(common::invalid_array_index_error()),
337        }
338    }};
339}
340
341// ---------------------------------------------------------------------------
342// Helper functions for value conversions
343// ---------------------------------------------------------------------------
344
345fn clone_string_to_pv(v: &str) -> PropertyValue {
346    PropertyValue::CharacterString(v.to_owned())
347}
348
349fn clone_octetstring_to_pv(v: &[u8]) -> PropertyValue {
350    PropertyValue::OctetString(v.to_owned())
351}
352
353fn clone_bitstring_to_pv(v: &(u8, Vec<u8>)) -> PropertyValue {
354    PropertyValue::BitString {
355        unused_bits: v.0,
356        data: v.1.clone(),
357    }
358}
359
360/// A set Priority_Array slot of a date-time value type: the date-time choice
361/// of BACnetPriorityValue, context tag 1 around the Date and Time (#2098).
362/// Present_Value and Relinquish_Default stay a bare BACnetDateTime; only the
363/// array element is the CHOICE.
364fn datetime_priority_value(v: (Date, Time)) -> PropertyValue {
365    let mut buf = bytes::BytesMut::with_capacity(12);
366    bacnet_encoding::primitives::encode_priority_date_time(&mut buf, &v.0, &v.1);
367    PropertyValue::ApplicationData(buf.to_vec())
368}
369
370fn datetime_to_pv(dt: &(Date, Time)) -> PropertyValue {
371    PropertyValue::List(vec![PropertyValue::Date(dt.0), PropertyValue::Time(dt.1)])
372}
373
374fn pv_to_date(v: PropertyValue) -> Result<Date, Error> {
375    if let PropertyValue::Date(d) = v {
376        Ok(d)
377    } else {
378        Err(common::invalid_data_type_error())
379    }
380}
381
382fn pv_to_time(v: PropertyValue) -> Result<Time, Error> {
383    if let PropertyValue::Time(t) = v {
384        Ok(t)
385    } else {
386        Err(common::invalid_data_type_error())
387    }
388}
389
390fn pv_to_datetime(v: PropertyValue) -> Result<(Date, Time), Error> {
391    if let PropertyValue::List(items) = v {
392        if items.len() == 2 {
393            let d = if let PropertyValue::Date(d) = &items[0] {
394                *d
395            } else {
396                return Err(common::invalid_data_type_error());
397            };
398            let t = if let PropertyValue::Time(t) = &items[1] {
399                *t
400            } else {
401                return Err(common::invalid_data_type_error());
402            };
403            Ok((d, t))
404        } else {
405            Err(common::invalid_data_type_error())
406        }
407    } else {
408        Err(common::invalid_data_type_error())
409    }
410}
411
412// ---------------------------------------------------------------------------
413// 9 Commandable value objects
414// ---------------------------------------------------------------------------
415
416define_value_object_commandable! {
417    name: IntegerValueObject,
418    doc: "BACnet Integer Value object (type 45).",
419    object_type: ObjectType::INTEGER_VALUE,
420    value_type: i32,
421    default_value: 0,
422    pv_to_property: (|v: &i32| PropertyValue::Signed(*v)),
423    property_to_pv: (|v: PropertyValue| -> Result<i32, Error> {
424        if let PropertyValue::Signed(n) = v { Ok(n) }
425        else { Err(common::invalid_data_type_error()) }
426    }),
427    pa_wrap: PropertyValue::Signed,
428    rd_wrap: (|v: &i32| PropertyValue::Signed(*v)),
429    rd_validate: (|_: &i32| -> Result<(), Error> { Ok(()) }),
430    copy_type: copy,
431    units: EngineeringUnits,
432    cov_increment: u64,
433    property_metadata: metadata::INTEGER_VALUE_BASE,
434}
435
436define_value_object_commandable! {
437    name: PositiveIntegerValueObject,
438    doc: "BACnet Positive Integer Value object (type 48).",
439    object_type: ObjectType::POSITIVE_INTEGER_VALUE,
440    value_type: u64,
441    default_value: 0,
442    pv_to_property: (|v: &u64| PropertyValue::Unsigned(*v)),
443    property_to_pv: (|v: PropertyValue| -> Result<u64, Error> {
444        if let PropertyValue::Unsigned(n) = v { Ok(n) }
445        else { Err(common::invalid_data_type_error()) }
446    }),
447    pa_wrap: PropertyValue::Unsigned,
448    rd_wrap: (|v: &u64| PropertyValue::Unsigned(*v)),
449    rd_validate: (|_: &u64| -> Result<(), Error> { Ok(()) }),
450    copy_type: copy,
451    units: EngineeringUnits,
452    cov_increment: u64,
453    property_metadata: metadata::POSITIVE_INTEGER_VALUE_BASE,
454}
455
456define_value_object_commandable! {
457    name: LargeAnalogValueObject,
458    doc: "BACnet Large Analog Value object (type 46).",
459    object_type: ObjectType::LARGE_ANALOG_VALUE,
460    value_type: f64,
461    default_value: 0.0,
462    pv_to_property: (|v: &f64| PropertyValue::Double(*v)),
463    property_to_pv: (|v: PropertyValue| -> Result<f64, Error> {
464        if let PropertyValue::Double(n) = v { Ok(n) }
465        else { Err(common::invalid_data_type_error()) }
466    }),
467    pa_wrap: PropertyValue::Double,
468    rd_wrap: (|v: &f64| PropertyValue::Double(*v)),
469    rd_validate: (|v: &f64| -> Result<(), Error> {
470        if v.is_finite() {
471            Ok(())
472        } else {
473            Err(common::value_out_of_range_error())
474        }
475    }),
476    copy_type: copy,
477    units: EngineeringUnits,
478    cov_increment: f64,
479    property_metadata: metadata::LARGE_ANALOG_VALUE_BASE,
480}
481
482define_value_object_commandable! {
483    name: CharacterStringValueObject,
484    doc: "BACnet CharacterString Value object (type 40).",
485    object_type: ObjectType::CHARACTERSTRING_VALUE,
486    value_type: String,
487    default_value: String::new(),
488    pv_to_property: (|v: &String| PropertyValue::CharacterString(v.clone())),
489    property_to_pv: (|v: PropertyValue| -> Result<String, Error> {
490        if let PropertyValue::CharacterString(s) = v { Ok(s) }
491        else { Err(common::invalid_data_type_error()) }
492    }),
493    pa_wrap: clone_string_to_pv,
494    rd_wrap: (|v: &String| PropertyValue::CharacterString(v.clone())),
495    rd_validate: (|_: &String| -> Result<(), Error> { Ok(()) }),
496    copy_type: clone,
497    property_metadata: metadata::CHARACTERSTRING_VALUE_BASE,
498}
499
500define_value_object_commandable! {
501    name: OctetStringValueObject,
502    doc: "BACnet OctetString Value object (type 47).",
503    object_type: ObjectType::OCTETSTRING_VALUE,
504    value_type: Vec<u8>,
505    default_value: Vec::new(),
506    pv_to_property: (|v: &Vec<u8>| PropertyValue::OctetString(v.clone())),
507    property_to_pv: (|v: PropertyValue| -> Result<Vec<u8>, Error> {
508        if let PropertyValue::OctetString(b) = v { Ok(b) }
509        else { Err(common::invalid_data_type_error()) }
510    }),
511    pa_wrap: clone_octetstring_to_pv,
512    rd_wrap: (|v: &Vec<u8>| PropertyValue::OctetString(v.clone())),
513    rd_validate: (|_: &Vec<u8>| -> Result<(), Error> { Ok(()) }),
514    copy_type: clone,
515    property_metadata: metadata::OCTETSTRING_VALUE_BASE,
516}
517
518define_value_object_commandable! {
519    name: BitStringValueObject,
520    doc: "BACnet BitString Value object (type 39).",
521    object_type: ObjectType::BITSTRING_VALUE,
522    value_type: (u8, Vec<u8>),
523    default_value: (0, Vec::new()),
524    pv_to_property: (|v: &(u8, Vec<u8>)| PropertyValue::BitString {
525        unused_bits: v.0,
526        data: v.1.clone(),
527    }),
528    property_to_pv: (|v: PropertyValue| -> Result<(u8, Vec<u8>), Error> {
529        if let PropertyValue::BitString { unused_bits, data } = v { Ok((unused_bits, data)) }
530        else { Err(common::invalid_data_type_error()) }
531    }),
532    pa_wrap: clone_bitstring_to_pv,
533    rd_wrap: (|v: &(u8, Vec<u8>)| PropertyValue::BitString {
534        unused_bits: v.0,
535        data: v.1.clone(),
536    }),
537    rd_validate: (|_: &(u8, Vec<u8>)| -> Result<(), Error> { Ok(()) }),
538    copy_type: clone,
539    property_metadata: metadata::BITSTRING_VALUE_BASE,
540}
541
542define_value_object_commandable! {
543    name: DateValueObject,
544    doc: "BACnet Date Value object (type 42).",
545    object_type: ObjectType::DATE_VALUE,
546    value_type: Date,
547    default_value: Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
548    pv_to_property: (|v: &Date| PropertyValue::Date(*v)),
549    property_to_pv: pv_to_date,
550    pa_wrap: PropertyValue::Date,
551    rd_wrap: (|v: &Date| PropertyValue::Date(*v)),
552    rd_validate: (|_: &Date| -> Result<(), Error> { Ok(()) }),
553    copy_type: copy,
554    property_metadata: metadata::DATE_VALUE_BASE,
555}
556
557const TIME_VALUE_PROPERTY_METADATA: &[PropertyMetadata] = &[
558    PropertyMetadata::new(
559        PropertyIdentifier::OBJECT_IDENTIFIER,
560        PropertyConformance::RequiredRead,
561        None,
562        PropertyWriteCapability::ReadOnly,
563    ),
564    PropertyMetadata::new(
565        PropertyIdentifier::OBJECT_NAME,
566        PropertyConformance::RequiredRead,
567        None,
568        PropertyWriteCapability::Always,
569    ),
570    PropertyMetadata::new(
571        PropertyIdentifier::DESCRIPTION,
572        PropertyConformance::Optional,
573        None,
574        PropertyWriteCapability::Always,
575    ),
576    PropertyMetadata::new(
577        PropertyIdentifier::OBJECT_TYPE,
578        PropertyConformance::RequiredRead,
579        None,
580        PropertyWriteCapability::ReadOnly,
581    ),
582    PropertyMetadata::new(
583        PropertyIdentifier::PRESENT_VALUE,
584        PropertyConformance::RequiredRead,
585        None,
586        PropertyWriteCapability::Always,
587    ),
588    PropertyMetadata::new(
589        PropertyIdentifier::STATUS_FLAGS,
590        PropertyConformance::RequiredRead,
591        None,
592        PropertyWriteCapability::ReadOnly,
593    ),
594    PropertyMetadata::new(
595        PropertyIdentifier::OUT_OF_SERVICE,
596        PropertyConformance::Optional,
597        None,
598        PropertyWriteCapability::Always,
599    ),
600    PropertyMetadata::new(
601        PropertyIdentifier::RELIABILITY,
602        PropertyConformance::Optional,
603        None,
604        PropertyWriteCapability::ReadOnly,
605    ),
606    PropertyMetadata::new(
607        PropertyIdentifier::PRIORITY_ARRAY,
608        PropertyConformance::Optional,
609        Some(PropertyPresenceCondition::Commandable),
610        PropertyWriteCapability::ReadOnly,
611    ),
612    PropertyMetadata::new(
613        PropertyIdentifier::RELINQUISH_DEFAULT,
614        PropertyConformance::Optional,
615        Some(PropertyPresenceCondition::Commandable),
616        PropertyWriteCapability::Always,
617    ),
618    PropertyMetadata::new(
619        PropertyIdentifier::CURRENT_COMMAND_PRIORITY,
620        PropertyConformance::Optional,
621        Some(PropertyPresenceCondition::Commandable),
622        PropertyWriteCapability::ReadOnly,
623    ),
624    PropertyMetadata::new(
625        PropertyIdentifier::PROPERTY_LIST,
626        PropertyConformance::RequiredRead,
627        None,
628        PropertyWriteCapability::ReadOnly,
629    ),
630];
631
632define_value_object_commandable! {
633    name: TimeValueObject,
634    doc: "BACnet Time Value object (type 50).",
635    object_type: ObjectType::TIME_VALUE,
636    value_type: Time,
637    default_value: Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
638    pv_to_property: (|v: &Time| PropertyValue::Time(*v)),
639    property_to_pv: pv_to_time,
640    pa_wrap: PropertyValue::Time,
641    rd_wrap: (|v: &Time| PropertyValue::Time(*v)),
642    rd_validate: (|_: &Time| -> Result<(), Error> { Ok(()) }),
643    copy_type: copy,
644    property_metadata: TIME_VALUE_PROPERTY_METADATA,
645}
646
647define_value_object_commandable! {
648    name: DateTimeValueObject,
649    doc: "BACnet DateTime Value object (type 44).",
650    object_type: ObjectType::DATETIME_VALUE,
651    value_type: (Date, Time),
652    default_value: (
653        Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
654        Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
655    ),
656    pv_to_property: (|v: &(Date, Time)| datetime_to_pv(v)),
657    property_to_pv: pv_to_datetime,
658    pa_wrap: datetime_priority_value,
659    rd_wrap: (|v: &(Date, Time)| datetime_to_pv(v)),
660    rd_validate: (|_: &(Date, Time)| -> Result<(), Error> { Ok(()) }),
661    copy_type: copy,
662    property_metadata: metadata::DATETIME_VALUE_BASE,
663}
664
665// ---------------------------------------------------------------------------
666// 3 Commandable pattern value objects (with priority array)
667// ---------------------------------------------------------------------------
668
669define_value_object_commandable! {
670    name: DatePatternValueObject,
671    doc: "BACnet Date Pattern Value object (type 41).",
672    object_type: ObjectType::DATEPATTERN_VALUE,
673    value_type: Date,
674    default_value: Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
675    pv_to_property: (|v: &Date| PropertyValue::Date(*v)),
676    property_to_pv: pv_to_date,
677    pa_wrap: PropertyValue::Date,
678    rd_wrap: (|v: &Date| PropertyValue::Date(*v)),
679    rd_validate: (|_: &Date| -> Result<(), Error> { Ok(()) }),
680    copy_type: copy,
681    property_metadata: metadata::DATEPATTERN_VALUE_BASE,
682}
683
684define_value_object_commandable! {
685    name: TimePatternValueObject,
686    doc: "BACnet Time Pattern Value object (type 49).",
687    object_type: ObjectType::TIMEPATTERN_VALUE,
688    value_type: Time,
689    default_value: Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
690    pv_to_property: (|v: &Time| PropertyValue::Time(*v)),
691    property_to_pv: pv_to_time,
692    pa_wrap: PropertyValue::Time,
693    rd_wrap: (|v: &Time| PropertyValue::Time(*v)),
694    rd_validate: (|_: &Time| -> Result<(), Error> { Ok(()) }),
695    copy_type: copy,
696    property_metadata: metadata::TIMEPATTERN_VALUE_BASE,
697}
698
699define_value_object_commandable! {
700    name: DateTimePatternValueObject,
701    doc: "BACnet DateTime Pattern Value object (type 43).",
702    object_type: ObjectType::DATETIMEPATTERN_VALUE,
703    value_type: (Date, Time),
704    default_value: (
705        Date { year: 0xFF, month: 0xFF, day: 0xFF, day_of_week: 0xFF },
706        Time { hour: 0xFF, minute: 0xFF, second: 0xFF, hundredths: 0xFF },
707    ),
708    pv_to_property: (|v: &(Date, Time)| datetime_to_pv(v)),
709    property_to_pv: pv_to_datetime,
710    pa_wrap: datetime_priority_value,
711    rd_wrap: (|v: &(Date, Time)| datetime_to_pv(v)),
712    rd_validate: (|_: &(Date, Time)| -> Result<(), Error> { Ok(()) }),
713    copy_type: copy,
714    property_metadata: metadata::DATETIMEPATTERN_VALUE_BASE,
715}
716
717// ---------------------------------------------------------------------------
718// Tests
719// ---------------------------------------------------------------------------
720
721#[cfg(test)]
722mod tests;