use crate::object::{
BacnetObject, ObjectIdentifier, ObjectType, PropertyIdentifier, PropertyValue,
ObjectError, Result, EventState, Reliability,
};
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum BinaryPV {
Inactive = 0,
Active = 1,
}
impl From<bool> for BinaryPV {
fn from(value: bool) -> Self {
if value { BinaryPV::Active } else { BinaryPV::Inactive }
}
}
impl From<BinaryPV> for bool {
fn from(value: BinaryPV) -> Self {
value == BinaryPV::Active
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Polarity {
Normal = 0,
Reverse = 1,
}
#[derive(Debug, Clone)]
pub struct BinaryInput {
pub identifier: ObjectIdentifier,
pub object_name: String,
pub present_value: BinaryPV,
pub description: String,
pub device_type: String,
pub status_flags: u8,
pub event_state: EventState,
pub reliability: Reliability,
pub out_of_service: bool,
pub polarity: Polarity,
pub inactive_text: String,
pub active_text: String,
pub change_of_state_time: Option<crate::object::Time>,
pub change_of_state_count: u32,
pub time_of_state_count_reset: Option<crate::object::Time>,
}
#[derive(Debug, Clone)]
pub struct BinaryOutput {
pub identifier: ObjectIdentifier,
pub object_name: String,
pub present_value: BinaryPV,
pub description: String,
pub device_type: String,
pub status_flags: u8,
pub event_state: EventState,
pub reliability: Reliability,
pub out_of_service: bool,
pub polarity: Polarity,
pub inactive_text: String,
pub active_text: String,
pub priority_array: [Option<BinaryPV>; 16],
pub relinquish_default: BinaryPV,
pub minimum_off_time: u32,
pub minimum_on_time: u32,
}
#[derive(Debug, Clone)]
pub struct BinaryValue {
pub identifier: ObjectIdentifier,
pub object_name: String,
pub present_value: BinaryPV,
pub description: String,
pub status_flags: u8,
pub event_state: EventState,
pub reliability: Reliability,
pub out_of_service: bool,
pub inactive_text: String,
pub active_text: String,
pub priority_array: [Option<BinaryPV>; 16],
pub relinquish_default: BinaryPV,
}
impl BinaryInput {
pub fn new(instance: u32, object_name: String) -> Self {
Self {
identifier: ObjectIdentifier::new(ObjectType::BinaryInput, instance),
object_name,
present_value: BinaryPV::Inactive,
description: String::new(),
device_type: String::new(),
status_flags: 0,
event_state: EventState::Normal,
reliability: Reliability::NoFaultDetected,
out_of_service: false,
polarity: Polarity::Normal,
inactive_text: "INACTIVE".to_string(),
active_text: "ACTIVE".to_string(),
change_of_state_time: None,
change_of_state_count: 0,
time_of_state_count_reset: None,
}
}
pub fn set_present_value(&mut self, value: BinaryPV) {
if value != self.present_value {
self.present_value = value;
self.change_of_state_count += 1;
}
}
pub fn get_status_flags(&self) -> (bool, bool, bool, bool) {
(
(self.status_flags & 0x08) != 0, (self.status_flags & 0x04) != 0, (self.status_flags & 0x02) != 0, (self.status_flags & 0x01) != 0, )
}
pub fn set_status_flags(&mut self, in_alarm: bool, fault: bool, overridden: bool, out_of_service: bool) {
self.status_flags = 0;
if in_alarm { self.status_flags |= 0x08; }
if fault { self.status_flags |= 0x04; }
if overridden { self.status_flags |= 0x02; }
if out_of_service { self.status_flags |= 0x01; }
}
}
impl BinaryOutput {
pub fn new(instance: u32, object_name: String) -> Self {
Self {
identifier: ObjectIdentifier::new(ObjectType::BinaryOutput, instance),
object_name,
present_value: BinaryPV::Inactive,
description: String::new(),
device_type: String::new(),
status_flags: 0,
event_state: EventState::Normal,
reliability: Reliability::NoFaultDetected,
out_of_service: false,
polarity: Polarity::Normal,
inactive_text: "INACTIVE".to_string(),
active_text: "ACTIVE".to_string(),
priority_array: [None; 16],
relinquish_default: BinaryPV::Inactive,
minimum_off_time: 0,
minimum_on_time: 0,
}
}
pub fn write_priority(&mut self, priority: u8, value: Option<BinaryPV>) -> Result<()> {
if priority < 1 || priority > 16 {
return Err(ObjectError::InvalidValue("Priority must be 1-16".to_string()));
}
self.priority_array[(priority - 1) as usize] = value;
self.update_present_value();
Ok(())
}
fn update_present_value(&mut self) {
for priority_value in &self.priority_array {
if let Some(value) = priority_value {
self.present_value = *value;
return;
}
}
self.present_value = self.relinquish_default;
}
pub fn get_effective_priority(&self) -> Option<u8> {
for (i, priority_value) in self.priority_array.iter().enumerate() {
if priority_value.is_some() {
return Some((i + 1) as u8);
}
}
None
}
}
impl BinaryValue {
pub fn new(instance: u32, object_name: String) -> Self {
Self {
identifier: ObjectIdentifier::new(ObjectType::BinaryValue, instance),
object_name,
present_value: BinaryPV::Inactive,
description: String::new(),
status_flags: 0,
event_state: EventState::Normal,
reliability: Reliability::NoFaultDetected,
out_of_service: false,
inactive_text: "INACTIVE".to_string(),
active_text: "ACTIVE".to_string(),
priority_array: [None; 16],
relinquish_default: BinaryPV::Inactive,
}
}
pub fn write_priority(&mut self, priority: u8, value: Option<BinaryPV>) -> Result<()> {
if priority < 1 || priority > 16 {
return Err(ObjectError::InvalidValue("Priority must be 1-16".to_string()));
}
self.priority_array[(priority - 1) as usize] = value;
self.update_present_value();
Ok(())
}
fn update_present_value(&mut self) {
for priority_value in &self.priority_array {
if let Some(value) = priority_value {
self.present_value = *value;
return;
}
}
self.present_value = self.relinquish_default;
}
}
impl BacnetObject for BinaryInput {
fn identifier(&self) -> ObjectIdentifier {
self.identifier
}
fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
match property {
PropertyIdentifier::ObjectIdentifier => {
Ok(PropertyValue::ObjectIdentifier(self.identifier))
}
PropertyIdentifier::ObjectName => {
Ok(PropertyValue::CharacterString(self.object_name.clone()))
}
PropertyIdentifier::ObjectType => {
Ok(PropertyValue::Enumerated(ObjectType::BinaryInput as u32))
}
PropertyIdentifier::PresentValue => {
Ok(PropertyValue::Enumerated(self.present_value as u32))
}
PropertyIdentifier::OutOfService => {
Ok(PropertyValue::Boolean(self.out_of_service))
}
_ => Err(ObjectError::UnknownProperty),
}
}
fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
match property {
PropertyIdentifier::ObjectName => {
if let PropertyValue::CharacterString(name) = value {
self.object_name = name;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
PropertyIdentifier::OutOfService => {
if let PropertyValue::Boolean(oos) = value {
self.out_of_service = oos;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
_ => Err(ObjectError::PropertyNotWritable),
}
}
fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
matches!(
property,
PropertyIdentifier::ObjectName | PropertyIdentifier::OutOfService
)
}
fn property_list(&self) -> Vec<PropertyIdentifier> {
vec![
PropertyIdentifier::ObjectIdentifier,
PropertyIdentifier::ObjectName,
PropertyIdentifier::ObjectType,
PropertyIdentifier::PresentValue,
PropertyIdentifier::OutOfService,
]
}
}
impl BacnetObject for BinaryOutput {
fn identifier(&self) -> ObjectIdentifier {
self.identifier
}
fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
match property {
PropertyIdentifier::ObjectIdentifier => {
Ok(PropertyValue::ObjectIdentifier(self.identifier))
}
PropertyIdentifier::ObjectName => {
Ok(PropertyValue::CharacterString(self.object_name.clone()))
}
PropertyIdentifier::ObjectType => {
Ok(PropertyValue::Enumerated(ObjectType::BinaryOutput as u32))
}
PropertyIdentifier::PresentValue => {
Ok(PropertyValue::Enumerated(self.present_value as u32))
}
PropertyIdentifier::OutOfService => {
Ok(PropertyValue::Boolean(self.out_of_service))
}
PropertyIdentifier::PriorityArray => {
let array: Vec<PropertyValue> = self.priority_array
.iter()
.map(|&v| match v {
Some(val) => PropertyValue::Enumerated(val as u32),
None => PropertyValue::Null,
})
.collect();
Ok(PropertyValue::Array(array))
}
_ => Err(ObjectError::UnknownProperty),
}
}
fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
match property {
PropertyIdentifier::ObjectName => {
if let PropertyValue::CharacterString(name) = value {
self.object_name = name;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
PropertyIdentifier::PresentValue => {
if let PropertyValue::Enumerated(val) = value {
let binary_val = match val {
0 => BinaryPV::Inactive,
1 => BinaryPV::Active,
_ => return Err(ObjectError::InvalidValue("Binary value must be 0 or 1".to_string())),
};
self.write_priority(8, Some(binary_val))
} else {
Err(ObjectError::InvalidPropertyType)
}
}
PropertyIdentifier::OutOfService => {
if let PropertyValue::Boolean(oos) = value {
self.out_of_service = oos;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
_ => Err(ObjectError::PropertyNotWritable),
}
}
fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
matches!(
property,
PropertyIdentifier::ObjectName
| PropertyIdentifier::PresentValue
| PropertyIdentifier::OutOfService
)
}
fn property_list(&self) -> Vec<PropertyIdentifier> {
vec![
PropertyIdentifier::ObjectIdentifier,
PropertyIdentifier::ObjectName,
PropertyIdentifier::ObjectType,
PropertyIdentifier::PresentValue,
PropertyIdentifier::OutOfService,
PropertyIdentifier::PriorityArray,
]
}
}
impl BacnetObject for BinaryValue {
fn identifier(&self) -> ObjectIdentifier {
self.identifier
}
fn get_property(&self, property: PropertyIdentifier) -> Result<PropertyValue> {
match property {
PropertyIdentifier::ObjectIdentifier => {
Ok(PropertyValue::ObjectIdentifier(self.identifier))
}
PropertyIdentifier::ObjectName => {
Ok(PropertyValue::CharacterString(self.object_name.clone()))
}
PropertyIdentifier::ObjectType => {
Ok(PropertyValue::Enumerated(ObjectType::BinaryValue as u32))
}
PropertyIdentifier::PresentValue => {
Ok(PropertyValue::Enumerated(self.present_value as u32))
}
PropertyIdentifier::OutOfService => {
Ok(PropertyValue::Boolean(self.out_of_service))
}
PropertyIdentifier::PriorityArray => {
let array: Vec<PropertyValue> = self.priority_array
.iter()
.map(|&v| match v {
Some(val) => PropertyValue::Enumerated(val as u32),
None => PropertyValue::Null,
})
.collect();
Ok(PropertyValue::Array(array))
}
_ => Err(ObjectError::UnknownProperty),
}
}
fn set_property(&mut self, property: PropertyIdentifier, value: PropertyValue) -> Result<()> {
match property {
PropertyIdentifier::ObjectName => {
if let PropertyValue::CharacterString(name) = value {
self.object_name = name;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
PropertyIdentifier::PresentValue => {
if let PropertyValue::Enumerated(val) = value {
let binary_val = match val {
0 => BinaryPV::Inactive,
1 => BinaryPV::Active,
_ => return Err(ObjectError::InvalidValue("Binary value must be 0 or 1".to_string())),
};
self.write_priority(8, Some(binary_val))
} else {
Err(ObjectError::InvalidPropertyType)
}
}
PropertyIdentifier::OutOfService => {
if let PropertyValue::Boolean(oos) = value {
self.out_of_service = oos;
Ok(())
} else {
Err(ObjectError::InvalidPropertyType)
}
}
_ => Err(ObjectError::PropertyNotWritable),
}
}
fn is_property_writable(&self, property: PropertyIdentifier) -> bool {
matches!(
property,
PropertyIdentifier::ObjectName
| PropertyIdentifier::PresentValue
| PropertyIdentifier::OutOfService
)
}
fn property_list(&self) -> Vec<PropertyIdentifier> {
vec![
PropertyIdentifier::ObjectIdentifier,
PropertyIdentifier::ObjectName,
PropertyIdentifier::ObjectType,
PropertyIdentifier::PresentValue,
PropertyIdentifier::OutOfService,
PropertyIdentifier::PriorityArray,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_binary_pv_conversions() {
assert_eq!(BinaryPV::from(true), BinaryPV::Active);
assert_eq!(BinaryPV::from(false), BinaryPV::Inactive);
assert_eq!(bool::from(BinaryPV::Active), true);
assert_eq!(bool::from(BinaryPV::Inactive), false);
}
#[test]
fn test_binary_input_creation() {
let bi = BinaryInput::new(1, "Door Switch".to_string());
assert_eq!(bi.identifier.instance, 1);
assert_eq!(bi.object_name, "Door Switch");
assert_eq!(bi.present_value, BinaryPV::Inactive);
assert_eq!(bi.change_of_state_count, 0);
}
#[test]
fn test_binary_input_change_of_state() {
let mut bi = BinaryInput::new(1, "Test".to_string());
bi.set_present_value(BinaryPV::Active);
assert_eq!(bi.present_value, BinaryPV::Active);
assert_eq!(bi.change_of_state_count, 1);
bi.set_present_value(BinaryPV::Active); assert_eq!(bi.change_of_state_count, 1);
bi.set_present_value(BinaryPV::Inactive);
assert_eq!(bi.change_of_state_count, 2);
}
#[test]
fn test_binary_output_priority() {
let mut bo = BinaryOutput::new(1, "Fan Control".to_string());
bo.write_priority(8, Some(BinaryPV::Active)).unwrap();
assert_eq!(bo.present_value, BinaryPV::Active);
assert_eq!(bo.get_effective_priority(), Some(8));
bo.write_priority(3, Some(BinaryPV::Inactive)).unwrap();
assert_eq!(bo.present_value, BinaryPV::Inactive);
assert_eq!(bo.get_effective_priority(), Some(3));
bo.write_priority(3, None).unwrap();
assert_eq!(bo.present_value, BinaryPV::Active);
assert_eq!(bo.get_effective_priority(), Some(8));
}
#[test]
fn test_binary_object_properties() {
let mut bv = BinaryValue::new(1, "Test Value".to_string());
let name = bv.get_property(PropertyIdentifier::ObjectName).unwrap();
if let PropertyValue::CharacterString(n) = name {
assert_eq!(n, "Test Value");
} else {
panic!("Expected CharacterString");
}
bv.set_property(
PropertyIdentifier::PresentValue,
PropertyValue::Enumerated(1),
).unwrap();
assert_eq!(bv.present_value, BinaryPV::Active);
let result = bv.set_property(
PropertyIdentifier::PresentValue,
PropertyValue::Enumerated(2),
);
assert!(result.is_err());
}
}