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