Skip to main content

bacnet_types/enums/
misc.rs

1// ===========================================================================
2// Miscellaneous enums
3// ===========================================================================
4
5use super::{ConfirmedServiceChoice, UnconfirmedServiceChoice};
6
7bacnet_enum! {
8    /// BACnet load control shed state (Clause 12.28).
9    pub struct ShedState(u32);
10
11    const SHED_INACTIVE = 0;
12    const SHED_REQUEST_PENDING = 1;
13    const SHED_COMPLIANT = 2;
14    const SHED_NON_COMPLIANT = 3;
15}
16
17bacnet_enum! {
18    /// BACnet node type for Structured View (Clause 12.29).
19    pub struct NodeType(u32);
20
21    const UNKNOWN = 0;
22    const SYSTEM = 1;
23    const NETWORK = 2;
24    const DEVICE = 3;
25    const ORGANIZATIONAL = 4;
26    const AREA = 5;
27    const EQUIPMENT = 6;
28    const POINT = 7;
29    const COLLECTION = 8;
30    const PROPERTY = 9;
31    const FUNCTIONAL = 10;
32    const OTHER = 11;
33    const SUBSYSTEM = 12;
34    const BUILDING = 13;
35    const FLOOR = 14;
36    const SECTION = 15;
37    const MODULE = 16;
38    const TREE = 17;
39    const MEMBER = 18;
40    const PROTOCOL = 19;
41    const ROOM = 20;
42    const ZONE = 21;
43}
44
45bacnet_enum! {
46    /// BACnet maintenance type (Clause 21); used by life safety point/zone
47    /// and access door Maintenance_Required.
48    pub struct Maintenance(u32);
49
50    const NONE = 0;
51    const PERIODIC_TEST = 1;
52    const NEED_SERVICE_OPERATIONAL = 2;
53    const NEED_SERVICE_INOPERATIVE = 3;
54}
55
56bacnet_enum! {
57    /// BACnet subordinate/parent relationship for Structured View
58    /// (Clause 21).
59    ///
60    /// Values above `DEFAULT` come in even/odd forward/reverse pairs
61    /// (e.g. `CONTAINS`(2) / `CONTAINED_BY`(3)); proprietary extensions
62    /// follow the same pairing so the opposite of a relationship `n > 1` is
63    /// always `n ^ 1`.
64    pub struct Relationship(u32);
65
66    const UNKNOWN = 0;
67    const DEFAULT = 1;
68    const CONTAINS = 2;
69    const CONTAINED_BY = 3;
70    const USES = 4;
71    const USED_BY = 5;
72    const COMMANDS = 6;
73    const COMMANDED_BY = 7;
74    const ADJUSTS = 8;
75    const ADJUSTED_BY = 9;
76    const INGRESS = 10;
77    const EGRESS = 11;
78    const SUPPLIES_AIR = 12;
79    const RECEIVES_AIR = 13;
80    const SUPPLIES_HOT_AIR = 14;
81    const RECEIVES_HOT_AIR = 15;
82    const SUPPLIES_COOL_AIR = 16;
83    const RECEIVES_COOL_AIR = 17;
84    const SUPPLIES_POWER = 18;
85    const RECEIVES_POWER = 19;
86    const SUPPLIES_GAS = 20;
87    const RECEIVES_GAS = 21;
88    const SUPPLIES_WATER = 22;
89    const RECEIVES_WATER = 23;
90    const SUPPLIES_HOT_WATER = 24;
91    const RECEIVES_HOT_WATER = 25;
92    const SUPPLIES_COOL_WATER = 26;
93    const RECEIVES_COOL_WATER = 27;
94    const SUPPLIES_STEAM = 28;
95    const RECEIVES_STEAM = 29;
96}
97
98bacnet_enum! {
99    /// BACnet acknowledgment filter for GetEnrollmentSummary (Clause 13.11.1.1).
100    pub struct AcknowledgmentFilter(u32);
101
102    const ALL = 0;
103    const ACKED = 1;
104    const NOT_ACKED = 2;
105}
106
107bacnet_enum! {
108    /// Event-state filter for GetEnrollmentSummary (Clause 13.11.1.1).
109    pub struct EnrollmentSummaryEventStateFilter(u32);
110
111    const OFFNORMAL = 0;
112    const FAULT = 1;
113    const NORMAL = 2;
114    const ALL = 3;
115    const ACTIVE = 4;
116}
117
118bacnet_enum! {
119    /// Event transition bit positions (Clause 12.11).
120    pub struct EventTransitionBits(u8);
121
122    const TO_OFFNORMAL = 0;
123    const TO_FAULT = 1;
124    const TO_NORMAL = 2;
125}
126
127bacnet_enum! {
128    /// Bit positions within a `BACnetServicesSupported` bit string (Clause 21).
129    pub struct ServiceSupported(u8);
130
131    const ACKNOWLEDGE_ALARM = 0;
132    const CONFIRMED_COV_NOTIFICATION = 1;
133    const CONFIRMED_EVENT_NOTIFICATION = 2;
134    const GET_ALARM_SUMMARY = 3;
135    const GET_ENROLLMENT_SUMMARY = 4;
136    const SUBSCRIBE_COV = 5;
137    const ATOMIC_READ_FILE = 6;
138    const ATOMIC_WRITE_FILE = 7;
139    const ADD_LIST_ELEMENT = 8;
140    const REMOVE_LIST_ELEMENT = 9;
141    const CREATE_OBJECT = 10;
142    const DELETE_OBJECT = 11;
143    const READ_PROPERTY = 12;
144    // 13: readPropertyConditional (removed)
145    const READ_PROPERTY_MULTIPLE = 14;
146    const WRITE_PROPERTY = 15;
147    const WRITE_PROPERTY_MULTIPLE = 16;
148    const DEVICE_COMMUNICATION_CONTROL = 17;
149    const CONFIRMED_PRIVATE_TRANSFER = 18;
150    const CONFIRMED_TEXT_MESSAGE = 19;
151    const REINITIALIZE_DEVICE = 20;
152    const VT_OPEN = 21;
153    const VT_CLOSE = 22;
154    const VT_DATA = 23;
155    // 24: authenticate (removed), 25: requestKey (removed)
156    const I_AM = 26;
157    const I_HAVE = 27;
158    const UNCONFIRMED_COV_NOTIFICATION = 28;
159    const UNCONFIRMED_EVENT_NOTIFICATION = 29;
160    const UNCONFIRMED_PRIVATE_TRANSFER = 30;
161    const UNCONFIRMED_TEXT_MESSAGE = 31;
162    const TIME_SYNCHRONIZATION = 32;
163    const WHO_HAS = 33;
164    const WHO_IS = 34;
165    const READ_RANGE = 35;
166    const UTC_TIME_SYNCHRONIZATION = 36;
167    const LIFE_SAFETY_OPERATION = 37;
168    const SUBSCRIBE_COV_PROPERTY = 38;
169    const GET_EVENT_INFORMATION = 39;
170    const WRITE_GROUP = 40;
171    const SUBSCRIBE_COV_PROPERTY_MULTIPLE = 41;
172    const CONFIRMED_COV_NOTIFICATION_MULTIPLE = 42;
173    const UNCONFIRMED_COV_NOTIFICATION_MULTIPLE = 43;
174    const CONFIRMED_AUDIT_NOTIFICATION = 44;
175    const AUDIT_LOG_QUERY = 45;
176    const UNCONFIRMED_AUDIT_NOTIFICATION = 46;
177    const WHO_AM_I = 47;
178    const YOU_ARE = 48;
179}
180
181impl ServiceSupported {
182    /// The `BACnetServicesSupported` bit for a confirmed service choice.
183    ///
184    /// The choice and bit numberings diverge for every service added after
185    /// the original standard (read-range is choice 26 but bit 35,
186    /// get-event-information choice 29 but bit 39, …) — always map through
187    /// here, never by reusing the choice number as a bit. `None` for choices
188    /// with no defined bit (reserved/unassigned values).
189    pub fn from_confirmed_choice(choice: ConfirmedServiceChoice) -> Option<Self> {
190        Some(match choice.to_raw() {
191            0 => Self::ACKNOWLEDGE_ALARM,
192            1 => Self::CONFIRMED_COV_NOTIFICATION,
193            2 => Self::CONFIRMED_EVENT_NOTIFICATION,
194            3 => Self::GET_ALARM_SUMMARY,
195            4 => Self::GET_ENROLLMENT_SUMMARY,
196            5 => Self::SUBSCRIBE_COV,
197            6 => Self::ATOMIC_READ_FILE,
198            7 => Self::ATOMIC_WRITE_FILE,
199            8 => Self::ADD_LIST_ELEMENT,
200            9 => Self::REMOVE_LIST_ELEMENT,
201            10 => Self::CREATE_OBJECT,
202            11 => Self::DELETE_OBJECT,
203            12 => Self::READ_PROPERTY,
204            14 => Self::READ_PROPERTY_MULTIPLE,
205            15 => Self::WRITE_PROPERTY,
206            16 => Self::WRITE_PROPERTY_MULTIPLE,
207            17 => Self::DEVICE_COMMUNICATION_CONTROL,
208            18 => Self::CONFIRMED_PRIVATE_TRANSFER,
209            19 => Self::CONFIRMED_TEXT_MESSAGE,
210            20 => Self::REINITIALIZE_DEVICE,
211            21 => Self::VT_OPEN,
212            22 => Self::VT_CLOSE,
213            23 => Self::VT_DATA,
214            26 => Self::READ_RANGE,
215            27 => Self::LIFE_SAFETY_OPERATION,
216            28 => Self::SUBSCRIBE_COV_PROPERTY,
217            29 => Self::GET_EVENT_INFORMATION,
218            30 => Self::SUBSCRIBE_COV_PROPERTY_MULTIPLE,
219            31 => Self::CONFIRMED_COV_NOTIFICATION_MULTIPLE,
220            32 => Self::CONFIRMED_AUDIT_NOTIFICATION,
221            33 => Self::AUDIT_LOG_QUERY,
222            _ => return None,
223        })
224    }
225
226    /// The `BACnetServicesSupported` bit for an unconfirmed service choice.
227    ///
228    /// Every unconfirmed choice diverges from its bit (who-is is choice 8 but
229    /// bit 34). `None` for choices with no defined bit.
230    pub fn from_unconfirmed_choice(choice: UnconfirmedServiceChoice) -> Option<Self> {
231        Some(match choice.to_raw() {
232            0 => Self::I_AM,
233            1 => Self::I_HAVE,
234            2 => Self::UNCONFIRMED_COV_NOTIFICATION,
235            3 => Self::UNCONFIRMED_EVENT_NOTIFICATION,
236            4 => Self::UNCONFIRMED_PRIVATE_TRANSFER,
237            5 => Self::UNCONFIRMED_TEXT_MESSAGE,
238            6 => Self::TIME_SYNCHRONIZATION,
239            7 => Self::WHO_HAS,
240            8 => Self::WHO_IS,
241            9 => Self::UTC_TIME_SYNCHRONIZATION,
242            10 => Self::WRITE_GROUP,
243            11 => Self::UNCONFIRMED_COV_NOTIFICATION_MULTIPLE,
244            12 => Self::UNCONFIRMED_AUDIT_NOTIFICATION,
245            13 => Self::WHO_AM_I,
246            14 => Self::YOU_ARE,
247            _ => return None,
248        })
249    }
250}
251
252bacnet_enum! {
253    /// BACnet message priority for TextMessage services (Clause 16.5).
254    pub struct MessagePriority(u32);
255
256    const NORMAL = 0;
257    const URGENT = 1;
258}
259
260bacnet_enum! {
261    /// BACnet virtual terminal class (Clause 17.1).
262    pub struct VTClass(u32);
263
264    const DEFAULT_TERMINAL = 0;
265    const ANSI_X3_64 = 1;
266    const DEC_VT52 = 2;
267    const DEC_VT100 = 3;
268    const DEC_VT220 = 4;
269    const HP_700_94 = 5;
270    const IBM_3130 = 6;
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn service_choice_to_bit_divergent_pairs() {
279        // The pairs where choice != bit — the exact confusions that produced
280        // the old hardcoded Protocol_Services_Supported constant (#192).
281        let confirmed = [
282            (26, ServiceSupported::READ_RANGE, 35),
283            (27, ServiceSupported::LIFE_SAFETY_OPERATION, 37),
284            (28, ServiceSupported::SUBSCRIBE_COV_PROPERTY, 38),
285            (29, ServiceSupported::GET_EVENT_INFORMATION, 39),
286            (30, ServiceSupported::SUBSCRIBE_COV_PROPERTY_MULTIPLE, 41),
287            (
288                31,
289                ServiceSupported::CONFIRMED_COV_NOTIFICATION_MULTIPLE,
290                42,
291            ),
292            (32, ServiceSupported::CONFIRMED_AUDIT_NOTIFICATION, 44),
293            (33, ServiceSupported::AUDIT_LOG_QUERY, 45),
294        ];
295        for (choice, expected, bit) in confirmed {
296            let got =
297                ServiceSupported::from_confirmed_choice(ConfirmedServiceChoice::from_raw(choice))
298                    .unwrap();
299            assert_eq!(got, expected);
300            assert_eq!(got.to_raw(), bit);
301        }
302
303        let unconfirmed = [
304            (0, ServiceSupported::I_AM, 26),
305            (5, ServiceSupported::UNCONFIRMED_TEXT_MESSAGE, 31),
306            (6, ServiceSupported::TIME_SYNCHRONIZATION, 32),
307            (7, ServiceSupported::WHO_HAS, 33),
308            (8, ServiceSupported::WHO_IS, 34),
309            (9, ServiceSupported::UTC_TIME_SYNCHRONIZATION, 36),
310            (10, ServiceSupported::WRITE_GROUP, 40),
311            (14, ServiceSupported::YOU_ARE, 48),
312        ];
313        for (choice, expected, bit) in unconfirmed {
314            let got = ServiceSupported::from_unconfirmed_choice(
315                UnconfirmedServiceChoice::from_raw(choice),
316            )
317            .unwrap();
318            assert_eq!(got, expected);
319            assert_eq!(got.to_raw(), bit);
320        }
321    }
322
323    #[test]
324    fn service_choice_identity_range_and_reserved() {
325        // Choices 0..=23 (minus reserved 13) map bit == choice.
326        for c in (0..=23u8).filter(|c| *c != 13) {
327            let bit = ServiceSupported::from_confirmed_choice(ConfirmedServiceChoice::from_raw(c))
328                .unwrap()
329                .to_raw();
330            assert_eq!(bit, c, "choice {c} should be the identity mapping");
331        }
332        // Reserved/unassigned choices have no bit.
333        assert!(
334            ServiceSupported::from_confirmed_choice(ConfirmedServiceChoice::from_raw(13)).is_none()
335        );
336        assert!(
337            ServiceSupported::from_confirmed_choice(ConfirmedServiceChoice::from_raw(34)).is_none()
338        );
339        assert!(
340            ServiceSupported::from_unconfirmed_choice(UnconfirmedServiceChoice::from_raw(15))
341                .is_none()
342        );
343    }
344}