use crate::object::{
engineering_units::EngineeringUnits, event_state::EventState, reliability::Reliability,
BacnetObject, ObjectError, ObjectIdentifier, ObjectType, PropertyIdentifier, PropertyValue,
Result,
};
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
#[derive(Debug, Clone)]
pub struct AnalogInput {
pub identifier: ObjectIdentifier,
pub object_name: String,
pub present_value: f32,
pub description: String,
pub device_type: String,
pub status_flags: u8,
pub event_state: EventState,
pub reliability: Reliability,
pub out_of_service: bool,
pub units: EngineeringUnits,
pub min_pres_value: Option<f32>,
pub max_pres_value: Option<f32>,
pub resolution: Option<f32>,
pub cov_increment: Option<f32>,
}
#[derive(Debug, Clone)]
pub struct AnalogOutput {
pub identifier: ObjectIdentifier,
pub object_name: String,
pub present_value: f32,
pub description: String,
pub device_type: String,
pub status_flags: u8,
pub event_state: EventState,
pub reliability: Reliability,
pub out_of_service: bool,
pub units: EngineeringUnits,
pub min_pres_value: Option<f32>,
pub max_pres_value: Option<f32>,
pub resolution: Option<f32>,
pub priority_array: [Option<f32>; 16],
pub relinquish_default: f32,
pub cov_increment: Option<f32>,
}
#[derive(Debug, Clone)]
pub struct AnalogValue {
pub identifier: ObjectIdentifier,
pub object_name: String,
pub present_value: f32,
pub description: String,
pub status_flags: u8,
pub event_state: EventState,
pub reliability: Reliability,
pub out_of_service: bool,
pub units: EngineeringUnits,
pub priority_array: [Option<f32>; 16],
pub relinquish_default: f32,
pub cov_increment: Option<f32>,
}
impl AnalogInput {
pub fn new(instance: u32, object_name: String) -> Self {
Self {
identifier: ObjectIdentifier::new(ObjectType::AnalogInput, instance),
object_name,
present_value: 0.0,
description: String::new(),
device_type: String::new(),
status_flags: 0,
event_state: EventState::Normal,
reliability: Reliability::NoFaultDetected,
out_of_service: false,
units: EngineeringUnits::NoUnits,
min_pres_value: None,
max_pres_value: None,
resolution: None,
cov_increment: None,
}
}
pub fn set_present_value(&mut self, value: f32) {
self.present_value = value;
}
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 AnalogOutput {
pub fn new(instance: u32, object_name: String) -> Self {
Self {
identifier: ObjectIdentifier::new(ObjectType::AnalogOutput, instance),
object_name,
present_value: 0.0,
description: String::new(),
device_type: String::new(),
status_flags: 0,
event_state: EventState::Normal,
reliability: Reliability::NoFaultDetected,
out_of_service: false,
units: EngineeringUnits::NoUnits,
min_pres_value: None,
max_pres_value: None,
resolution: None,
priority_array: [None; 16],
relinquish_default: 0.0,
cov_increment: None,
}
}
pub fn write_priority(&mut self, priority: u8, value: Option<f32>) -> Result<()> {
if !(1..=16).contains(&priority) {
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) {
if let Some(value) = self.priority_array.iter().flatten().next() {
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 AnalogValue {
pub fn new(instance: u32, object_name: String) -> Self {
Self {
identifier: ObjectIdentifier::new(ObjectType::AnalogValue, instance),
object_name,
present_value: 0.0,
description: String::new(),
status_flags: 0,
event_state: EventState::Normal,
reliability: Reliability::NoFaultDetected,
out_of_service: false,
units: EngineeringUnits::NoUnits,
priority_array: [None; 16],
relinquish_default: 0.0,
cov_increment: None,
}
}
pub fn write_priority(&mut self, priority: u8, value: Option<f32>) -> Result<()> {
if !(1..=16).contains(&priority) {
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) {
if let Some(value) = self.priority_array.iter().flatten().next() {
self.present_value = *value;
return;
}
self.present_value = self.relinquish_default;
}
}
impl BacnetObject for AnalogInput {
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(u32::from(
ObjectType::AnalogInput,
))),
PropertyIdentifier::PresentValue => Ok(PropertyValue::Real(self.present_value)),
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 AnalogOutput {
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(u32::from(
ObjectType::AnalogOutput,
))),
PropertyIdentifier::PresentValue => Ok(PropertyValue::Real(self.present_value)),
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::Real(val),
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::Real(val) = value {
self.write_priority(8, Some(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 AnalogValue {
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(u32::from(
ObjectType::AnalogValue,
))),
PropertyIdentifier::PresentValue => Ok(PropertyValue::Real(self.present_value)),
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::Real(val),
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::Real(val) = value {
self.write_priority(8, Some(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_analog_input_creation() {
let ai = AnalogInput::new(1, "Temperature Sensor".to_string());
assert_eq!(ai.identifier.instance, 1);
assert_eq!(ai.object_name, "Temperature Sensor");
assert_eq!(ai.present_value, 0.0);
assert!(!ai.out_of_service);
}
#[test]
fn test_analog_output_priority() {
let mut ao = AnalogOutput::new(1, "Damper Position".to_string());
ao.write_priority(8, Some(75.0)).unwrap();
assert_eq!(ao.present_value, 75.0);
assert_eq!(ao.get_effective_priority(), Some(8));
ao.write_priority(3, Some(50.0)).unwrap();
assert_eq!(ao.present_value, 50.0);
assert_eq!(ao.get_effective_priority(), Some(3));
ao.write_priority(3, None).unwrap();
assert_eq!(ao.present_value, 75.0);
assert_eq!(ao.get_effective_priority(), Some(8));
ao.write_priority(8, None).unwrap();
assert_eq!(ao.present_value, ao.relinquish_default);
assert_eq!(ao.get_effective_priority(), None);
}
#[test]
fn test_analog_object_properties() {
let mut av = AnalogValue::new(1, "Test Value".to_string());
let name = av.get_property(PropertyIdentifier::ObjectName).unwrap();
if let PropertyValue::CharacterString(n) = name {
assert_eq!(n, "Test Value");
} else {
panic!("Expected CharacterString");
}
av.set_property(PropertyIdentifier::PresentValue, PropertyValue::Real(42.5))
.unwrap();
assert_eq!(av.present_value, 42.5);
assert!(av.is_property_writable(PropertyIdentifier::PresentValue));
assert!(!av.is_property_writable(PropertyIdentifier::ObjectIdentifier));
}
#[test]
fn test_status_flags() {
let mut ai = AnalogInput::new(1, "Test".to_string());
ai.set_status_flags(true, false, true, false);
let (in_alarm, fault, overridden, out_of_service) = ai.get_status_flags();
assert!(in_alarm);
assert!(!fault);
assert!(overridden);
assert!(!out_of_service);
assert_eq!(ai.status_flags, 0x0A); }
}