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