use crate::object::{
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 MultiStateInput {
pub identifier: ObjectIdentifier,
pub object_name: String,
pub present_value: u32,
pub description: String,
pub device_type: String,
pub status_flags: u8,
pub event_state: EventState,
pub reliability: Reliability,
pub out_of_service: bool,
pub number_of_states: u32,
pub state_text: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct MultiStateOutput {
pub identifier: ObjectIdentifier,
pub object_name: String,
pub present_value: u32,
pub description: String,
pub device_type: String,
pub status_flags: u8,
pub event_state: EventState,
pub reliability: Reliability,
pub out_of_service: bool,
pub number_of_states: u32,
pub state_text: Vec<String>,
pub priority_array: [Option<u32>; 16],
pub relinquish_default: u32,
}
#[derive(Debug, Clone)]
pub struct MultiStateValue {
pub identifier: ObjectIdentifier,
pub object_name: String,
pub present_value: u32,
pub description: String,
pub status_flags: u8,
pub event_state: EventState,
pub reliability: Reliability,
pub out_of_service: bool,
pub number_of_states: u32,
pub state_text: Vec<String>,
pub priority_array: [Option<u32>; 16],
pub relinquish_default: u32,
}
impl MultiStateInput {
pub fn new(instance: u32, object_name: String, number_of_states: u32) -> Self {
let mut state_text = Vec::with_capacity(number_of_states as usize);
for i in 1..=number_of_states {
state_text.push(format!("State {}", i));
}
Self {
identifier: ObjectIdentifier::new(ObjectType::MultiStateInput, instance),
object_name,
present_value: 1,
description: String::new(),
device_type: String::new(),
status_flags: 0,
event_state: EventState::Normal,
reliability: Reliability::NoFaultDetected,
out_of_service: false,
number_of_states,
state_text,
}
}
pub fn set_present_value(&mut self, value: u32) -> Result<()> {
if value < 1 || value > self.number_of_states {
return Err(ObjectError::InvalidValue(format!(
"Value must be between 1 and {}",
self.number_of_states
)));
}
self.present_value = value;
Ok(())
}
pub fn get_state_text(&self) -> Option<&str> {
if self.present_value > 0 && self.present_value <= self.state_text.len() as u32 {
Some(&self.state_text[(self.present_value - 1) as usize])
} else {
None
}
}
pub fn set_state_text(&mut self, state: u32, text: String) -> Result<()> {
if state < 1 || state > self.number_of_states {
return Err(ObjectError::InvalidValue(format!(
"State must be between 1 and {}",
self.number_of_states
)));
}
self.state_text[(state - 1) as usize] = text;
Ok(())
}
}
impl MultiStateOutput {
pub fn new(instance: u32, object_name: String, number_of_states: u32) -> Self {
let mut state_text = Vec::with_capacity(number_of_states as usize);
for i in 1..=number_of_states {
state_text.push(format!("State {}", i));
}
Self {
identifier: ObjectIdentifier::new(ObjectType::MultiStateOutput, instance),
object_name,
present_value: 1,
description: String::new(),
device_type: String::new(),
status_flags: 0,
event_state: EventState::Normal,
reliability: Reliability::NoFaultDetected,
out_of_service: false,
number_of_states,
state_text,
priority_array: [None; 16],
relinquish_default: 1,
}
}
pub fn write_priority(&mut self, priority: u8, value: Option<u32>) -> Result<()> {
if !(1..=16).contains(&priority) {
return Err(ObjectError::InvalidValue(
"Priority must be 1-16".to_string(),
));
}
if let Some(val) = value {
if val < 1 || val > self.number_of_states {
return Err(ObjectError::InvalidValue(format!(
"Value must be between 1 and {}",
self.number_of_states
)));
}
}
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 MultiStateValue {
pub fn new(instance: u32, object_name: String, number_of_states: u32) -> Self {
let mut state_text = Vec::with_capacity(number_of_states as usize);
for i in 1..=number_of_states {
state_text.push(format!("State {}", i));
}
Self {
identifier: ObjectIdentifier::new(ObjectType::MultiStateValue, instance),
object_name,
present_value: 1,
description: String::new(),
status_flags: 0,
event_state: EventState::Normal,
reliability: Reliability::NoFaultDetected,
out_of_service: false,
number_of_states,
state_text,
priority_array: [None; 16],
relinquish_default: 1,
}
}
pub fn write_priority(&mut self, priority: u8, value: Option<u32>) -> Result<()> {
if !(1..=16).contains(&priority) {
return Err(ObjectError::InvalidValue(
"Priority must be 1-16".to_string(),
));
}
if let Some(val) = value {
if val < 1 || val > self.number_of_states {
return Err(ObjectError::InvalidValue(format!(
"Value must be between 1 and {}",
self.number_of_states
)));
}
}
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 MultiStateInput {
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::MultiStateInput,
))),
PropertyIdentifier::PresentValue => {
Ok(PropertyValue::UnsignedInteger(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 MultiStateOutput {
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::MultiStateOutput,
))),
PropertyIdentifier::PresentValue => {
Ok(PropertyValue::UnsignedInteger(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::UnsignedInteger(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::UnsignedInteger(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 MultiStateValue {
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::MultiStateValue,
))),
PropertyIdentifier::PresentValue => {
Ok(PropertyValue::UnsignedInteger(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::UnsignedInteger(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::UnsignedInteger(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_multistate_input_creation() {
let msi = MultiStateInput::new(1, "Mode Selector".to_string(), 5);
assert_eq!(msi.identifier.instance, 1);
assert_eq!(msi.object_name, "Mode Selector");
assert_eq!(msi.number_of_states, 5);
assert_eq!(msi.present_value, 1);
assert_eq!(msi.state_text.len(), 5);
}
#[test]
fn test_multistate_state_text() {
let mut msi = MultiStateInput::new(1, "Mode".to_string(), 3);
msi.set_state_text(1, "OFF".to_string()).unwrap();
msi.set_state_text(2, "AUTO".to_string()).unwrap();
msi.set_state_text(3, "MANUAL".to_string()).unwrap();
assert_eq!(msi.get_state_text(), Some("OFF"));
msi.set_present_value(2).unwrap();
assert_eq!(msi.get_state_text(), Some("AUTO"));
assert!(msi.set_present_value(4).is_err());
}
#[test]
fn test_multistate_output_priority() {
let mut mso = MultiStateOutput::new(1, "Sequence Control".to_string(), 4);
mso.write_priority(8, Some(3)).unwrap();
assert_eq!(mso.present_value, 3);
assert_eq!(mso.get_effective_priority(), Some(8));
mso.write_priority(3, Some(2)).unwrap();
assert_eq!(mso.present_value, 2);
assert_eq!(mso.get_effective_priority(), Some(3));
assert!(mso.write_priority(3, Some(5)).is_err());
mso.write_priority(3, None).unwrap();
assert_eq!(mso.present_value, 3); }
#[test]
fn test_multistate_properties() {
let mut msv = MultiStateValue::new(1, "Operating Mode".to_string(), 4);
let name = msv.get_property(PropertyIdentifier::ObjectName).unwrap();
if let PropertyValue::CharacterString(n) = name {
assert_eq!(n, "Operating Mode");
} else {
panic!("Expected CharacterString");
}
msv.set_property(
PropertyIdentifier::PresentValue,
PropertyValue::UnsignedInteger(3),
)
.unwrap();
assert_eq!(msv.present_value, 3);
}
}