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;