pub fn compute_status_flags(
base_flags: bacnet_types::primitives::StatusFlags,
reliability: u32,
out_of_service: bool,
event_state: u32,
) -> bacnet_types::primitives::PropertyValue {
let mut flags = base_flags;
if event_state != 0 {
flags |= bacnet_types::primitives::StatusFlags::IN_ALARM;
} else {
flags -= bacnet_types::primitives::StatusFlags::IN_ALARM;
}
if reliability != 0 {
flags |= bacnet_types::primitives::StatusFlags::FAULT;
} else {
flags -= bacnet_types::primitives::StatusFlags::FAULT;
}
if out_of_service {
flags |= bacnet_types::primitives::StatusFlags::OUT_OF_SERVICE;
} else {
flags -= bacnet_types::primitives::StatusFlags::OUT_OF_SERVICE;
}
bacnet_types::primitives::PropertyValue::BitString {
unused_bits: 4,
data: vec![flags.bits() << 4],
}
}
#[inline]
pub(crate) fn protocol_error(
class: bacnet_types::enums::ErrorClass,
code: bacnet_types::enums::ErrorCode,
) -> bacnet_types::error::Error {
bacnet_types::error::Error::Protocol {
class: class.to_raw() as u32,
code: code.to_raw() as u32,
}
}
pub fn read_property_list_property(
props: &[bacnet_types::enums::PropertyIdentifier],
array_index: Option<u32>,
) -> Result<bacnet_types::primitives::PropertyValue, bacnet_types::error::Error> {
use bacnet_types::enums::PropertyIdentifier;
let filtered: Vec<_> = props
.iter()
.copied()
.filter(|p| {
*p != PropertyIdentifier::OBJECT_IDENTIFIER
&& *p != PropertyIdentifier::OBJECT_NAME
&& *p != PropertyIdentifier::OBJECT_TYPE
&& *p != PropertyIdentifier::PROPERTY_LIST
})
.collect();
match array_index {
None => {
let elements = filtered
.iter()
.map(|p| bacnet_types::primitives::PropertyValue::Enumerated(p.to_raw()))
.collect();
Ok(bacnet_types::primitives::PropertyValue::List(elements))
}
Some(0) => Ok(bacnet_types::primitives::PropertyValue::Unsigned(
filtered.len() as u64,
)),
Some(idx) => {
let i = (idx - 1) as usize;
if i < filtered.len() {
Ok(bacnet_types::primitives::PropertyValue::Enumerated(
filtered[i].to_raw(),
))
} else {
Err(invalid_array_index_error())
}
}
}
}
macro_rules! read_common_properties {
($self:expr, $property:expr, $array_index:expr) => {
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::OBJECT_IDENTIFIER => Some(Ok(
bacnet_types::primitives::PropertyValue::ObjectIdentifier($self.oid),
)),
p if p == bacnet_types::enums::PropertyIdentifier::OBJECT_NAME => Some(Ok(
bacnet_types::primitives::PropertyValue::CharacterString($self.name.clone()),
)),
p if p == bacnet_types::enums::PropertyIdentifier::DESCRIPTION => Some(Ok(
bacnet_types::primitives::PropertyValue::CharacterString($self.description.clone()),
)),
p if p == bacnet_types::enums::PropertyIdentifier::STATUS_FLAGS => {
Some(Ok(common::compute_status_flags(
$self.status_flags,
$self.reliability,
$self.out_of_service,
0, )))
}
p if p == bacnet_types::enums::PropertyIdentifier::OUT_OF_SERVICE => Some(Ok(
bacnet_types::primitives::PropertyValue::Boolean($self.out_of_service),
)),
p if p == bacnet_types::enums::PropertyIdentifier::RELIABILITY => Some(Ok(
bacnet_types::primitives::PropertyValue::Enumerated($self.reliability),
)),
p if p == bacnet_types::enums::PropertyIdentifier::PROPERTY_LIST => {
let props = $self.property_list();
Some($crate::common::read_property_list_property(
&props,
$array_index,
))
}
_ => None,
}
};
}
pub(crate) use read_common_properties;
#[inline]
pub(crate) fn unknown_property_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::UNKNOWN_PROPERTY,
)
}
#[inline]
pub(crate) fn write_out_of_service(
out_of_service: &mut bool,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
if property == bacnet_types::enums::PropertyIdentifier::OUT_OF_SERVICE {
if let bacnet_types::primitives::PropertyValue::Boolean(v) = value {
*out_of_service = *v;
Some(Ok(()))
} else {
Some(Err(protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::INVALID_DATA_TYPE,
)))
}
} else {
None
}
}
#[inline]
pub(crate) fn write_out_of_service_with_reliability_restore(
out_of_service: &mut bool,
reliability: &mut u32,
saved_reliability: &mut Option<u32>,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
if property == bacnet_types::enums::PropertyIdentifier::OUT_OF_SERVICE {
if let bacnet_types::primitives::PropertyValue::Boolean(v) = value {
if !*out_of_service && *v {
*saved_reliability = Some(*reliability);
} else if *out_of_service && !*v {
*reliability = saved_reliability
.take()
.unwrap_or(bacnet_types::enums::Reliability::NO_FAULT_DETECTED.to_raw());
}
*out_of_service = *v;
Some(Ok(()))
} else {
Some(Err(protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::INVALID_DATA_TYPE,
)))
}
} else {
None
}
}
pub(crate) use crate::reliability_inhibit::ReliabilityInhibitState;
#[inline]
pub(crate) fn write_description(
description: &mut String,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
if property == bacnet_types::enums::PropertyIdentifier::DESCRIPTION {
if let bacnet_types::primitives::PropertyValue::CharacterString(s) = value {
*description = s.clone();
Some(Ok(()))
} else {
Some(Err(invalid_data_type_error()))
}
} else {
None
}
}
pub(crate) fn write_object_name(
name: &mut String,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
if property == bacnet_types::enums::PropertyIdentifier::OBJECT_NAME {
if let bacnet_types::primitives::PropertyValue::CharacterString(s) = value {
if s.is_empty() {
Some(Err(value_out_of_range_error()))
} else {
*name = s.clone();
Some(Ok(()))
}
} else {
Some(Err(invalid_data_type_error()))
}
} else {
None
}
}
#[inline]
pub(crate) fn write_access_denied_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::WRITE_ACCESS_DENIED,
)
}
#[inline]
pub(crate) fn invalid_data_type_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::INVALID_DATA_TYPE,
)
}
#[inline]
pub(crate) fn value_out_of_range_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::VALUE_OUT_OF_RANGE,
)
}
#[inline]
pub(crate) fn invalid_data_encoding_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::INVALID_DATA_ENCODING,
)
}
#[inline]
pub(crate) fn check_fixed_width_bit_string(
unused_bits: u8,
data: &[u8],
named_bits: u8,
) -> Result<u8, bacnet_types::error::Error> {
if data.len() == 1 && unused_bits == 8 - named_bits {
Ok(data[0])
} else {
Err(invalid_data_encoding_error())
}
}
#[inline]
pub(crate) fn is_reliability_value_valid(value: u32) -> bool {
bacnet_types::enums::Reliability::ALL_NAMED
.iter()
.any(|&(_, named)| named.to_raw() == value)
|| (64..=65_535).contains(&value)
}
#[inline]
pub(crate) fn invalid_array_index_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::INVALID_ARRAY_INDEX,
)
}
#[inline]
pub(crate) fn property_is_not_an_array_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::PROPERTY_IS_NOT_AN_ARRAY,
)
}
#[inline]
pub(crate) fn reject_non_finite(v: f32) -> Result<(), bacnet_types::error::Error> {
if v.is_finite() {
Ok(())
} else {
Err(value_out_of_range_error())
}
}
#[inline]
pub(crate) fn u64_to_u32(v: u64) -> Result<u32, bacnet_types::error::Error> {
u32::try_from(v).map_err(|_| value_out_of_range_error())
}
#[inline]
pub(crate) fn recalculate_from_priority_array<T: Copy>(
priority_array: &[Option<T>; 16],
relinquish_default: T,
) -> T {
priority_array
.iter()
.flatten()
.next()
.copied()
.unwrap_or(relinquish_default)
}
#[derive(Debug, Clone)]
pub struct ValueSourceTracking {
pub value_source: bacnet_types::primitives::PropertyValue,
#[allow(dead_code)]
pub value_source_array: [bacnet_types::primitives::PropertyValue; 16],
pub last_command_time: bacnet_types::primitives::BACnetTimeStamp,
#[allow(dead_code)]
pub command_time_array: [bacnet_types::primitives::BACnetTimeStamp; 16],
}
impl Default for ValueSourceTracking {
fn default() -> Self {
Self {
value_source: bacnet_types::primitives::PropertyValue::Null,
value_source_array: std::array::from_fn(|_| {
bacnet_types::primitives::PropertyValue::Null
}),
last_command_time: bacnet_types::primitives::BACnetTimeStamp::SequenceNumber(0),
command_time_array: std::array::from_fn(|_| {
bacnet_types::primitives::BACnetTimeStamp::SequenceNumber(0)
}),
}
}
}
pub(crate) fn current_command_priority<T>(
priority_array: &[Option<T>; 16],
) -> bacnet_types::primitives::PropertyValue {
for (i, slot) in priority_array.iter().enumerate() {
if slot.is_some() {
return bacnet_types::primitives::PropertyValue::Unsigned((i + 1) as u64);
}
}
bacnet_types::primitives::PropertyValue::Null
}
macro_rules! read_generic_event_properties {
($self:expr, $property:expr) => {
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::EVENT_STATE => {
Some(Ok(bacnet_types::primitives::PropertyValue::Enumerated(
$self.event_detector.event_state.to_raw(),
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::EVENT_ENABLE => {
Some(Ok(bacnet_types::primitives::PropertyValue::BitString {
unused_bits: 5,
data: vec![bacnet_types::bitstring::pack_octet(
$self.event_detector.event_enable,
)],
}))
}
p if p == bacnet_types::enums::PropertyIdentifier::NOTIFY_TYPE => {
Some(Ok(bacnet_types::primitives::PropertyValue::Enumerated(
$self.event_detector.notify_type,
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::NOTIFICATION_CLASS => {
Some(Ok(bacnet_types::primitives::PropertyValue::Unsigned(
$self.event_detector.notification_class as u64,
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::TIME_DELAY => {
Some(Ok(bacnet_types::primitives::PropertyValue::Unsigned(
$self.event_detector.time_delay as u64,
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::TIME_DELAY_NORMAL => {
Some(Ok(bacnet_types::primitives::PropertyValue::Unsigned(
$self
.event_detector
.time_delay_normal
.unwrap_or($self.event_detector.time_delay) as u64,
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::ACKED_TRANSITIONS => {
Some(Ok(bacnet_types::primitives::PropertyValue::BitString {
unused_bits: 5,
data: vec![bacnet_types::bitstring::pack_octet(
$self.event_detector.acked_transitions,
)],
}))
}
_ => None,
}
};
}
pub(crate) use read_generic_event_properties;
macro_rules! read_analog_event_properties {
($self:expr, $property:expr) => {
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::HIGH_LIMIT => Some(Ok(
bacnet_types::primitives::PropertyValue::Real($self.event_detector.high_limit),
)),
p if p == bacnet_types::enums::PropertyIdentifier::LOW_LIMIT => Some(Ok(
bacnet_types::primitives::PropertyValue::Real($self.event_detector.low_limit),
)),
p if p == bacnet_types::enums::PropertyIdentifier::DEADBAND => Some(Ok(
bacnet_types::primitives::PropertyValue::Real($self.event_detector.deadband),
)),
p if p == bacnet_types::enums::PropertyIdentifier::LIMIT_ENABLE => {
Some(Ok(bacnet_types::primitives::PropertyValue::BitString {
unused_bits: 6,
data: vec![$self.event_detector.limit_enable.to_bits()],
}))
}
_ => None,
}
};
}
pub(crate) use read_analog_event_properties;
macro_rules! write_analog_event_properties {
($self:expr, $property:expr, $value:expr) => {
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::HIGH_LIMIT => {
if let bacnet_types::primitives::PropertyValue::Real(v) = $value {
if let Err(e) = $crate::common::reject_non_finite(v) {
Some(Err(e))
} else {
$self.event_detector.high_limit = v;
Some(Ok(()))
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::LOW_LIMIT => {
if let bacnet_types::primitives::PropertyValue::Real(v) = $value {
if let Err(e) = $crate::common::reject_non_finite(v) {
Some(Err(e))
} else {
$self.event_detector.low_limit = v;
Some(Ok(()))
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::DEADBAND => {
if let bacnet_types::primitives::PropertyValue::Real(v) = $value {
if v < 0.0 || !v.is_finite() {
Some(Err($crate::common::value_out_of_range_error()))
} else {
$self.event_detector.deadband = v;
Some(Ok(()))
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::LIMIT_ENABLE => {
if let bacnet_types::primitives::PropertyValue::BitString { unused_bits, data } =
&$value
{
match $crate::common::check_fixed_width_bit_string(*unused_bits, data, 2) {
Ok(byte) => {
$self.event_detector.limit_enable =
$crate::event::LimitEnable::from_bits(byte);
Some(Ok(()))
}
Err(e) => Some(Err(e)),
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
_ => None,
}
};
}
pub(crate) use write_analog_event_properties;
macro_rules! write_generic_event_properties {
($self:expr, $property:expr, $value:expr) => {
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::EVENT_ENABLE => {
if let bacnet_types::primitives::PropertyValue::BitString { unused_bits, data } =
&$value
{
match $crate::common::check_fixed_width_bit_string(*unused_bits, data, 3) {
Ok(byte) => {
$self.event_detector.event_enable =
bacnet_types::bitstring::unpack_octet(&[byte], 3);
Some(Ok(()))
}
Err(e) => Some(Err(e)),
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::NOTIFICATION_CLASS => {
if let bacnet_types::primitives::PropertyValue::Unsigned(v) = $value {
match $crate::common::u64_to_u32(v) {
Ok(v32) => {
$self.event_detector.notification_class = v32;
Some(Ok(()))
}
Err(e) => Some(Err(e)),
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::NOTIFY_TYPE => {
if let bacnet_types::primitives::PropertyValue::Enumerated(v) = $value {
let named = bacnet_types::enums::NotifyType::ALL_NAMED
.iter()
.any(|&(_, n)| n.to_raw() == v);
if !named {
Some(Err($crate::common::value_out_of_range_error()))
} else {
$self.event_detector.notify_type = v;
Some(Ok(()))
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::TIME_DELAY => {
if let bacnet_types::primitives::PropertyValue::Unsigned(v) = $value {
match $crate::common::u64_to_u32(v) {
Ok(v32) => {
$self.event_detector.time_delay = v32;
Some(Ok(()))
}
Err(e) => Some(Err(e)),
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::TIME_DELAY_NORMAL => {
if let bacnet_types::primitives::PropertyValue::Unsigned(v) = $value {
match $crate::common::u64_to_u32(v) {
Ok(v32) => {
$self.event_detector.time_delay_normal = Some(v32);
Some(Ok(()))
}
Err(e) => Some(Err(e)),
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::ACKED_TRANSITIONS => {
Some(Err($crate::common::write_access_denied_error()))
}
_ => None,
}
};
}
pub(crate) use write_generic_event_properties;
macro_rules! read_priority_array {
($self:expr, $array_index:expr, $wrap:expr) => {{
let wrap_fn = $wrap;
match $array_index {
None => {
let elements = $self
.priority_array
.iter()
.map(|slot| match slot {
Some(v) => wrap_fn(*v),
None => bacnet_types::primitives::PropertyValue::Null,
})
.collect();
Ok(bacnet_types::primitives::PropertyValue::List(elements))
}
Some(0) => Ok(bacnet_types::primitives::PropertyValue::Unsigned(16)),
Some(idx) if (1..=16).contains(&idx) => {
match $self.priority_array[(idx - 1) as usize] {
Some(v) => Ok(wrap_fn(v)),
None => Ok(bacnet_types::primitives::PropertyValue::Null),
}
}
_ => Err($crate::common::invalid_array_index_error()),
}
}};
}
pub(crate) use read_priority_array;
macro_rules! write_priority_array {
($self:expr, $value:expr, $priority:expr, $extract:expr) => {{
let prio = $priority.unwrap_or(16);
if !(1..=16).contains(&prio) {
return Err($crate::common::value_out_of_range_error());
}
let idx = (prio - 1) as usize;
match $value {
bacnet_types::primitives::PropertyValue::Null => {
$self.priority_array[idx] = None;
}
other => {
let extracted = ($extract)(other)?;
$self.priority_array[idx] = Some(extracted);
}
}
$self.recalculate_present_value();
Ok(())
}};
}
pub(crate) use write_priority_array;
macro_rules! write_priority_array_direct {
($self:expr, $property:expr, $array_index:expr, $value:expr, $extract:expr) => {
if $property == bacnet_types::enums::PropertyIdentifier::PRIORITY_ARRAY {
let idx = match $array_index {
Some(n) if (1..=16).contains(&n) => (n - 1) as usize,
Some(_) => return Err($crate::common::invalid_array_index_error()),
None => return Err($crate::common::write_access_denied_error()),
};
match $value {
bacnet_types::primitives::PropertyValue::Null => {
$self.priority_array[idx] = None;
}
other => {
let extracted = ($extract)(other)?;
$self.priority_array[idx] = Some(extracted);
}
}
$self.recalculate_present_value();
return Ok(());
}
};
}
pub(crate) use write_priority_array_direct;
#[inline]
pub(crate) fn write_cov_increment(
cov_increment: &mut f32,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
if property == bacnet_types::enums::PropertyIdentifier::COV_INCREMENT {
if let bacnet_types::primitives::PropertyValue::Real(v) = value {
if *v < 0.0 || !v.is_finite() {
Some(Err(value_out_of_range_error()))
} else {
*cov_increment = *v;
Some(Ok(()))
}
} else {
Some(Err(invalid_data_type_error()))
}
} else {
None
}
}
#[inline]
pub(crate) fn is_generic_event_property_writable(
property: bacnet_types::enums::PropertyIdentifier,
) -> bool {
matches!(
property,
bacnet_types::enums::PropertyIdentifier::EVENT_ENABLE
| bacnet_types::enums::PropertyIdentifier::NOTIFICATION_CLASS
| bacnet_types::enums::PropertyIdentifier::NOTIFY_TYPE
| bacnet_types::enums::PropertyIdentifier::TIME_DELAY
| bacnet_types::enums::PropertyIdentifier::TIME_DELAY_NORMAL
)
}
#[inline]
pub(crate) fn is_event_property_writable(
property: bacnet_types::enums::PropertyIdentifier,
) -> bool {
is_generic_event_property_writable(property)
|| matches!(
property,
bacnet_types::enums::PropertyIdentifier::HIGH_LIMIT
| bacnet_types::enums::PropertyIdentifier::LOW_LIMIT
| bacnet_types::enums::PropertyIdentifier::DEADBAND
| bacnet_types::enums::PropertyIdentifier::LIMIT_ENABLE
)
}
#[inline]
pub(crate) fn is_commandable_property_writable(
property: bacnet_types::enums::PropertyIdentifier,
) -> bool {
matches!(
property,
bacnet_types::enums::PropertyIdentifier::PRIORITY_ARRAY
| bacnet_types::enums::PropertyIdentifier::PRESENT_VALUE
| bacnet_types::enums::PropertyIdentifier::RELINQUISH_DEFAULT
)
}
#[inline]
pub(crate) fn is_common_writable(property: bacnet_types::enums::PropertyIdentifier) -> bool {
matches!(
property,
bacnet_types::enums::PropertyIdentifier::OUT_OF_SERVICE
| bacnet_types::enums::PropertyIdentifier::OBJECT_NAME
| bacnet_types::enums::PropertyIdentifier::DESCRIPTION
)
}
#[inline]
pub(crate) fn is_multistate_commandable_writable(
property: bacnet_types::enums::PropertyIdentifier,
) -> bool {
is_commandable_property_writable(property)
|| is_common_writable(property)
|| property == bacnet_types::enums::PropertyIdentifier::STATE_TEXT
}
#[inline]
pub(crate) fn is_multistate_input_writable(
property: bacnet_types::enums::PropertyIdentifier,
) -> bool {
is_common_writable(property)
|| property == bacnet_types::enums::PropertyIdentifier::PRESENT_VALUE
|| property == bacnet_types::enums::PropertyIdentifier::STATE_TEXT
}