bacnet_types/constructed/mod.rs
1//! BACnet constructed data types per ASHRAE 135-2020.
2//!
3//! This module provides compound/structured types that are used by higher-level
4//! BACnet objects (Calendar, Schedule, TrendLog, NotificationClass, Loop, etc.).
5//! All types follow the same `no_std`-compatible pattern used in `primitives.rs`.
6
7#[cfg(not(feature = "std"))]
8use alloc::{string::String, vec::Vec};
9
10use crate::error::Error;
11use crate::primitives::{Date, ObjectIdentifier, Time};
12use crate::MacAddr;
13
14mod audit;
15pub use audit::{
16 AuditPropertyReference, BACnetAuditLogDatum, BACnetAuditLogQueryParameters,
17 BACnetAuditLogRecord, BACnetAuditLogRecordResult, BACnetAuditNotification,
18};
19mod staging;
20pub use staging::BACnetStageLimitValue;
21
22// ---------------------------------------------------------------------------
23// BACnetDateRange (Clause 21 -- used by CalendarEntry and BACnetSpecialEvent)
24// ---------------------------------------------------------------------------
25
26/// BACnet date range: a SEQUENCE of start and end Date values.
27///
28/// Encoded as 8 bytes: 4 bytes for start_date followed by 4 bytes for end_date.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct BACnetDateRange {
31 /// The start of the date range (inclusive).
32 pub start_date: Date,
33 /// The end of the date range (inclusive).
34 pub end_date: Date,
35}
36
37impl BACnetDateRange {
38 /// Encode to 8 bytes (start_date || end_date).
39 pub fn encode(&self) -> [u8; 8] {
40 let mut out = [0u8; 8];
41 out[..4].copy_from_slice(&self.start_date.encode());
42 out[4..].copy_from_slice(&self.end_date.encode());
43 out
44 }
45
46 /// Decode from at least 8 bytes.
47 pub fn decode(data: &[u8]) -> Result<Self, Error> {
48 if data.len() < 8 {
49 return Err(Error::buffer_too_short(8, data.len()));
50 }
51 Ok(Self {
52 start_date: Date::decode(&data[0..4])?,
53 end_date: Date::decode(&data[4..8])?,
54 })
55 }
56}
57
58// ---------------------------------------------------------------------------
59// BACnetWeekNDay (Clause 21 -- used by CalendarEntry)
60// ---------------------------------------------------------------------------
61
62/// BACnet Week-And-Day: OCTET STRING(3) encoding month, week_of_month,
63/// and day_of_week.
64///
65/// Each field may be `0xFF` to mean "any" (wildcard).
66///
67/// - `month`: 1-12, 13=odd, 14=even, 0xFF=any
68/// - `week_of_month`: 1=first, 2=second, ..., 5=last, 6=any-in-first,
69/// 0xFF=any
70/// - `day_of_week`: 1=Monday..7=Sunday, 0xFF=any
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct BACnetWeekNDay {
73 /// Month (1-14, or 0xFF for any).
74 pub month: u8,
75 /// Week of month (1-6, or 0xFF for any).
76 pub week_of_month: u8,
77 /// Day of week (1-7, or 0xFF for any).
78 pub day_of_week: u8,
79}
80
81impl BACnetWeekNDay {
82 /// Wildcard value indicating "any" for any field.
83 pub const ANY: u8 = 0xFF;
84
85 /// Encode to 3 bytes.
86 pub fn encode(&self) -> [u8; 3] {
87 [self.month, self.week_of_month, self.day_of_week]
88 }
89
90 /// Decode from at least 3 bytes.
91 pub fn decode(data: &[u8]) -> Result<Self, Error> {
92 if data.len() < 3 {
93 return Err(Error::buffer_too_short(3, data.len()));
94 }
95 Ok(Self {
96 month: data[0],
97 week_of_month: data[1],
98 day_of_week: data[2],
99 })
100 }
101}
102
103// ---------------------------------------------------------------------------
104// BACnetCalendarEntry (Clause 12.6.3 -- property list of Calendar object)
105// ---------------------------------------------------------------------------
106
107/// BACnet calendar entry: a CHOICE between a specific date, a date range,
108/// or a week-and-day pattern.
109///
110/// Context tags per spec:
111/// - `[0]` Date
112/// - `[1]` DateRange
113/// - `[2]` WeekNDay
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum BACnetCalendarEntry {
116 /// A single specific date (context tag 0).
117 Date(Date),
118 /// A contiguous date range (context tag 1).
119 DateRange(BACnetDateRange),
120 /// A recurring week-and-day pattern (context tag 2).
121 WeekNDay(BACnetWeekNDay),
122}
123
124// ---------------------------------------------------------------------------
125// BACnetTimeValue (Clause 12.17.4 -- used by Schedule weekly_schedule)
126// ---------------------------------------------------------------------------
127
128/// BACnet time-value pair: a Time followed by an application-tagged value.
129///
130/// The `value` field holds raw application-tagged bytes because the value
131/// type is polymorphic (Real, Boolean, Unsigned, Null, etc.) and the Schedule
132/// object stores them opaquely for later dispatch.
133#[derive(Debug, Clone, PartialEq)]
134pub struct BACnetTimeValue {
135 /// The time at which the value applies.
136 pub time: Time,
137 /// Raw application-tagged BACnet encoding of the value.
138 pub value: Vec<u8>,
139}
140
141// ---------------------------------------------------------------------------
142// SpecialEventPeriod (Clause 12.17.5 -- used by BACnetSpecialEvent)
143// ---------------------------------------------------------------------------
144
145/// The period portion of a BACnetSpecialEvent: either an inline
146/// CalendarEntry or a reference to an existing Calendar object.
147///
148/// Context tags per spec:
149/// - `[0]` CalendarEntry (constructed)
150/// - `[1]` CalendarReference (ObjectIdentifier)
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub enum SpecialEventPeriod {
153 /// An inline calendar entry (context tag 0).
154 CalendarEntry(BACnetCalendarEntry),
155 /// A reference to a Calendar object (context tag 1).
156 CalendarReference(ObjectIdentifier),
157}
158
159// ---------------------------------------------------------------------------
160// BACnetSpecialEvent (Clause 12.17.5 -- exception_schedule of Schedule)
161// ---------------------------------------------------------------------------
162
163/// BACnet special event: an exception schedule entry combining a period
164/// definition, a list of time-value pairs, and a priority.
165#[derive(Debug, Clone, PartialEq)]
166pub struct BACnetSpecialEvent {
167 /// The period this special event applies to.
168 pub period: SpecialEventPeriod,
169 /// Ordered list of time-value pairs to apply during this period.
170 pub list_of_time_values: Vec<BACnetTimeValue>,
171 /// Priority for conflict resolution (1=highest..16=lowest).
172 pub event_priority: u8,
173}
174
175// ---------------------------------------------------------------------------
176// BACnetObjectPropertyReference (Clause 21 -- used by Loop and others)
177// ---------------------------------------------------------------------------
178
179/// A reference to a specific property (and optionally an array index) on
180/// a specific object within the same device.
181#[derive(Debug, Clone, PartialEq, Eq)]
182pub struct BACnetObjectPropertyReference {
183 /// The object being referenced.
184 pub object_identifier: ObjectIdentifier,
185 /// The property being referenced (PropertyIdentifier raw value).
186 pub property_identifier: u32,
187 /// Optional array index within the property.
188 pub property_array_index: Option<u32>,
189}
190
191impl BACnetObjectPropertyReference {
192 /// Create a reference without an array index.
193 pub fn new(object_identifier: ObjectIdentifier, property_identifier: u32) -> Self {
194 Self {
195 object_identifier,
196 property_identifier,
197 property_array_index: None,
198 }
199 }
200
201 /// Create a reference with an array index.
202 pub fn new_indexed(
203 object_identifier: ObjectIdentifier,
204 property_identifier: u32,
205 array_index: u32,
206 ) -> Self {
207 Self {
208 object_identifier,
209 property_identifier,
210 property_array_index: Some(array_index),
211 }
212 }
213}
214
215// ---------------------------------------------------------------------------
216// BACnetDeviceObjectPropertyReference (Clause 21 -- used by several objects)
217// ---------------------------------------------------------------------------
218
219/// Like `BACnetObjectPropertyReference` but may also specify a remote device.
220///
221/// When `device_identifier` is `None`, the reference is to an object in the
222/// local device.
223#[derive(Debug, Clone, PartialEq, Eq)]
224pub struct BACnetDeviceObjectPropertyReference {
225 /// The object being referenced.
226 pub object_identifier: ObjectIdentifier,
227 /// The property being referenced (PropertyIdentifier raw value).
228 pub property_identifier: u32,
229 /// Optional array index within the property.
230 pub property_array_index: Option<u32>,
231 /// Optional device identifier (None = local device).
232 pub device_identifier: Option<ObjectIdentifier>,
233}
234
235impl BACnetDeviceObjectPropertyReference {
236 /// Create a local-device reference without an array index.
237 pub fn new_local(object_identifier: ObjectIdentifier, property_identifier: u32) -> Self {
238 Self {
239 object_identifier,
240 property_identifier,
241 property_array_index: None,
242 device_identifier: None,
243 }
244 }
245
246 /// Create a remote-device reference without an array index.
247 pub fn new_remote(
248 object_identifier: ObjectIdentifier,
249 property_identifier: u32,
250 device_identifier: ObjectIdentifier,
251 ) -> Self {
252 Self {
253 object_identifier,
254 property_identifier,
255 property_array_index: None,
256 device_identifier: Some(device_identifier),
257 }
258 }
259
260 /// Create a reference with an array index (may be local or remote).
261 pub fn with_index(mut self, array_index: u32) -> Self {
262 self.property_array_index = Some(array_index);
263 self
264 }
265}
266
267// ---------------------------------------------------------------------------
268// BACnetAddress (Clause 21 -- network address used by BACnetRecipient)
269// ---------------------------------------------------------------------------
270
271/// A BACnet network address: network number (0 = local) plus MAC address.
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub struct BACnetAddress {
274 /// Network number (0 = local network, 1-65534 = remote, 65535 = broadcast).
275 pub network_number: u16,
276 /// MAC-layer address (variable length; empty = local broadcast).
277 pub mac_address: MacAddr,
278}
279
280impl BACnetAddress {
281 /// Create a local-broadcast address.
282 pub fn local_broadcast() -> Self {
283 Self {
284 network_number: 0,
285 mac_address: MacAddr::new(),
286 }
287 }
288
289 /// Create a BACnet/IP address from a 6-byte octet-string (4-byte IPv4 + 2-byte port).
290 pub fn from_ip(ip_port_bytes: [u8; 6]) -> Self {
291 Self {
292 network_number: 0,
293 mac_address: MacAddr::from_slice(&ip_port_bytes),
294 }
295 }
296}
297
298// ---------------------------------------------------------------------------
299// BACnetRecipient (Clause 21 -- used by BACnetDestination / NotificationClass)
300// ---------------------------------------------------------------------------
301
302/// A BACnet notification recipient: either a Device object reference or a
303/// network address.
304///
305/// Context tags per spec:
306/// - `[0]` Device (ObjectIdentifier)
307/// - `[1]` Address (BACnetAddress)
308#[derive(Debug, Clone, PartialEq, Eq)]
309pub enum BACnetRecipient {
310 /// A device identified by its Object Identifier (context tag 0).
311 Device(ObjectIdentifier),
312 /// A device identified by its network address (context tag 1).
313 Address(BACnetAddress),
314}
315
316// ---------------------------------------------------------------------------
317// BACnetDestination (Clause 12.15.5 -- recipient_list of NotificationClass)
318// ---------------------------------------------------------------------------
319
320/// A single entry in a NotificationClass recipient list.
321///
322/// Specifies *who* receives the notification, *when* (days/times), and *how*
323/// (confirmed vs. unconfirmed, which transition types).
324#[derive(Debug, Clone, PartialEq, Eq)]
325pub struct BACnetDestination {
326 /// Bitmask of valid days (bit 0 = Monday ... bit 6 = Sunday), 7 bits used.
327 pub valid_days: u8,
328 /// Start of the daily time window during which this destination is active.
329 pub from_time: Time,
330 /// End of the daily time window.
331 pub to_time: Time,
332 /// The notification recipient.
333 pub recipient: BACnetRecipient,
334 /// Process identifier on the receiving device.
335 pub process_identifier: u32,
336 /// If true, use ConfirmedEventNotification; otherwise unconfirmed.
337 pub issue_confirmed_notifications: bool,
338 /// Bitmask of event transitions to send (bit 0=ToOffNormal, bit 1=ToFault,
339 /// bit 2=ToNormal), 3 bits used.
340 pub transitions: u8,
341}
342
343// ---------------------------------------------------------------------------
344// LogDatum (Clause 12.20.5 -- log_buffer element datum of TrendLog)
345// ---------------------------------------------------------------------------
346
347/// The datum field of a BACnetLogRecord: a CHOICE covering all possible
348/// logged value types.
349///
350/// Context tags per spec:
351/// - `[0]` log-status (BACnetLogStatus, 8-bit flags)
352/// - `[1]` boolean-value
353/// - `[2]` real-value
354/// - `[3]` enum-value (unsigned)
355/// - `[4]` unsigned-value
356/// - `[5]` signed-value
357/// - `[6]` bitstring-value
358/// - `[7]` null-value
359/// - `[8]` failure (BACnetError)
360/// - `[9]` time-change (REAL, clock-adjustment seconds)
361/// - `[10]` any-value (raw application-tagged bytes)
362#[derive(Debug, Clone, PartialEq)]
363pub enum LogDatum {
364 /// Log-status flags (context tag 0). Bit 0=log-disabled, bit 1=buffer-purged,
365 /// bit 2=log-interrupted.
366 LogStatus(u8),
367 /// Boolean value (context tag 1).
368 BooleanValue(bool),
369 /// Real (f32) value (context tag 2).
370 RealValue(f32),
371 /// Enumerated value (context tag 3).
372 EnumValue(u32),
373 /// Unsigned integer value (context tag 4).
374 UnsignedValue(u64),
375 /// Signed integer value (context tag 5).
376 SignedValue(i64),
377 /// Bit-string value (context tag 6).
378 BitstringValue {
379 /// Number of unused bits in the last byte.
380 unused_bits: u8,
381 /// The bit data.
382 data: Vec<u8>,
383 },
384 /// Null value (context tag 7).
385 NullValue,
386 /// Error (context tag 8): error class + error code.
387 Failure {
388 /// Raw BACnet error class value.
389 error_class: u32,
390 /// Raw BACnet error code value.
391 error_code: u32,
392 },
393 /// Time-change: clock-adjustment amount in seconds (context tag 9).
394 TimeChange(f32),
395 /// Any-value: raw application-tagged bytes for types not enumerated above
396 /// (context tag 10).
397 AnyValue(Vec<u8>),
398}
399
400// ---------------------------------------------------------------------------
401// BACnetLogRecord (Clause 12.20.5 -- log_buffer of TrendLog)
402// ---------------------------------------------------------------------------
403
404/// A single record stored in a TrendLog object's log buffer.
405///
406/// Contains a timestamp (date + time), the logged datum, and optional
407/// status flags that were in effect at logging time.
408#[derive(Debug, Clone, PartialEq)]
409pub struct BACnetLogRecord {
410 /// The date at which this record was logged.
411 pub date: Date,
412 /// The time at which this record was logged.
413 pub time: Time,
414 /// The logged datum.
415 pub log_datum: LogDatum,
416 /// Optional status flags at time of logging (4-bit BACnet StatusFlags).
417 pub status_flags: Option<u8>,
418}
419
420// ---------------------------------------------------------------------------
421// BACnetScale (Clause 21)
422// ---------------------------------------------------------------------------
423
424/// BACnet Scale: CHOICE { float-scale [0] Real, integer-scale [1] Integer }.
425#[derive(Debug, Clone, PartialEq)]
426pub enum BACnetScale {
427 FloatScale(f32),
428 IntegerScale(i32),
429}
430
431// ---------------------------------------------------------------------------
432// BACnetPrescale (Clause 21)
433// ---------------------------------------------------------------------------
434
435/// BACnet Prescale: SEQUENCE { multiplier Unsigned, modulo-divide Unsigned }.
436#[derive(Debug, Clone, PartialEq, Eq)]
437pub struct BACnetPrescale {
438 pub multiplier: u32,
439 pub modulo_divide: u32,
440}
441
442// ---------------------------------------------------------------------------
443// BACnetShedLevel (Clause 12 — used by LoadControl)
444// ---------------------------------------------------------------------------
445
446/// BACnet ShedLevel — CHOICE for LoadControl.
447#[derive(Debug, Clone, PartialEq)]
448pub enum BACnetShedLevel {
449 /// Shed level as a percentage (0–100).
450 Percent(u32),
451 /// Shed level as an abstract level value.
452 Level(u32),
453 /// Shed level as a floating-point amount.
454 Amount(f32),
455}
456
457// ---------------------------------------------------------------------------
458// BACnetLightingCommand (Clause 21 -- used by LightingOutput)
459// ---------------------------------------------------------------------------
460
461/// BACnet Lighting Command -- controls lighting operations.
462///
463/// Per ASHRAE 135-2020 Clause 21, this type is used by the LightingOutput
464/// object's LIGHTING_COMMAND property to specify a lighting operation
465/// (e.g., fade, ramp, step) with optional parameters.
466#[derive(Debug, Clone, PartialEq)]
467pub struct BACnetLightingCommand {
468 /// The lighting operation (LightingOperation enum raw value).
469 pub operation: u32,
470 /// Optional target brightness level (0.0 to 100.0 percent).
471 pub target_level: Option<f32>,
472 /// Optional ramp rate (percent per second).
473 pub ramp_rate: Option<f32>,
474 /// Optional step increment (percent).
475 pub step_increment: Option<f32>,
476 /// Optional fade time (milliseconds).
477 pub fade_time: Option<u32>,
478 /// Optional priority (1-16).
479 pub priority: Option<u32>,
480}
481
482// ---------------------------------------------------------------------------
483// BACnetDeviceObjectReference (Clause 21 -- used by Access Control objects)
484// ---------------------------------------------------------------------------
485
486/// BACnet Device Object Reference.
487///
488/// Clause 21 encodes the optional device identifier as context `[0]` followed
489/// by the required object identifier as context `[1]`. `None` identifies a
490/// local object, including Staging target references.
491#[derive(Debug, Clone, PartialEq, Eq)]
492pub struct BACnetDeviceObjectReference {
493 /// Optional device identifier (None = local device).
494 pub device_identifier: Option<ObjectIdentifier>,
495 /// The object being referenced.
496 pub object_identifier: ObjectIdentifier,
497}
498
499// ---------------------------------------------------------------------------
500// BACnetAccessRule (Clause 12 -- used by AccessRights object)
501// ---------------------------------------------------------------------------
502
503/// BACnet Access Rule for access control objects.
504///
505/// Specifies a time range and location with an enable/disable flag,
506/// used in positive and negative access rules lists.
507#[derive(Debug, Clone, PartialEq)]
508pub struct BACnetAccessRule {
509 /// Time range specifier: 0 = specified, 1 = always.
510 pub time_range_specifier: u32,
511 /// Optional time range (start date, start time, end date, end time).
512 /// Present only when `time_range_specifier` is 0 (specified).
513 pub time_range: Option<(Date, Time, Date, Time)>,
514 /// Location specifier: 0 = specified, 1 = all.
515 pub location_specifier: u32,
516 /// Optional location reference. Present only when `location_specifier` is 0 (specified).
517 pub location: Option<BACnetDeviceObjectReference>,
518 /// Whether access is enabled or disabled by this rule.
519 pub enable: bool,
520}
521
522// ---------------------------------------------------------------------------
523// BACnetAssignedAccessRights (Clause 12 -- used by AccessCredential/AccessUser)
524// ---------------------------------------------------------------------------
525
526/// BACnet Assigned Access Rights.
527///
528/// Associates a reference to an AccessRights object with an enable flag.
529#[derive(Debug, Clone, PartialEq, Eq)]
530pub struct BACnetAssignedAccessRights {
531 /// Reference to an AccessRights object.
532 pub assigned_access_rights: ObjectIdentifier,
533 /// Whether these access rights are currently enabled.
534 pub enable: bool,
535}
536
537// ---------------------------------------------------------------------------
538// BACnetAssignedLandingCalls (Clause 12 -- used by ElevatorGroup)
539// ---------------------------------------------------------------------------
540
541/// BACnet Assigned Landing Calls for elevator group.
542#[derive(Debug, Clone, PartialEq)]
543pub struct BACnetAssignedLandingCalls {
544 /// The floor number for this landing call.
545 pub floor_number: u8,
546 /// Direction: 0=up, 1=down, 2=unknown.
547 pub direction: u32,
548}
549
550// ---------------------------------------------------------------------------
551// FaultParameters (Clause 12.12.50)
552// ---------------------------------------------------------------------------
553
554/// Fault parameter variants for configuring fault detection algorithms.
555#[derive(Debug, Clone, PartialEq)]
556pub enum FaultParameters {
557 /// No fault detection.
558 FaultNone,
559 /// Fault on characterstring match.
560 FaultCharacterString { fault_values: Vec<String> },
561 /// Vendor-defined fault algorithm.
562 FaultExtended {
563 vendor_id: u16,
564 extended_fault_type: u32,
565 parameters: Vec<u8>,
566 },
567 /// Fault on life safety state match.
568 FaultLifeSafety {
569 fault_values: Vec<u32>,
570 mode_for_reference: BACnetDeviceObjectPropertyReference,
571 },
572 /// Fault on property state match.
573 FaultState {
574 fault_values: Vec<BACnetPropertyStates>,
575 },
576 /// Fault on status flags change.
577 FaultStatusFlags {
578 reference: BACnetDeviceObjectPropertyReference,
579 },
580 /// Fault when value exceeds range.
581 FaultOutOfRange { min_normal: f64, max_normal: f64 },
582 /// Fault from listed reference.
583 FaultListed {
584 reference: BACnetDeviceObjectPropertyReference,
585 },
586}
587
588// ---------------------------------------------------------------------------
589// BACnetRecipientProcess (Clause 21)
590// ---------------------------------------------------------------------------
591
592/// BACnet Recipient Process — a recipient with an associated process identifier.
593#[derive(Debug, Clone, PartialEq)]
594pub struct BACnetRecipientProcess {
595 pub recipient: BACnetRecipient,
596 pub process_identifier: u32,
597}
598
599// ---------------------------------------------------------------------------
600// BACnetCOVSubscription (Clause 21)
601// ---------------------------------------------------------------------------
602
603/// BACnet COV Subscription — represents an active COV subscription.
604///
605/// The `monitored_property_reference` is a `BACnetObjectPropertyReference`
606/// (object + property + optional index).
607#[derive(Debug, Clone, PartialEq)]
608pub struct BACnetCOVSubscription {
609 pub recipient: BACnetRecipientProcess,
610 pub monitored_property_reference: BACnetObjectPropertyReference,
611 pub issue_confirmed_notifications: bool,
612 pub time_remaining: u32,
613 pub cov_increment: Option<f32>,
614}
615
616// ---------------------------------------------------------------------------
617// BACnetValueSource (Clause 21)
618// ---------------------------------------------------------------------------
619
620/// BACnet Value Source — identifies the source of a property value write.
621#[derive(Debug, Clone, PartialEq)]
622pub enum BACnetValueSource {
623 None,
624 Object(ObjectIdentifier),
625 Address(BACnetAddress),
626}
627
628// ---------------------------------------------------------------------------
629// BACnetEventParameter (Clause 13.5 -- Event_Parameters CHOICE alternatives)
630// ---------------------------------------------------------------------------
631
632mod event_parameter;
633pub use event_parameter::{event_parameter_tag, BACnetEventParameter, ChangeOfValueCriteria};
634
635mod property_states;
636pub use property_states::{
637 BACnetExtendedPropertyState, BACnetPropertyStates, BACnetProprietaryPropertyState,
638};
639
640// ---------------------------------------------------------------------------
641// Tests
642// ---------------------------------------------------------------------------
643
644#[cfg(test)]
645mod tests;