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