Skip to main content

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;