use bacnet_types::constructed::{BACnetPropertyStates, ChangeOfValueCriteria};
use bacnet_types::enums::{EventState, EventType};
use bacnet_types::primitives::PropertyValue;
pub(crate) struct ArmEvaluation {
pub indication: Option<Indication>,
pub establish_baseline: Option<PropertyValue>,
}
impl ArmEvaluation {
fn simple(indication: Option<Indication>) -> Self {
Self {
indication,
establish_baseline: None,
}
}
}
pub(crate) struct Indication {
pub target: EventState,
pub condition: u64,
pub offnormal_value: Option<u64>,
pub new_baseline: Option<PropertyValue>,
}
pub(crate) fn inhibited_arm(
params: &bacnet_types::constructed::BACnetEventParameter,
monitored_value: &PropertyValue,
current: EventState,
) -> ArmEvaluation {
ArmEvaluation::simple((current != EventState::NORMAL).then(|| {
Indication {
new_baseline: matches!(
params,
bacnet_types::constructed::BACnetEventParameter::ChangeOfValue { .. }
)
.then(|| monitored_value.clone()),
..Indication::plain(EventState::NORMAL, 0)
}
}))
}
impl Indication {
fn plain(target: EventState, condition: u64) -> Self {
Self {
target,
condition,
offnormal_value: None,
new_baseline: None,
}
}
}
pub fn encode_out_of_range_params(high_limit: f32, low_limit: f32, deadband: f32) -> Vec<u8> {
let mut buf = Vec::with_capacity(12);
buf.extend_from_slice(&high_limit.to_le_bytes());
buf.extend_from_slice(&low_limit.to_le_bytes());
buf.extend_from_slice(&deadband.to_le_bytes());
buf
}
pub fn encode_floating_limit_params(
setpoint: f32,
high_diff_limit: f32,
low_diff_limit: f32,
deadband: f32,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(16);
buf.extend_from_slice(&setpoint.to_le_bytes());
buf.extend_from_slice(&high_diff_limit.to_le_bytes());
buf.extend_from_slice(&low_diff_limit.to_le_bytes());
buf.extend_from_slice(&deadband.to_le_bytes());
buf
}
pub fn encode_change_of_state_params(alarm_values: &[u32]) -> Vec<u8> {
let mut buf = Vec::with_capacity(4 + alarm_values.len() * 4);
buf.extend_from_slice(&(alarm_values.len() as u32).to_le_bytes());
for &v in alarm_values {
buf.extend_from_slice(&v.to_le_bytes());
}
buf
}
pub fn encode_change_of_value_params(increment: f32) -> Vec<u8> {
increment.to_le_bytes().to_vec()
}
pub fn encode_change_of_bitstring_params(mask: &[u8], alarm_bits: &[u8]) -> Vec<u8> {
let len = mask.len().min(alarm_bits.len());
let mut buf = Vec::with_capacity(4 + len * 2);
buf.extend_from_slice(&(len as u32).to_le_bytes());
buf.extend_from_slice(&mask[..len]);
buf.extend_from_slice(&alarm_bits[..len]);
buf
}
fn reachable_or_normal(current: EventState, reachable: &[EventState]) -> EventState {
if reachable.contains(¤t) {
current
} else {
EventState::NORMAL
}
}
fn eval_out_of_range(params: &[u8], value: f32, current: EventState) -> EventState {
if params.len() < 12 {
return current;
}
let current = reachable_or_normal(
current,
&[
EventState::NORMAL,
EventState::HIGH_LIMIT,
EventState::LOW_LIMIT,
],
);
let high_limit = f32::from_le_bytes([params[0], params[1], params[2], params[3]]);
let low_limit = f32::from_le_bytes([params[4], params[5], params[6], params[7]]);
let deadband = f32::from_le_bytes([params[8], params[9], params[10], params[11]]);
match current {
s if s == EventState::NORMAL => {
if value > high_limit {
EventState::HIGH_LIMIT
} else if value < low_limit {
EventState::LOW_LIMIT
} else {
EventState::NORMAL
}
}
s if s == EventState::HIGH_LIMIT => {
if value < low_limit {
EventState::LOW_LIMIT
} else if value < high_limit - deadband {
EventState::NORMAL
} else {
EventState::HIGH_LIMIT
}
}
s if s == EventState::LOW_LIMIT => {
if value > high_limit {
EventState::HIGH_LIMIT
} else if value > low_limit + deadband {
EventState::NORMAL
} else {
EventState::LOW_LIMIT
}
}
_ => current,
}
}
fn eval_floating_limit(params: &[u8], value: f32, current: EventState) -> EventState {
if params.len() < 16 {
return current;
}
let current = reachable_or_normal(
current,
&[
EventState::NORMAL,
EventState::HIGH_LIMIT,
EventState::LOW_LIMIT,
],
);
let setpoint = f32::from_le_bytes([params[0], params[1], params[2], params[3]]);
let high_diff = f32::from_le_bytes([params[4], params[5], params[6], params[7]]);
let low_diff = f32::from_le_bytes([params[8], params[9], params[10], params[11]]);
let deadband = f32::from_le_bytes([params[12], params[13], params[14], params[15]]);
let high_limit = setpoint + high_diff;
let low_limit = setpoint - low_diff;
match current {
s if s == EventState::NORMAL => {
if value > high_limit {
EventState::HIGH_LIMIT
} else if value < low_limit {
EventState::LOW_LIMIT
} else {
EventState::NORMAL
}
}
s if s == EventState::HIGH_LIMIT => {
if value < low_limit {
EventState::LOW_LIMIT
} else if value < high_limit - deadband {
EventState::NORMAL
} else {
EventState::HIGH_LIMIT
}
}
s if s == EventState::LOW_LIMIT => {
if value > high_limit {
EventState::HIGH_LIMIT
} else if value > low_limit + deadband {
EventState::NORMAL
} else {
EventState::LOW_LIMIT
}
}
_ => current,
}
}
fn eval_change_of_state(params: &[u8], value: u32, _current: EventState) -> EventState {
if params.len() < 4 {
return EventState::NORMAL;
}
let count = u32::from_le_bytes([params[0], params[1], params[2], params[3]]) as usize;
let needed = 4usize.saturating_add(count.saturating_mul(4));
if params.len() < needed {
return EventState::NORMAL;
}
for i in 0..count {
let offset = 4 + i * 4;
let alarm_val = u32::from_le_bytes([
params[offset],
params[offset + 1],
params[offset + 2],
params[offset + 3],
]);
if value == alarm_val {
return EventState::OFFNORMAL;
}
}
EventState::NORMAL
}
fn eval_change_of_bitstring(params: &[u8], value_bits: &[u8], _current: EventState) -> EventState {
if params.len() < 4 {
return EventState::NORMAL;
}
let mask_len = u32::from_le_bytes([params[0], params[1], params[2], params[3]]) as usize;
let needed = 4usize.saturating_add(mask_len.saturating_mul(2));
if params.len() < needed {
return EventState::NORMAL;
}
let mask = ¶ms[4..4 + mask_len];
let alarm_bits = ¶ms[4 + mask_len..4 + 2 * mask_len];
for i in 0..mask_len {
let monitored_byte = value_bits.get(i).copied().unwrap_or(0);
if (monitored_byte & mask[i]) != (alarm_bits[i] & mask[i]) {
return EventState::NORMAL;
}
}
EventState::OFFNORMAL
}
fn eval_change_of_value(params: &[u8], value: f32, _current: EventState) -> EventState {
if params.len() < 4 {
return EventState::NORMAL;
}
let increment = f32::from_le_bytes([params[0], params[1], params[2], params[3]]);
if increment <= 0.0 || !increment.is_finite() {
return EventState::NORMAL;
}
if value.abs() >= increment {
EventState::OFFNORMAL
} else {
EventState::NORMAL
}
}
pub(crate) fn eval_out_of_range_struct(
low_limit: f32,
high_limit: f32,
deadband: f32,
value: f32,
current: EventState,
) -> ArmEvaluation {
let new_state = eval_out_of_range(
&encode_out_of_range_params(high_limit, low_limit, deadband),
value,
current,
);
ArmEvaluation::simple((new_state != current).then(|| Indication::plain(new_state, 0)))
}
pub(crate) fn eval_floating_limit_struct(
setpoint: f32,
high_diff_limit: f32,
low_diff_limit: f32,
deadband: f32,
value: f32,
current: EventState,
) -> ArmEvaluation {
let new_state = eval_floating_limit(
&encode_floating_limit_params(setpoint, high_diff_limit, low_diff_limit, deadband),
value,
current,
);
ArmEvaluation::simple((new_state != current).then(|| Indication::plain(new_state, 0)))
}
fn property_state_matches(state: &BACnetPropertyStates, value: PropertyStateValue) -> bool {
use BACnetPropertyStates as S;
match (state, value) {
(S::BooleanValue(expected), PropertyStateValue::Boolean(actual)) => *expected == actual,
(S::IntegerValue(expected), PropertyStateValue::Signed(actual)) => *expected == actual,
(S::UnsignedValue(expected), PropertyStateValue::Unsigned(actual)) => {
u64::from(*expected) == actual
}
(S::BooleanValue(_) | S::IntegerValue(_) | S::UnsignedValue(_), _) => false,
(_, PropertyStateValue::Enumerated(actual)) => state.as_u32() == Some(actual),
_ => false,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PropertyStateValue {
Boolean(bool),
Signed(i32),
Unsigned(u64),
Enumerated(u32),
}
impl PropertyStateValue {
fn identity(self) -> u64 {
match self {
Self::Boolean(value) => u64::from(value),
Self::Signed(value) => (1u64 << 32) | value as u32 as u64,
Self::Unsigned(value) => (2u64 << 32) | value,
Self::Enumerated(value) => (3u64 << 32) | value as u64,
}
}
}
pub(crate) fn eval_change_of_state_struct(
alarm_values: &[BACnetPropertyStates],
value: PropertyStateValue,
current: EventState,
last_offnormal_value: Option<u64>,
) -> ArmEvaluation {
let matched = alarm_values
.iter()
.any(|s| property_state_matches(s, value));
let identity = value.identity();
let offnormal = |condition| Indication {
target: EventState::OFFNORMAL,
condition,
offnormal_value: Some(identity),
new_baseline: None,
};
if reachable_or_normal(current, &[EventState::NORMAL, EventState::OFFNORMAL]) != current {
return ArmEvaluation::simple(Some(if matched {
offnormal(0)
} else {
Indication::plain(EventState::NORMAL, 0)
}));
}
let indication = if current == EventState::NORMAL && matched {
Some(offnormal(0))
} else if current == EventState::OFFNORMAL && !matched {
Some(Indication::plain(EventState::NORMAL, 0))
} else if current == EventState::OFFNORMAL
&& matched
&& last_offnormal_value.is_some_and(|caused| caused != identity)
{
Some(offnormal(identity))
} else {
None
};
ArmEvaluation::simple(indication)
}
fn masked_value_hash(mask: &[u8], value_bits: &[u8]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for (i, &m) in mask.iter().enumerate() {
let b = value_bits.get(i).copied().unwrap_or(0) & m;
h = (h ^ b as u64).wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
pub(crate) fn eval_change_of_bitstring_struct(
bitmask: &(u8, Vec<u8>),
list_of_values: &[(u8, Vec<u8>)],
value_bits: &[u8],
current: EventState,
) -> ArmEvaluation {
let mask = &bitmask.1;
let width = mask.len().max(value_bits.len());
let mut matched = false;
for alarm in list_of_values {
let alarm_bits = &alarm.1;
if width == 0 {
continue;
}
let mut this_matches = true;
for i in 0..width {
let m = mask.get(i).copied().unwrap_or(0);
let v = value_bits.get(i).copied().unwrap_or(0) & m;
let a = alarm_bits.get(i).copied().unwrap_or(0) & m;
if v != a {
this_matches = false;
break;
}
}
if this_matches {
matched = true;
break;
}
}
if reachable_or_normal(current, &[EventState::NORMAL, EventState::OFFNORMAL]) != current {
return ArmEvaluation::simple(Some(if matched {
Indication::plain(EventState::OFFNORMAL, masked_value_hash(mask, value_bits))
} else {
Indication::plain(EventState::NORMAL, 0)
}));
}
let indication = if current == EventState::NORMAL && matched {
Some(Indication::plain(
EventState::OFFNORMAL,
masked_value_hash(mask, value_bits),
))
} else if current == EventState::OFFNORMAL && !matched {
Some(Indication::plain(EventState::NORMAL, 0))
} else {
None
};
ArmEvaluation::simple(indication)
}
pub(crate) fn eval_change_of_value_struct(
criteria: &ChangeOfValueCriteria,
monitored_value: &PropertyValue,
baseline: Option<&PropertyValue>,
current: EventState,
) -> Option<ArmEvaluation> {
let foreign_recovery = || {
(current != EventState::NORMAL).then(|| ArmEvaluation {
indication: Some(Indication {
target: EventState::NORMAL,
condition: 0,
offnormal_value: None,
new_baseline: Some(monitored_value.clone()),
}),
establish_baseline: None,
})
};
match criteria {
ChangeOfValueCriteria::Bitmask { data: mask, .. } => {
let current_bits = extract_bitstring(monitored_value)?;
if let Some(recovery) = foreign_recovery() {
return Some(recovery);
}
let masked_changed = |base: &[u8]| {
(0..mask.len()).any(|i| {
let now = current_bits.get(i).copied().unwrap_or(0) & mask[i];
let then = base.get(i).copied().unwrap_or(0) & mask[i];
now != then
})
};
let eval = match baseline.and_then(extract_bitstring) {
None => ArmEvaluation {
indication: None,
establish_baseline: Some(monitored_value.clone()),
},
Some(base_bits) => ArmEvaluation::simple(
(masked_changed(&base_bits) && current == EventState::NORMAL).then(|| {
Indication {
target: EventState::NORMAL,
condition: 0,
offnormal_value: None,
new_baseline: Some(monitored_value.clone()),
}
}),
),
};
Some(eval)
}
ChangeOfValueCriteria::ReferencedPropertyIncrement(increment) => {
let value = extract_real(monitored_value)?;
if let Some(recovery) = foreign_recovery() {
return Some(recovery);
}
let eval = match baseline.and_then(extract_real) {
None => ArmEvaluation {
indication: None,
establish_baseline: Some(monitored_value.clone()),
},
Some(_) if !increment.is_finite() || *increment <= 0.0 => {
ArmEvaluation::simple(None)
}
Some(base) => ArmEvaluation::simple(
((value - base).abs() >= *increment && current == EventState::NORMAL).then(
|| Indication {
target: EventState::NORMAL,
condition: 0,
offnormal_value: None,
new_baseline: Some(monitored_value.clone()),
},
),
),
};
Some(eval)
}
}
}
fn eval_legacy_le(
data: &[u8],
monitored_value: &PropertyValue,
current: EventState,
event_type: EventType,
) -> EventState {
if event_type == EventType::OUT_OF_RANGE {
extract_real(monitored_value)
.map(|v| eval_out_of_range(data, v, current))
.unwrap_or(current)
} else if event_type == EventType::FLOATING_LIMIT {
extract_real(monitored_value)
.map(|v| eval_floating_limit(data, v, current))
.unwrap_or(current)
} else if event_type == EventType::CHANGE_OF_STATE {
extract_enumerated(monitored_value)
.map(|v| eval_change_of_state(data, v, current))
.unwrap_or(current)
} else if event_type == EventType::CHANGE_OF_BITSTRING {
extract_bitstring(monitored_value)
.map(|bits| eval_change_of_bitstring(data, &bits, current))
.unwrap_or(current)
} else if event_type == EventType::CHANGE_OF_VALUE {
extract_real(monitored_value)
.map(|v| eval_change_of_value(data, v, current))
.unwrap_or(current)
} else {
current
}
}
pub(crate) fn eval_none_arm(current: EventState) -> ArmEvaluation {
let offnormal = current != EventState::NORMAL && current != EventState::FAULT;
ArmEvaluation::simple(offnormal.then(|| Indication::plain(EventState::NORMAL, 0)))
}
pub(crate) fn eval_legacy_le_arm(
data: &[u8],
monitored_value: &PropertyValue,
current: EventState,
event_type: EventType,
) -> ArmEvaluation {
let new_state = eval_legacy_le(data, monitored_value, current, event_type);
ArmEvaluation::simple((new_state != current).then(|| Indication::plain(new_state, 0)))
}
pub(crate) fn extract_real(pv: &PropertyValue) -> Option<f32> {
match pv {
PropertyValue::Real(v) => Some(*v),
PropertyValue::Double(v) => Some(*v as f32),
PropertyValue::Unsigned(v) => Some(*v as f32),
PropertyValue::Signed(v) => Some(*v as f32),
_ => None,
}
}
pub(crate) fn extract_enumerated(pv: &PropertyValue) -> Option<u32> {
match pv {
PropertyValue::Enumerated(v) => Some(*v),
PropertyValue::Unsigned(v) => u32::try_from(*v).ok(),
_ => None,
}
}
pub(crate) fn extract_property_state_value(pv: &PropertyValue) -> Option<PropertyStateValue> {
match pv {
PropertyValue::Boolean(value) => Some(PropertyStateValue::Boolean(*value)),
PropertyValue::Signed(value) => Some(PropertyStateValue::Signed(*value)),
PropertyValue::Enumerated(value) => Some(PropertyStateValue::Enumerated(*value)),
PropertyValue::Unsigned(value) => Some(PropertyStateValue::Unsigned(*value)),
_ => None,
}
}
pub(crate) fn extract_bitstring(pv: &PropertyValue) -> Option<Vec<u8>> {
match pv {
PropertyValue::BitString { data, .. } => Some(data.clone()),
_ => None,
}
}