Skip to main content

bacnet_rs/object/
binary.rs

1//! Binary Object Types Implementation
2//!
3//! This module implements the Binary Input, Binary Output, and Binary Value object types
4//! as defined in ASHRAE 135. These objects represent binary (two-state) values in BACnet.
5
6use crate::object::{
7    event_state::EventState, reliability::Reliability, BacnetObject, ObjectError, ObjectIdentifier,
8    ObjectType, PropertyIdentifier, PropertyValue, Result,
9};
10
11#[cfg(not(feature = "std"))]
12use alloc::{string::String, vec::Vec};
13
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17/// Binary values enumeration
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(u32)]
20pub enum BinaryPV {
21    Inactive = 0,
22    Active = 1,
23}
24
25impl From<bool> for BinaryPV {
26    fn from(value: bool) -> Self {
27        if value {
28            BinaryPV::Active
29        } else {
30            BinaryPV::Inactive
31        }
32    }
33}
34
35impl From<BinaryPV> for bool {
36    fn from(value: BinaryPV) -> Self {
37        value == BinaryPV::Active
38    }
39}
40
41/// Polarity enumeration
42#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44#[repr(u32)]
45pub enum Polarity {
46    Normal = 0,
47    Reverse = 1,
48}
49
50impl TryFrom<u32> for Polarity {
51    type Error = ObjectError;
52
53    fn try_from(value: u32) -> Result<Self> {
54        match value {
55            0 => Ok(Polarity::Normal),
56            1 => Ok(Polarity::Reverse),
57            _ => Err(ObjectError::InvalidValue(
58                "Polarity must be 0 or 1".to_string(),
59            )),
60        }
61    }
62}
63
64/// Binary Input object
65#[derive(Debug, Clone)]
66pub struct BinaryInput {
67    /// Object identifier
68    pub identifier: ObjectIdentifier,
69    /// Object name
70    pub object_name: String,
71    /// Present value
72    pub present_value: BinaryPV,
73    /// Description
74    pub description: String,
75    /// Device type
76    pub device_type: String,
77    /// Status flags (4 bits: in_alarm, fault, overridden, out_of_service)
78    pub status_flags: u8,
79    /// Event state
80    pub event_state: EventState,
81    /// Reliability
82    pub reliability: Reliability,
83    /// Out of service
84    pub out_of_service: bool,
85    /// Polarity
86    pub polarity: Polarity,
87    /// Inactive text
88    pub inactive_text: String,
89    /// Active text
90    pub active_text: String,
91    /// Change of value time
92    pub change_of_state_time: Option<crate::object::Time>,
93    /// Change of state count
94    pub change_of_state_count: u32,
95    /// Time of state count reset
96    pub time_of_state_count_reset: Option<crate::object::Time>,
97}
98
99/// Binary Output object
100#[derive(Debug, Clone)]
101pub struct BinaryOutput {
102    /// Object identifier
103    pub identifier: ObjectIdentifier,
104    /// Object name
105    pub object_name: String,
106    /// Present value
107    pub present_value: BinaryPV,
108    /// Description
109    pub description: String,
110    /// Device type
111    pub device_type: String,
112    /// Status flags
113    pub status_flags: u8,
114    /// Event state
115    pub event_state: EventState,
116    /// Reliability
117    pub reliability: Reliability,
118    /// Out of service
119    pub out_of_service: bool,
120    /// Polarity
121    pub polarity: Polarity,
122    /// Inactive text
123    pub inactive_text: String,
124    /// Active text
125    pub active_text: String,
126    /// Priority array (16 levels)
127    pub priority_array: [Option<BinaryPV>; 16],
128    /// Relinquish default
129    pub relinquish_default: BinaryPV,
130    /// Minimum off time
131    pub minimum_off_time: u32,
132    /// Minimum on time
133    pub minimum_on_time: u32,
134}
135
136/// Binary Value object
137#[derive(Debug, Clone)]
138pub struct BinaryValue {
139    /// Object identifier
140    pub identifier: ObjectIdentifier,
141    /// Object name
142    pub object_name: String,
143    /// Present value
144    pub present_value: BinaryPV,
145    /// Description
146    pub description: String,
147    /// Status flags
148    pub status_flags: u8,
149    /// Event state
150    pub event_state: EventState,
151    /// Reliability
152    pub reliability: Reliability,
153    /// Out of service
154    pub out_of_service: bool,
155    /// Inactive text
156    pub inactive_text: String,
157    /// Active text
158    pub active_text: String,
159    /// Priority array (16 levels)
160    pub priority_array: [Option<BinaryPV>; 16],
161    /// Relinquish default
162    pub relinquish_default: BinaryPV,
163}
164
165impl BinaryInput {
166    /// Create a new Binary Input object
167    pub fn new(instance: u32, object_name: String) -> Self {
168        Self {
169            identifier: ObjectIdentifier::new(ObjectType::BinaryInput, instance),
170            object_name,
171            present_value: BinaryPV::Inactive,
172            description: String::new(),
173            device_type: String::new(),
174            status_flags: 0,
175            event_state: EventState::Normal,
176            reliability: Reliability::NoFaultDetected,
177            out_of_service: false,
178            polarity: Polarity::Normal,
179            inactive_text: "INACTIVE".to_string(),
180            active_text: "ACTIVE".to_string(),
181            change_of_state_time: None,
182            change_of_state_count: 0,
183            time_of_state_count_reset: None,
184        }
185    }
186
187    /// Set the present value and update change of state
188    pub fn set_present_value(&mut self, value: BinaryPV) {
189        if value != self.present_value {
190            self.present_value = value;
191            self.change_of_state_count += 1;
192            // In a real implementation, would set change_of_state_time to current time
193        }
194    }
195
196    /// Get status flags as individual booleans
197    pub fn get_status_flags(&self) -> (bool, bool, bool, bool) {
198        (
199            (self.status_flags & 0x08) != 0, // in_alarm
200            (self.status_flags & 0x04) != 0, // fault
201            (self.status_flags & 0x02) != 0, // overridden
202            (self.status_flags & 0x01) != 0, // out_of_service
203        )
204    }
205
206    /// Set status flags from individual booleans
207    pub fn set_status_flags(
208        &mut self,
209        in_alarm: bool,
210        fault: bool,
211        overridden: bool,
212        out_of_service: bool,
213    ) {
214        self.status_flags = 0;
215        if in_alarm {
216            self.status_flags |= 0x08;
217        }
218        if fault {
219            self.status_flags |= 0x04;
220        }
221        if overridden {
222            self.status_flags |= 0x02;
223        }
224        if out_of_service {
225            self.status_flags |= 0x01;
226        }
227    }
228}
229
230impl BinaryOutput {
231    /// Create a new Binary Output object
232    pub fn new(instance: u32, object_name: String) -> Self {
233        Self {
234            identifier: ObjectIdentifier::new(ObjectType::BinaryOutput, instance),
235            object_name,
236            present_value: BinaryPV::Inactive,
237            description: String::new(),
238            device_type: String::new(),
239            status_flags: 0,
240            event_state: EventState::Normal,
241            reliability: Reliability::NoFaultDetected,
242            out_of_service: false,
243            polarity: Polarity::Normal,
244            inactive_text: "INACTIVE".to_string(),
245            active_text: "ACTIVE".to_string(),
246            priority_array: [None; 16],
247            relinquish_default: BinaryPV::Inactive,
248            minimum_off_time: 0,
249            minimum_on_time: 0,
250        }
251    }
252
253    /// Write to priority array at specified priority level (1-16)
254    pub fn write_priority(&mut self, priority: u8, value: Option<BinaryPV>) -> Result<()> {
255        if !(1..=16).contains(&priority) {
256            return Err(ObjectError::InvalidValue(
257                "Priority must be 1-16".to_string(),
258            ));
259        }
260        self.priority_array[(priority - 1) as usize] = value;
261        self.update_present_value();
262        Ok(())
263    }
264
265    /// Update present value based on priority array
266    fn update_present_value(&mut self) {
267        // Find highest priority non-null value
268        if let Some(value) = self.priority_array.iter().flatten().next() {
269            self.present_value = *value;
270            return;
271        }
272        // If all priorities are null, use relinquish default
273        self.present_value = self.relinquish_default;
274    }
275
276    /// Get the effective priority level for current present value
277    pub fn get_effective_priority(&self) -> Option<u8> {
278        for (i, priority_value) in self.priority_array.iter().enumerate() {
279            if priority_value.is_some() {
280                return Some((i + 1) as u8);
281            }
282        }
283        None
284    }
285}
286
287impl BinaryValue {
288    /// Create a new Binary Value object
289    pub fn new(instance: u32, object_name: String) -> Self {
290        Self {
291            identifier: ObjectIdentifier::new(ObjectType::BinaryValue, instance),
292            object_name,
293            present_value: BinaryPV::Inactive,
294            description: String::new(),
295            status_flags: 0,
296            event_state: EventState::Normal,
297            reliability: Reliability::NoFaultDetected,
298            out_of_service: false,
299            inactive_text: "INACTIVE".to_string(),
300            active_text: "ACTIVE".to_string(),
301            priority_array: [None; 16],
302            relinquish_default: BinaryPV::Inactive,
303        }
304    }
305
306    /// Write to priority array at specified priority level (1-16)
307    pub fn write_priority(&mut self, priority: u8, value: Option<BinaryPV>) -> Result<()> {
308        if !(1..=16).contains(&priority) {
309            return Err(ObjectError::InvalidValue(
310                "Priority must be 1-16".to_string(),
311            ));
312        }
313        self.priority_array[(priority - 1) as usize] = value;
314        self.update_present_value();
315        Ok(())
316    }
317
318    /// Update present value based on priority array
319    fn update_present_value(&mut self) {
320        // Find highest priority non-null value
321        if let Some(value) = self.priority_array.iter().flatten().next() {
322            self.present_value = *value;
323            return;
324        }
325        // If all priorities are null, use relinquish default
326        self.present_value = self.relinquish_default;
327    }
328}
329
330impl BacnetObject for BinaryInput {
331    fn identifier(&self) -> ObjectIdentifier {
332        self.identifier
333    }
334
335    fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
336        match property {
337            PropertyIdentifier::ObjectIdentifier => {
338                Ok(PropertyValue::ObjectIdentifier(self.identifier))
339            }
340            PropertyIdentifier::ObjectName => {
341                Ok(PropertyValue::CharacterString(self.object_name.clone()))
342            }
343            PropertyIdentifier::ObjectType => Ok(PropertyValue::Enumerated(u32::from(
344                ObjectType::BinaryInput,
345            ))),
346            PropertyIdentifier::PresentValue => {
347                Ok(PropertyValue::Enumerated(self.present_value as u32))
348            }
349            PropertyIdentifier::OutOfService => Ok(PropertyValue::Boolean(self.out_of_service)),
350            _ => Err(ObjectError::UnknownProperty),
351        }
352    }
353
354    fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
355        match property {
356            PropertyIdentifier::ObjectName => {
357                if let PropertyValue::CharacterString(name) = value {
358                    self.object_name = name;
359                    Ok(())
360                } else {
361                    Err(ObjectError::InvalidPropertyType)
362                }
363            }
364            PropertyIdentifier::OutOfService => {
365                if let PropertyValue::Boolean(oos) = value {
366                    self.out_of_service = oos;
367                    Ok(())
368                } else {
369                    Err(ObjectError::InvalidPropertyType)
370                }
371            }
372            _ => Err(ObjectError::PropertyNotWritable),
373        }
374    }
375
376    fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
377        matches!(
378            property,
379            PropertyIdentifier::ObjectName | PropertyIdentifier::OutOfService
380        )
381    }
382
383    fn property_list(&self) -> Vec<PropertyIdentifier> {
384        vec![
385            PropertyIdentifier::ObjectIdentifier,
386            PropertyIdentifier::ObjectName,
387            PropertyIdentifier::ObjectType,
388            PropertyIdentifier::PresentValue,
389            PropertyIdentifier::OutOfService,
390        ]
391    }
392}
393
394impl BacnetObject for BinaryOutput {
395    fn identifier(&self) -> ObjectIdentifier {
396        self.identifier
397    }
398
399    fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
400        match property {
401            PropertyIdentifier::ObjectIdentifier => {
402                Ok(PropertyValue::ObjectIdentifier(self.identifier))
403            }
404            PropertyIdentifier::ObjectName => {
405                Ok(PropertyValue::CharacterString(self.object_name.clone()))
406            }
407            PropertyIdentifier::ObjectType => Ok(PropertyValue::Enumerated(u32::from(
408                ObjectType::BinaryOutput,
409            ))),
410            PropertyIdentifier::PresentValue => {
411                Ok(PropertyValue::Enumerated(self.present_value as u32))
412            }
413            PropertyIdentifier::OutOfService => Ok(PropertyValue::Boolean(self.out_of_service)),
414            PropertyIdentifier::PriorityArray => {
415                let array: Vec<PropertyValue> = self
416                    .priority_array
417                    .iter()
418                    .map(|&v| match v {
419                        Some(val) => PropertyValue::Enumerated(val as u32),
420                        None => PropertyValue::Null,
421                    })
422                    .collect();
423                Ok(PropertyValue::Array(array))
424            }
425            _ => Err(ObjectError::UnknownProperty),
426        }
427    }
428
429    fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
430        match property {
431            PropertyIdentifier::ObjectName => {
432                if let PropertyValue::CharacterString(name) = value {
433                    self.object_name = name;
434                    Ok(())
435                } else {
436                    Err(ObjectError::InvalidPropertyType)
437                }
438            }
439            PropertyIdentifier::PresentValue => {
440                if let PropertyValue::Enumerated(val) = value {
441                    let binary_val = match val {
442                        0 => BinaryPV::Inactive,
443                        1 => BinaryPV::Active,
444                        _ => {
445                            return Err(ObjectError::InvalidValue(
446                                "Binary value must be 0 or 1".to_string(),
447                            ))
448                        }
449                    };
450                    // Write to priority 8 (manual operator) by default
451                    self.write_priority(8, Some(binary_val))
452                } else {
453                    Err(ObjectError::InvalidPropertyType)
454                }
455            }
456            PropertyIdentifier::OutOfService => {
457                if let PropertyValue::Boolean(oos) = value {
458                    self.out_of_service = oos;
459                    Ok(())
460                } else {
461                    Err(ObjectError::InvalidPropertyType)
462                }
463            }
464            _ => Err(ObjectError::PropertyNotWritable),
465        }
466    }
467
468    fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
469        matches!(
470            property,
471            PropertyIdentifier::ObjectName
472                | PropertyIdentifier::PresentValue
473                | PropertyIdentifier::OutOfService
474        )
475    }
476
477    fn property_list(&self) -> Vec<PropertyIdentifier> {
478        vec![
479            PropertyIdentifier::ObjectIdentifier,
480            PropertyIdentifier::ObjectName,
481            PropertyIdentifier::ObjectType,
482            PropertyIdentifier::PresentValue,
483            PropertyIdentifier::OutOfService,
484            PropertyIdentifier::PriorityArray,
485        ]
486    }
487}
488
489impl BacnetObject for BinaryValue {
490    fn identifier(&self) -> ObjectIdentifier {
491        self.identifier
492    }
493
494    fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
495        match property {
496            PropertyIdentifier::ObjectIdentifier => {
497                Ok(PropertyValue::ObjectIdentifier(self.identifier))
498            }
499            PropertyIdentifier::ObjectName => {
500                Ok(PropertyValue::CharacterString(self.object_name.clone()))
501            }
502            PropertyIdentifier::ObjectType => Ok(PropertyValue::Enumerated(u32::from(
503                ObjectType::BinaryValue,
504            ))),
505            PropertyIdentifier::PresentValue => {
506                Ok(PropertyValue::Enumerated(self.present_value as u32))
507            }
508            PropertyIdentifier::OutOfService => Ok(PropertyValue::Boolean(self.out_of_service)),
509            PropertyIdentifier::PriorityArray => {
510                let array: Vec<PropertyValue> = self
511                    .priority_array
512                    .iter()
513                    .map(|&v| match v {
514                        Some(val) => PropertyValue::Enumerated(val as u32),
515                        None => PropertyValue::Null,
516                    })
517                    .collect();
518                Ok(PropertyValue::Array(array))
519            }
520            _ => Err(ObjectError::UnknownProperty),
521        }
522    }
523
524    fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
525        match property {
526            PropertyIdentifier::ObjectName => {
527                if let PropertyValue::CharacterString(name) = value {
528                    self.object_name = name;
529                    Ok(())
530                } else {
531                    Err(ObjectError::InvalidPropertyType)
532                }
533            }
534            PropertyIdentifier::PresentValue => {
535                if let PropertyValue::Enumerated(val) = value {
536                    let binary_val = match val {
537                        0 => BinaryPV::Inactive,
538                        1 => BinaryPV::Active,
539                        _ => {
540                            return Err(ObjectError::InvalidValue(
541                                "Binary value must be 0 or 1".to_string(),
542                            ))
543                        }
544                    };
545                    // Write to priority 8 (manual operator) by default
546                    self.write_priority(8, Some(binary_val))
547                } else {
548                    Err(ObjectError::InvalidPropertyType)
549                }
550            }
551            PropertyIdentifier::OutOfService => {
552                if let PropertyValue::Boolean(oos) = value {
553                    self.out_of_service = oos;
554                    Ok(())
555                } else {
556                    Err(ObjectError::InvalidPropertyType)
557                }
558            }
559            _ => Err(ObjectError::PropertyNotWritable),
560        }
561    }
562
563    fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
564        matches!(
565            property,
566            PropertyIdentifier::ObjectName
567                | PropertyIdentifier::PresentValue
568                | PropertyIdentifier::OutOfService
569        )
570    }
571
572    fn property_list(&self) -> Vec<PropertyIdentifier> {
573        vec![
574            PropertyIdentifier::ObjectIdentifier,
575            PropertyIdentifier::ObjectName,
576            PropertyIdentifier::ObjectType,
577            PropertyIdentifier::PresentValue,
578            PropertyIdentifier::OutOfService,
579            PropertyIdentifier::PriorityArray,
580        ]
581    }
582}
583
584#[cfg(test)]
585mod tests {
586    use super::*;
587
588    #[test]
589    fn test_binary_pv_conversions() {
590        assert_eq!(BinaryPV::from(true), BinaryPV::Active);
591        assert_eq!(BinaryPV::from(false), BinaryPV::Inactive);
592        assert!(bool::from(BinaryPV::Active));
593        assert!(!bool::from(BinaryPV::Inactive));
594    }
595
596    #[test]
597    fn test_binary_input_creation() {
598        let bi = BinaryInput::new(1, "Door Switch".to_string());
599        assert_eq!(bi.identifier.instance, 1);
600        assert_eq!(bi.object_name, "Door Switch");
601        assert_eq!(bi.present_value, BinaryPV::Inactive);
602        assert_eq!(bi.change_of_state_count, 0);
603    }
604
605    #[test]
606    fn test_binary_input_change_of_state() {
607        let mut bi = BinaryInput::new(1, "Test".to_string());
608
609        bi.set_present_value(BinaryPV::Active);
610        assert_eq!(bi.present_value, BinaryPV::Active);
611        assert_eq!(bi.change_of_state_count, 1);
612
613        bi.set_present_value(BinaryPV::Active); // Same value, no change
614        assert_eq!(bi.change_of_state_count, 1);
615
616        bi.set_present_value(BinaryPV::Inactive);
617        assert_eq!(bi.change_of_state_count, 2);
618    }
619
620    #[test]
621    fn test_binary_output_priority() {
622        let mut bo = BinaryOutput::new(1, "Fan Control".to_string());
623
624        // Write to priority 8
625        bo.write_priority(8, Some(BinaryPV::Active)).unwrap();
626        assert_eq!(bo.present_value, BinaryPV::Active);
627        assert_eq!(bo.get_effective_priority(), Some(8));
628
629        // Write to higher priority 3
630        bo.write_priority(3, Some(BinaryPV::Inactive)).unwrap();
631        assert_eq!(bo.present_value, BinaryPV::Inactive);
632        assert_eq!(bo.get_effective_priority(), Some(3));
633
634        // Release priority 3
635        bo.write_priority(3, None).unwrap();
636        assert_eq!(bo.present_value, BinaryPV::Active);
637        assert_eq!(bo.get_effective_priority(), Some(8));
638    }
639
640    #[test]
641    fn test_binary_object_properties() {
642        let mut bv = BinaryValue::new(1, "Test Value".to_string());
643
644        // Test property access
645        let name = bv.get_property(PropertyIdentifier::ObjectName).unwrap();
646        if let PropertyValue::CharacterString(n) = name {
647            assert_eq!(n, "Test Value");
648        } else {
649            panic!("Expected CharacterString");
650        }
651
652        // Test property modification
653        bv.set_property(
654            PropertyIdentifier::PresentValue,
655            PropertyValue::Enumerated(1),
656        )
657        .unwrap();
658        assert_eq!(bv.present_value, BinaryPV::Active);
659
660        // Test invalid binary value
661        let result = bv.set_property(
662            PropertyIdentifier::PresentValue,
663            PropertyValue::Enumerated(2),
664        );
665        assert!(result.is_err());
666    }
667}