Skip to main content

bacnet_objects/
property_metadata.rs

1//! Executable property metadata and compatibility projections.
2
3use std::borrow::Cow;
4
5use bacnet_types::enums::PropertyIdentifier;
6
7/// A property's base conformance code in its Clause 12 object table.
8///
9/// Conditions attached to a base code are represented separately by
10/// [`PropertyPresenceCondition`]. This keeps the table code available to RPM
11/// and PICS while preserving why an implemented conditional row is present.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum PropertyConformance {
14    /// Required and readable (`R`).
15    RequiredRead,
16    /// Required and writable (`W`).
17    RequiredWrite,
18    /// Optional (`O`), including an implemented conditionally present row.
19    Optional,
20}
21
22impl PropertyConformance {
23    /// Whether the table code classifies this property as required.
24    pub const fn is_required(self) -> bool {
25        matches!(self, Self::RequiredRead | Self::RequiredWrite)
26    }
27}
28
29/// Why a conditionally present property row is implemented.
30///
31/// Metadata describes effective rows that are already present. These values
32/// retain the conformance reason; they are not predicates evaluated at runtime.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum PropertyPresenceCondition {
36    /// The row is present because the object is commandable.
37    Commandable,
38    /// The object reports intrinsically, and its table's footnotes make this
39    /// row required of such an object: the event configuration, Event_Enable,
40    /// Acked_Transitions, Notify_Type, Event_Time_Stamps and
41    /// Event_Detection_Enable, for example.
42    IntrinsicReportingRequired,
43    /// The object reports intrinsically, and its table's footnotes only let
44    /// this row be present for such an object without requiring it:
45    /// Event_Message_Texts, Event_Message_Texts_Config, the
46    /// Event_Algorithm_Inhibit pair and Time_Delay_Normal.
47    IntrinsicReportingOptional,
48    /// Audit Reporting is active, making its recipient required and writable.
49    AuditReporting,
50    /// An optional object-owned Audit setting is provisioned.
51    ObjectAuditReporting,
52    /// An optional Audit priority filter on a commandable reporting object.
53    CommandableAuditReporting,
54    /// The Value_Source mechanism is implemented, making its source required:
55    /// on a commandable object, or on one whose noncommandable Present_Value
56    /// tracks its last writer (#1552).
57    ValueSourceTracking,
58    /// Source tracking on a commandable object requires its source array and time.
59    CommandableValueSourceTracking,
60    /// The paired Active_Text and Inactive_Text option is implemented.
61    PairedText,
62    /// A Lighting Output has a trim, which makes its Trim_Fade_Time required
63    /// (Addendum 135-2020ca part 5, #1528).
64    LightingTrims,
65    /// A lighting output takes its colour from a colour object, which makes
66    /// Color_Reference required, and Color_Override and
67    /// Override_Color_Reference too where it supports colour override
68    /// (Addendum 135-2020ca part 4, #1527).
69    LightingColor,
70}
71
72/// The write capability implemented by an object's property-write routes.
73///
74/// A conditional capability remains writable for PICS purposes even when the
75/// current object state causes a particular request to be denied.
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77#[non_exhaustive]
78pub enum PropertyWriteCapability {
79    /// No network property-write route is implemented.
80    ReadOnly,
81    /// The property-write route is available whatever Out_Of_Service and
82    /// command ownership say. The object's own checks may still refuse a
83    /// value, or a write in some state: an Audit Log refuses a Buffer_Size
84    /// write while Log_Enable is TRUE, for example.
85    Always,
86    /// The property-write route is available only while Out_Of_Service is true.
87    WhenOutOfService,
88    /// Correction is restricted to the writer the value came from: the
89    /// original command owner at that priority, or for a Present_Value with
90    /// no priority array, its last writer (#1552).
91    WhenCommandOwner,
92    /// The property has no write route of its own, but a write of the named
93    /// property changes it too: State_Text written whole, or its size at
94    /// index 0, sets a multi-state object's Number_Of_States (#1443). A
95    /// WriteProperty naming this property itself is refused, so it doesn't
96    /// count as writable.
97    Through(PropertyIdentifier),
98}
99
100impl PropertyWriteCapability {
101    /// Whether a network property-write route naming this property is
102    /// implemented. [`Self::Through`] has none.
103    pub const fn is_writable(self) -> bool {
104        !matches!(self, Self::ReadOnly | Self::Through(_))
105    }
106
107    /// The property whose writes change this one, for [`Self::Through`].
108    pub const fn written_through(self) -> Option<PropertyIdentifier> {
109        match self {
110            Self::Through(property) => Some(property),
111            _ => None,
112        }
113    }
114}
115
116/// Canonical metadata for one effective property row on a BACnet object.
117///
118/// A migrated object's canonical set includes `PROPERTY_LIST` itself. The
119/// legacy [`BACnetObject::property_list`](crate::traits::BACnetObject::property_list)
120/// projection omits that identifier, and the encoded BACnet Property_List
121/// value additionally omits Object_Identifier, Object_Name, and Object_Type.
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123#[non_exhaustive]
124pub struct PropertyMetadata {
125    /// The standard property identifier.
126    pub property_identifier: PropertyIdentifier,
127    /// The base `R`, `W`, or `O` table classification.
128    pub conformance: PropertyConformance,
129    /// The reason a conditionally present row is implemented.
130    pub presence_condition: Option<PropertyPresenceCondition>,
131    /// The write route implemented for this property.
132    pub write_capability: PropertyWriteCapability,
133}
134
135impl PropertyMetadata {
136    /// Whether this effective row is required, including the rows intrinsic
137    /// reporting, enabled Audit Reporting and command-source mechanisms
138    /// require while retaining optional base table codes.
139    pub const fn is_required(self) -> bool {
140        self.conformance.is_required()
141            || matches!(
142                self.presence_condition,
143                Some(
144                    PropertyPresenceCondition::IntrinsicReportingRequired
145                        | PropertyPresenceCondition::AuditReporting
146                        | PropertyPresenceCondition::ValueSourceTracking
147                        | PropertyPresenceCondition::CommandableValueSourceTracking
148                        | PropertyPresenceCondition::LightingTrims
149                        | PropertyPresenceCondition::LightingColor
150                )
151            )
152    }
153
154    /// Construct one canonical metadata row.
155    ///
156    /// This constructor is the stable construction path as the non-exhaustive
157    /// metadata model gains conditions needed by later object migrations.
158    pub const fn new(
159        property_identifier: PropertyIdentifier,
160        conformance: PropertyConformance,
161        presence_condition: Option<PropertyPresenceCondition>,
162        write_capability: PropertyWriteCapability,
163    ) -> Self {
164        Self {
165            property_identifier,
166            conformance,
167            presence_condition,
168            write_capability,
169        }
170    }
171}
172
173/// Derive the legacy object property-list projection from canonical metadata.
174///
175/// The projection preserves metadata order and omits only `PROPERTY_LIST`.
176pub fn property_list_from_metadata(
177    metadata: &[PropertyMetadata],
178) -> Cow<'static, [PropertyIdentifier]> {
179    Cow::Owned(
180        metadata
181            .iter()
182            .filter_map(|row| {
183                (row.property_identifier != PropertyIdentifier::PROPERTY_LIST)
184                    .then_some(row.property_identifier)
185            })
186            .collect(),
187    )
188}
189
190/// Derive all effectively required identifiers from canonical metadata.
191///
192/// Unlike the legacy property-list projection, this includes `PROPERTY_LIST`.
193/// Consumers such as RPM apply their own service-specific exclusion.
194pub fn required_properties_from_metadata(
195    metadata: &[PropertyMetadata],
196) -> Cow<'static, [PropertyIdentifier]> {
197    Cow::Owned(
198        metadata
199            .iter()
200            .filter_map(|row| row.is_required().then_some(row.property_identifier))
201            .collect(),
202    )
203}
204
205/// Project implemented write capability for one property identifier.
206pub fn is_writable_in_metadata(
207    metadata: &[PropertyMetadata],
208    property_identifier: PropertyIdentifier,
209) -> bool {
210    metadata.iter().any(|row| {
211        row.property_identifier == property_identifier && row.write_capability.is_writable()
212    })
213}