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::MacAddr;
11use crate::bitstring::{DaysOfWeek, EventTransitionBits};
12use crate::enums::LifeSafetyState;
13use crate::error::Error;
14use crate::primitives::{Date, ObjectIdentifier, PropertyValue, Time};
15
16mod action;
17pub use action::{BACnetActionCommand, BACnetActionList};
18mod access;
19pub use access::{
20    BACnetAccessRule, BACnetAssignedAccessRights, BACnetAuthenticationFactor,
21    BACnetAuthenticationFactorFormat, BACnetAuthenticationPolicy, BACnetAuthenticationPolicyEntry,
22    BACnetCredentialAuthenticationFactor,
23};
24mod audit;
25pub use audit::{
26    AuditPropertyReference, BACnetAuditLogDatum, BACnetAuditLogQueryParameters,
27    BACnetAuditLogRecord, BACnetAuditLogRecordResult, BACnetAuditNotification,
28    BACnetObjectSelector,
29};
30mod color;
31pub use color::{BACnetColorCommand, BACnetXyColor};
32mod event_notification;
33pub use event_notification::{
34    ChangeOfValueChoice, EventNotificationRequest, NotificationParameters,
35};
36mod lift;
37pub use lift::{
38    AssignedLandingCall, BACnetAssignedLandingCalls, BACnetLandingCallStatus,
39    BACnetLandingDoorStatus, BACnetLiftCarCallList, LandingCallCommand, LandingDoor,
40};
41mod lighting;
42pub use lighting::BACnetLightingCommand;
43mod log;
44pub use log::{
45    BACnetEventLogRecord, BACnetLogMultipleRecord, BACnetLogRecord, EventLogDatum, LogData,
46    LogDatum, LogValue,
47};
48mod name_value;
49pub use name_value::BACnetNameValue;
50mod network_port;
51pub use network_port::{BACnetBDTEntry, BACnetFDTEntry, BACnetHostAddress, BACnetHostNPort};
52mod property_access;
53pub use property_access::{AccessResult, BACnetPropertyAccessResult};
54mod property_value;
55pub use property_value::BACnetPropertyValue;
56mod read_access;
57pub use read_access::{PropertyReference, ReadAccessSpecification};
58mod staging;
59pub use staging::BACnetStageLimitValue;
60
61// ---------------------------------------------------------------------------
62// BACnetDateRange (Clause 21 -- used by CalendarEntry and BACnetSpecialEvent)
63// ---------------------------------------------------------------------------
64
65/// BACnet date range: a SEQUENCE of start and end Date values.
66///
67/// On the wire each Date is application-tagged; the codec is
68/// `bacnet_encoding::constructed::{encode_date_range, decode_date_range}`.
69/// An unspecified start or end date leaves that side of the range open.
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct BACnetDateRange {
72    /// The start of the date range (inclusive).
73    pub start_date: Date,
74    /// The end of the date range (inclusive).
75    pub end_date: Date,
76}
77
78// ---------------------------------------------------------------------------
79// BACnetWeekNDay (Clause 21 -- used by CalendarEntry)
80// ---------------------------------------------------------------------------
81
82/// BACnet Week-And-Day: OCTET STRING(3) encoding month, week_of_month,
83/// and day_of_week.
84///
85/// Each field may be `0xFF` to mean "any" (wildcard).
86///
87/// - `month`: 1-12, 13=odd months, 14=even months, 0xFF=any
88/// - `week_of_month`: 1-5 = the days numbered 1-7, 8-14, 15-21, 22-28 and
89///   29-31; 6 = the last 7 days of the month; 7, 8 and 9 = the 7 days before
90///   the last 7, 14 and 21 days; 0xFF=any
91/// - `day_of_week`: 1=Monday..7=Sunday, 0xFF=any
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct BACnetWeekNDay {
94    /// Month (1-14, or 0xFF for any).
95    pub month: u8,
96    /// Week of month (1-9, or 0xFF for any).
97    pub week_of_month: u8,
98    /// Day of week (1-7, or 0xFF for any).
99    pub day_of_week: u8,
100}
101
102impl BACnetWeekNDay {
103    /// Wildcard value indicating "any" for any field.
104    pub const ANY: u8 = 0xFF;
105
106    /// Encode to 3 bytes.
107    pub fn encode(&self) -> [u8; 3] {
108        [self.month, self.week_of_month, self.day_of_week]
109    }
110
111    /// Decode from at least 3 bytes.
112    pub fn decode(data: &[u8]) -> Result<Self, Error> {
113        if data.len() < 3 {
114            return Err(Error::buffer_too_short(3, data.len()));
115        }
116        Ok(Self {
117            month: data[0],
118            week_of_month: data[1],
119            day_of_week: data[2],
120        })
121    }
122}
123
124// ---------------------------------------------------------------------------
125// BACnetCalendarEntry (Clause 21.6 -- Calendar Date_List; Calendar object Clause 12.9)
126// ---------------------------------------------------------------------------
127
128/// BACnet calendar entry: a CHOICE between a specific date, a date range,
129/// or a week-and-day pattern.
130///
131/// Context tags per spec:
132/// - `[0]` Date
133/// - `[1]` DateRange
134/// - `[2]` WeekNDay
135///
136/// The wire codec is `bacnet_encoding::constructed::{encode_calendar_entry,
137/// decode_calendar_entry}`.
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub enum BACnetCalendarEntry {
140    /// A single specific date (context tag 0).
141    Date(Date),
142    /// A contiguous date range (context tag 1).
143    DateRange(BACnetDateRange),
144    /// A recurring week-and-day pattern (context tag 2).
145    WeekNDay(BACnetWeekNDay),
146}
147
148// ---------------------------------------------------------------------------
149// BACnetTimeValue (Clause 12.24 -- Schedule Weekly_Schedule; Clause 21.6)
150// ---------------------------------------------------------------------------
151
152/// BACnet time-value pair: a Time followed by a value of any primitive
153/// datatype.
154///
155/// The value is typed, so a Schedule writes it with its own datatype. Only
156/// primitive values ([`PropertyValue::is_primitive`]) belong here; the codec
157/// refuses to encode a `List` or `ApplicationData`.
158#[derive(Debug, Clone, PartialEq)]
159pub struct BACnetTimeValue {
160    /// The time at which the value applies.
161    pub time: Time,
162    /// The value: NULL, BOOLEAN, Unsigned, INTEGER, REAL, Double, OCTET
163    /// STRING, CharacterString, BIT STRING, ENUMERATED, Date, Time or
164    /// BACnetObjectIdentifier.
165    pub value: PropertyValue,
166}
167
168// ---------------------------------------------------------------------------
169// SpecialEventPeriod (Clause 12.24 -- Schedule Exception_Schedule; Clause 21.6)
170// ---------------------------------------------------------------------------
171
172/// The period portion of a BACnetSpecialEvent: either an inline
173/// CalendarEntry or a reference to an existing Calendar object.
174///
175/// Context tags per spec:
176/// - `[0]` CalendarEntry (constructed)
177/// - `[1]` CalendarReference (ObjectIdentifier)
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub enum SpecialEventPeriod {
180    /// An inline calendar entry (context tag 0).
181    CalendarEntry(BACnetCalendarEntry),
182    /// A reference to a Calendar object (context tag 1).
183    CalendarReference(ObjectIdentifier),
184}
185
186// ---------------------------------------------------------------------------
187// BACnetSpecialEvent (Clause 12.24 -- Schedule Exception_Schedule; Clause 21.6)
188// ---------------------------------------------------------------------------
189
190/// BACnet special event: an exception schedule entry combining a period
191/// definition, a list of time-value pairs, and a priority.
192#[derive(Debug, Clone, PartialEq)]
193pub struct BACnetSpecialEvent {
194    /// The period this special event applies to.
195    pub period: SpecialEventPeriod,
196    /// Ordered list of time-value pairs to apply during this period.
197    pub list_of_time_values: Vec<BACnetTimeValue>,
198    /// Priority for conflict resolution, 1 (highest) to 16 (lowest).
199    ///
200    /// Held as the full Unsigned the wire can carry, so a decoded event keeps
201    /// a priority outside that range intact for its consumer to refuse; the
202    /// Schedule object answers such a value with VALUE_OUT_OF_RANGE.
203    pub event_priority: u64,
204}
205
206// ---------------------------------------------------------------------------
207// BACnetObjectPropertyReference (Clause 21 -- used by Loop and others)
208// ---------------------------------------------------------------------------
209
210/// A reference to a specific property (and optionally an array index) on
211/// a specific object within the same device.
212#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct BACnetObjectPropertyReference {
214    /// The object being referenced.
215    pub object_identifier: ObjectIdentifier,
216    /// The property being referenced (PropertyIdentifier raw value).
217    pub property_identifier: u32,
218    /// Optional array index within the property.
219    pub property_array_index: Option<u32>,
220}
221
222impl BACnetObjectPropertyReference {
223    /// Create a reference without an array index.
224    pub fn new(object_identifier: ObjectIdentifier, property_identifier: u32) -> Self {
225        Self {
226            object_identifier,
227            property_identifier,
228            property_array_index: None,
229        }
230    }
231
232    /// Create a reference with an array index.
233    pub fn new_indexed(
234        object_identifier: ObjectIdentifier,
235        property_identifier: u32,
236        array_index: u32,
237    ) -> Self {
238        Self {
239            object_identifier,
240            property_identifier,
241            property_array_index: Some(array_index),
242        }
243    }
244
245    /// Whether the reference names nothing: its object is at the reserved
246    /// instance 4194303, which Clause 12.1 lets an identifier hold to mean
247    /// uninitialized or unused. A property of this type has no empty
248    /// encoding, so the stack serves an unset one in this form (#1417).
249    pub fn is_unset(&self) -> bool {
250        self.object_identifier.instance_number() == ObjectIdentifier::WILDCARD_INSTANCE
251    }
252}
253
254// ---------------------------------------------------------------------------
255// BACnetDeviceObjectPropertyReference (Clause 21 -- used by several objects)
256// ---------------------------------------------------------------------------
257
258/// Like `BACnetObjectPropertyReference` but may also specify a remote device.
259///
260/// When `device_identifier` is `None`, the reference is to an object in the
261/// local device.
262#[derive(Debug, Clone, PartialEq, Eq)]
263pub struct BACnetDeviceObjectPropertyReference {
264    /// The object being referenced.
265    pub object_identifier: ObjectIdentifier,
266    /// The property being referenced (PropertyIdentifier raw value).
267    pub property_identifier: u32,
268    /// Optional array index within the property.
269    pub property_array_index: Option<u32>,
270    /// Optional device identifier (None = local device).
271    pub device_identifier: Option<ObjectIdentifier>,
272}
273
274impl BACnetDeviceObjectPropertyReference {
275    /// Create a local-device reference without an array index.
276    pub fn new_local(object_identifier: ObjectIdentifier, property_identifier: u32) -> Self {
277        Self {
278            object_identifier,
279            property_identifier,
280            property_array_index: None,
281            device_identifier: None,
282        }
283    }
284
285    /// Create a remote-device reference without an array index.
286    pub fn new_remote(
287        object_identifier: ObjectIdentifier,
288        property_identifier: u32,
289        device_identifier: ObjectIdentifier,
290    ) -> Self {
291        Self {
292            object_identifier,
293            property_identifier,
294            property_array_index: None,
295            device_identifier: Some(device_identifier),
296        }
297    }
298
299    /// Create a reference with an array index (may be local or remote).
300    pub fn with_index(mut self, array_index: u32) -> Self {
301        self.property_array_index = Some(array_index);
302        self
303    }
304
305    /// Whether the device identifier is absent or names a Device object,
306    /// the rule [`BACnetDeviceObjectReference::device_identifier_is_device`]
307    /// applies to that type's device member.
308    pub fn device_identifier_is_device(&self) -> bool {
309        device_identifier_is_device(self.device_identifier)
310    }
311
312    /// Whether the reference names nothing: its object, or its Device when
313    /// it has one, is at the reserved instance 4194303 (Clause 12.1), the
314    /// rule Clause 12.30.11 gives an empty Trend Log Multiple element. A
315    /// property of this type has no empty encoding, so the stack serves an
316    /// unset one in this form (#1417).
317    pub fn is_unset(&self) -> bool {
318        let reserved =
319            |oid: ObjectIdentifier| oid.instance_number() == ObjectIdentifier::WILDCARD_INSTANCE;
320        reserved(self.object_identifier) || self.device_identifier.is_some_and(reserved)
321    }
322}
323
324/// Whether an optional device member is absent or a Device object
325/// identifier, the only object type the member of a device-qualified
326/// reference can hold (Clause 21). The `device_identifier_is_device` methods
327/// of both reference types apply it, and so does every check the objects
328/// and the Python bindings run on a device member.
329pub fn device_identifier_is_device(device: Option<ObjectIdentifier>) -> bool {
330    device.is_none_or(|device| device.object_type() == crate::enums::ObjectType::DEVICE)
331}
332
333// ---------------------------------------------------------------------------
334// BACnetAddress (Clause 21 -- network address used by BACnetRecipient)
335// ---------------------------------------------------------------------------
336
337/// A BACnet network address: network number (0 = local) plus MAC address.
338#[derive(Debug, Clone, PartialEq, Eq)]
339pub struct BACnetAddress {
340    /// Network number (0 = local network, 1-65534 = remote, 65535 = broadcast).
341    pub network_number: u16,
342    /// MAC-layer address (variable length, at most [`Self::MAX_MAC_LEN`] on the
343    /// wire; empty = broadcast).
344    pub mac_address: MacAddr,
345}
346
347impl BACnetAddress {
348    /// The longest `mac_address`, in octets, that this stack encodes or decodes
349    /// in any `BACnetAddress` (#1124, #1156): `bacnet-npdu`'s
350    /// [`MAX_MAC_LEN`](bacnet_npdu::MAX_MAC_LEN), the bound on every DADR,
351    /// SADR and link MAC (#1141), where the reasons for 18 are given.
352    ///
353    /// Clause 21 puts no length on the OCTET STRING, but a MAC is only useful
354    /// if it names a node on some data link. Every codec for this type in
355    /// bacnet-encoding holds to the bound: the recipient, ValueSource and
356    /// AuditLogQuery decoders refuse a longer MAC and their encoders refuse to
357    /// write one. The local setters that store a configured recipient (a
358    /// Recipient_List destination or the Audit_Notification_Recipient) refuse
359    /// one too, so a stored recipient always decodes again. The NPDU codec
360    /// refuses a longer DLEN or SLEN, and bacnet-network's `NetworkLayer` and
361    /// `BACnetRouter` drop a frame whose link-layer source MAC is longer
362    /// (#1198), so the source addresses the stack learns off the network, which
363    /// COV subscription lists and audit records report, fit the bound as well.
364    /// So does the device MAC a You-Are request assigns (#1200).
365    pub const MAX_MAC_LEN: usize = bacnet_npdu::MAX_MAC_LEN;
366
367    /// Create a local-broadcast address.
368    pub fn local_broadcast() -> Self {
369        Self {
370            network_number: 0,
371            mac_address: MacAddr::new(),
372        }
373    }
374
375    /// Create a BACnet/IP address from a 6-byte octet-string (4-byte IPv4 + 2-byte port).
376    pub fn from_ip(ip_port_bytes: [u8; 6]) -> Self {
377        Self {
378            network_number: 0,
379            mac_address: MacAddr::from_slice(&ip_port_bytes),
380        }
381    }
382}
383
384// ---------------------------------------------------------------------------
385// BACnetRecipient (Clause 21 -- used by BACnetDestination / NotificationClass)
386// ---------------------------------------------------------------------------
387
388/// A BACnet notification recipient: either a Device object reference or a
389/// network address.
390///
391/// Context tags per spec:
392/// - `[0]` Device (ObjectIdentifier)
393/// - `[1]` Address (BACnetAddress)
394#[derive(Debug, Clone, PartialEq, Eq)]
395pub enum BACnetRecipient {
396    /// A device identified by its Object Identifier (context tag 0).
397    Device(ObjectIdentifier),
398    /// A device identified by its network address (context tag 1).
399    Address(BACnetAddress),
400}
401
402// ---------------------------------------------------------------------------
403// BACnetDestination -- notification recipient, schedule, and delivery options
404// ---------------------------------------------------------------------------
405
406/// A single entry in a NotificationClass recipient list.
407///
408/// Specifies *who* receives the notification, *when* (days/times), and *how*
409/// (confirmed vs. unconfirmed, which transition types).
410#[derive(Debug, Clone, PartialEq, Eq)]
411pub struct BACnetDestination {
412    /// Weekdays when this destination accepts notifications.
413    pub valid_days: DaysOfWeek,
414    /// Start of the daily time window during which this destination is active.
415    pub from_time: Time,
416    /// End of the daily time window.
417    pub to_time: Time,
418    /// The notification recipient.
419    pub recipient: BACnetRecipient,
420    /// Process identifier on the receiving device.
421    pub process_identifier: u32,
422    /// If true, use ConfirmedEventNotification; otherwise unconfirmed.
423    pub issue_confirmed_notifications: bool,
424    /// Event transitions whose notifications this destination receives.
425    pub transitions: EventTransitionBits,
426}
427
428// ---------------------------------------------------------------------------
429// BACnetEventNotificationSubscription (Clause 21)
430// ---------------------------------------------------------------------------
431
432/// One entry of a Notification Forwarder's Subscribed_Recipients (Clause
433/// 12.51.9): a recipient that asked for forwarded notifications for a limited
434/// time.
435///
436/// The recipient and process identifier identify the entry: the list services
437/// match on those two members alone.
438#[derive(Debug, Clone, PartialEq, Eq)]
439pub struct BACnetEventNotificationSubscription {
440    /// Device or address that receives the notifications (`[0]`).
441    pub recipient: BACnetRecipient,
442    /// Process on the recipient that receives them (`[1]`).
443    pub process_identifier: u32,
444    /// `true` for confirmed notifications, `false` for unconfirmed ones (`[2]`).
445    pub issue_confirmed_notifications: bool,
446    /// Minutes left before the entry lapses (`[3]`). Unlike COV subscription
447    /// lifetimes this counts minutes, not seconds.
448    pub time_remaining: u32,
449}
450
451// ---------------------------------------------------------------------------
452// BACnetPortPermission (Clause 21)
453// ---------------------------------------------------------------------------
454
455/// One element of a Notification Forwarder's Port_Filter (Clause 12.51.11):
456/// whether notifications that arrive through one network port are forwarded.
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
458pub struct BACnetPortPermission {
459    /// The port, by its Clause 6 port ID; 0 on a node that does not route
460    /// (`[0]`).
461    pub port_id: u8,
462    /// `true` when notifications received through the port are forwarded
463    /// (`[1]`).
464    pub enabled: bool,
465}
466
467// ---------------------------------------------------------------------------
468// BACnetScale (Clause 21)
469// ---------------------------------------------------------------------------
470
471/// BACnet Scale: CHOICE { float-scale \[0\] Real, integer-scale \[1\] Integer }.
472#[derive(Debug, Clone, PartialEq)]
473pub enum BACnetScale {
474    /// Present_Value is multiplied by this factor to get engineering units.
475    FloatScale(f32),
476    /// Present_Value is multiplied by ten raised to this power to get engineering units.
477    IntegerScale(i32),
478}
479
480// ---------------------------------------------------------------------------
481// BACnetPrescale (Clause 21)
482// ---------------------------------------------------------------------------
483
484/// BACnet Prescale: SEQUENCE { multiplier Unsigned, modulo-divide Unsigned }.
485#[derive(Debug, Clone, PartialEq, Eq)]
486pub struct BACnetPrescale {
487    /// Numerator of the pulse-to-value conversion ratio, added to the accumulator per input pulse.
488    pub multiplier: u32,
489    /// Denominator of the conversion ratio; each time the accumulator reaches it, the value steps
490    /// by one.
491    pub modulo_divide: u32,
492}
493
494// ---------------------------------------------------------------------------
495// BACnetShedLevel (Clause 21 — used by LoadControl)
496// ---------------------------------------------------------------------------
497
498/// A Load Control shed level (`BACnetShedLevel`, Clause 21): the datatype of
499/// Requested_Shed_Level, Expected_Shed_Level and Actual_Shed_Level
500/// (Clause 12.28).
501///
502/// On the wire each alternative is one primitive context tag: percent `[0]`,
503/// level `[1]`, amount `[2]`. The codec is
504/// `bacnet_encoding::constructed::{encode_shed_level, decode_shed_level}`.
505#[derive(Debug, Clone, PartialEq)]
506pub enum BACnetShedLevel {
507    /// The load to run at, as a percentage of the baseline (Unsigned).
508    Percent(u64),
509    /// A preconfigured shed level (Unsigned). Level 0 means no shed.
510    Level(u64),
511    /// Kilowatts to take off the baseline (REAL).
512    Amount(f32),
513}
514
515// ---------------------------------------------------------------------------
516// BACnetDeviceObjectReference (Clause 21 -- used by Access Control objects)
517// ---------------------------------------------------------------------------
518
519/// BACnet Device Object Reference.
520///
521/// Clause 21 encodes the optional device identifier as context `[0]` followed
522/// by the required object identifier as context `[1]`. `None` identifies a
523/// local object, including Staging target references.
524#[derive(Debug, Clone, PartialEq, Eq)]
525pub struct BACnetDeviceObjectReference {
526    /// Optional device identifier (None = local device).
527    pub device_identifier: Option<ObjectIdentifier>,
528    /// The object being referenced.
529    pub object_identifier: ObjectIdentifier,
530}
531
532impl BACnetDeviceObjectReference {
533    /// Whether the device identifier is absent or names a Device object.
534    ///
535    /// The Clause 21 production gives the first member to the Device that
536    /// holds the object, so an identifier of any other object type can't make
537    /// a valid reference. Setters and write paths that take these references
538    /// refuse one that fails this check.
539    pub fn device_identifier_is_device(&self) -> bool {
540        device_identifier_is_device(self.device_identifier)
541    }
542}
543
544impl From<ObjectIdentifier> for BACnetDeviceObjectReference {
545    /// A reference to an object in this device: no device identifier.
546    fn from(object_identifier: ObjectIdentifier) -> Self {
547        Self {
548            device_identifier: None,
549            object_identifier,
550        }
551    }
552}
553
554// ---------------------------------------------------------------------------
555// FaultParameters (Clause 12.12 -- Fault_Parameters of Event Enrollment)
556// ---------------------------------------------------------------------------
557
558/// Fault parameter variants for configuring fault detection algorithms.
559#[derive(Debug, Clone, PartialEq)]
560pub enum FaultParameters {
561    /// No fault detection.
562    FaultNone,
563    /// Fault on characterstring match.
564    FaultCharacterString {
565        /// Strings that, when the monitored value matches one, indicate a fault.
566        fault_values: Vec<String>,
567    },
568    /// Vendor-defined fault algorithm.
569    FaultExtended {
570        /// Vendor identifier that owns the extended algorithm.
571        vendor_id: u16,
572        /// Vendor-defined identifier of the fault algorithm.
573        extended_fault_type: u32,
574        /// Pre-encoded, vendor-specific parameter bytes carried opaquely.
575        parameters: Vec<u8>,
576    },
577    /// Fault on life safety state match.
578    FaultLifeSafety {
579        /// Life safety states that indicate a fault.
580        fault_values: Vec<LifeSafetyState>,
581        /// Reference to the mode property consulted when evaluating these states.
582        mode_property_reference: BACnetDeviceObjectPropertyReference,
583    },
584    /// Fault on property state match.
585    FaultState {
586        /// Property states that indicate a fault when the monitored property takes one of them.
587        fault_values: Vec<BACnetPropertyStates>,
588    },
589    /// Fault on status flags change.
590    FaultStatusFlags {
591        /// Reference to the StatusFlags property whose FAULT bit is monitored.
592        reference: BACnetDeviceObjectPropertyReference,
593    },
594    /// Fault when value exceeds range.
595    FaultOutOfRange {
596        /// Lower bound of the normal range; values below it are a fault.
597        min_normal: f64,
598        /// Upper bound of the normal range; values above it are a fault.
599        max_normal: f64,
600    },
601    /// Fault from listed reference.
602    FaultListed {
603        /// BACnetLIST property whose entries carry the fault indications to watch.
604        reference: BACnetDeviceObjectPropertyReference,
605    },
606}
607
608// ---------------------------------------------------------------------------
609// BACnetRecipientProcess (Clause 21)
610// ---------------------------------------------------------------------------
611
612/// BACnet Recipient Process — a recipient with an associated process identifier.
613#[derive(Debug, Clone, PartialEq)]
614pub struct BACnetRecipientProcess {
615    /// Device or address that receives the notifications.
616    pub recipient: BACnetRecipient,
617    /// Process on the recipient that asked for the notifications; echoed back in each one.
618    pub process_identifier: u32,
619}
620
621// ---------------------------------------------------------------------------
622// BACnetCOVSubscription (Clause 21)
623// ---------------------------------------------------------------------------
624
625/// BACnet COV Subscription — represents an active COV subscription.
626///
627/// The `monitored_property_reference` is a `BACnetObjectPropertyReference`
628/// (object + property + optional index).
629#[derive(Debug, Clone, PartialEq)]
630pub struct BACnetCOVSubscription {
631    /// Subscriber (device or address plus process identifier) that receives the notifications.
632    pub recipient: BACnetRecipientProcess,
633    /// Object and property being watched.
634    pub monitored_property_reference: BACnetObjectPropertyReference,
635    /// `true` for confirmed notifications, `false` for unconfirmed ones.
636    pub issue_confirmed_notifications: bool,
637    /// Seconds left before the subscription lapses; 0 means it never lapses.
638    pub time_remaining: u32,
639    /// COV increment in use for a numeric monitored property: the requested one, else the
640    /// object's COV_Increment. `None` when the monitored property isn't numeric.
641    pub cov_increment: Option<f32>,
642}
643
644// ---------------------------------------------------------------------------
645// BACnetCOVMultipleSubscription (Clause 21)
646// ---------------------------------------------------------------------------
647
648/// One COV-multiple context of Device `Active_COV_Multiple_Subscriptions`:
649/// a recipient and notification form with one remaining lifetime and maximum
650/// notification delay shared by every nested COV reference.
651#[derive(Debug, Clone, PartialEq)]
652pub struct BACnetCOVMultipleSubscription {
653    /// COV-client address and subscriber process identifier (`[0]`).
654    pub recipient: BACnetRecipientProcess,
655    /// Notification form of this context (`[1]`).
656    pub issue_confirmed_notifications: bool,
657    /// Remaining context lifetime in seconds (`[2]`).
658    pub time_remaining: u32,
659    /// Maximum notification delay in seconds (`[3]`).
660    pub max_notification_delay: u32,
661    /// Monitored objects and their COV references (`[4]`).
662    pub list_of_cov_subscription_specifications: Vec<BACnetCOVSubscriptionSpecification>,
663}
664
665/// The COV references a COV-multiple context holds for one monitored object.
666#[derive(Debug, Clone, PartialEq)]
667pub struct BACnetCOVSubscriptionSpecification {
668    /// Monitored object (`[0]`).
669    pub monitored_object_identifier: ObjectIdentifier,
670    /// COV references on that object (`[1]`).
671    pub list_of_cov_references: Vec<BACnetCOVReference>,
672}
673
674/// One monitored property (a `BACnetPropertyReference`) of a COV-multiple
675/// subscription specification.
676#[derive(Debug, Clone, PartialEq)]
677pub struct BACnetCOVReference {
678    /// Monitored property (`[0]` property identifier).
679    pub property_identifier: crate::enums::PropertyIdentifier,
680    /// Optional array index; absence and zero are distinct.
681    pub property_array_index: Option<u32>,
682    /// COV increment in use (`[1]`), present for numeric monitored values.
683    pub cov_increment: Option<f32>,
684    /// Whether notifications carry the time of change (`[2]`).
685    pub timestamped: bool,
686}
687
688// ---------------------------------------------------------------------------
689// BACnetValueSource (Clause 21)
690// ---------------------------------------------------------------------------
691
692/// BACnet Value Source — identifies the source of a property value write.
693#[derive(Debug, Clone, PartialEq)]
694pub enum BACnetValueSource {
695    /// No identified source: context \[0\] NULL.
696    None,
697    /// Source object, optionally qualified by a device: constructed context \[1\].
698    Object(BACnetDeviceObjectReference),
699    /// Source network/MAC address: constructed context \[2\].
700    Address(BACnetAddress),
701}
702
703// ---------------------------------------------------------------------------
704// BACnetEventParameter -- Event_Parameters algorithm alternatives
705// ---------------------------------------------------------------------------
706
707mod event_parameter;
708pub use event_parameter::{BACnetEventParameter, ChangeOfValueCriteria, event_parameter_tag};
709
710mod property_states;
711pub use property_states::{
712    BACnetExtendedPropertyState, BACnetPropertyStates, BACnetProprietaryPropertyState,
713};
714
715// ---------------------------------------------------------------------------
716// Tests
717// ---------------------------------------------------------------------------
718
719#[cfg(test)]
720mod tests;