use core::ops::ControlFlow;
use bacnet_types::bitstring::{EventTransitionBits, LimitEnable};
use bacnet_types::enums::{EventState, EventType, NotifyType, Reliability};
use bacnet_types::primitives::BACnetTimeStamp;
pub(crate) mod history;
pub(crate) mod options;
pub(crate) mod state_reporting;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EventStateChange {
pub from: EventState,
pub to: EventState,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransitionOutcome {
pub change: EventStateChange,
pub event_type: EventType,
pub distribute: bool,
}
impl EventStateChange {
pub fn event_type(&self, algorithm: EventType) -> EventType {
if self.from == EventState::FAULT || self.to == EventState::FAULT {
EventType::CHANGE_OF_RELIABILITY
} else {
algorithm
}
}
pub fn transition(&self) -> EventTransition {
EventTransition::for_target_state(self.to)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventTransition {
ToOffnormal,
ToFault,
ToNormal,
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnrollmentSummaryCapability {
pub event_type: EventType,
pub last_transition: Option<EventTransition>,
}
impl EventTransition {
pub fn for_target_state(state: EventState) -> Self {
if state == EventState::NORMAL {
Self::ToNormal
} else if state == EventState::FAULT {
Self::ToFault
} else {
Self::ToOffnormal
}
}
pub fn bit_mask(self) -> EventTransitionBits {
match self {
EventTransition::ToOffnormal => EventTransitionBits::TO_OFFNORMAL,
EventTransition::ToFault => EventTransitionBits::TO_FAULT,
EventTransition::ToNormal => EventTransitionBits::TO_NORMAL,
}
}
pub fn index(self) -> usize {
match self {
EventTransition::ToOffnormal => 0,
EventTransition::ToFault => 1,
EventTransition::ToNormal => 2,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EventTransitionCommit {
pub change: EventStateChange,
pub coordinate: EventTransition,
pub ack_required: bool,
pub timestamp: BACnetTimeStamp,
pub message_text: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventTransitionCommitError {
Unsupported,
CoordinateTargetMismatch {
coordinate: EventTransition,
target: EventState,
},
CurrentStateMismatch {
expected: EventState,
actual: EventState,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PendingTransition {
pub state: EventState,
pub remaining: u32,
}
impl PendingTransition {
fn seed(state: EventState, delay: u32) -> Self {
Self {
state,
remaining: delay,
}
}
}
fn delay_toward(time_delay: u32, time_delay_normal: Option<u32>, target: EventState) -> u32 {
if target == EventState::NORMAL {
time_delay_normal.unwrap_or(time_delay)
} else {
time_delay
}
}
fn inhibited_target(
event_state: EventState,
pending: &mut Option<PendingTransition>,
) -> Option<EventState> {
*pending = None;
(event_state != EventState::NORMAL).then_some(EventState::NORMAL)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FaultPrecedence {
EnterFault,
HoldFault,
ReenterFault,
RecoverToNormal,
RunAlgorithm,
}
pub(crate) fn fault_precedence(
reliability: Reliability,
fault_reliability: Option<Reliability>,
current: EventState,
) -> FaultPrecedence {
let faulted = reliability != Reliability::NO_FAULT_DETECTED;
match (faulted, current == EventState::FAULT, fault_reliability) {
(true, false, _) => FaultPrecedence::EnterFault,
(true, true, Some(previous)) if previous == reliability => FaultPrecedence::HoldFault,
(true, true, Some(_)) => FaultPrecedence::ReenterFault,
(true, true, None) => FaultPrecedence::ReenterFault,
(false, true, _) => FaultPrecedence::RecoverToNormal,
(false, false, _) => FaultPrecedence::RunAlgorithm,
}
}
#[derive(Debug, Clone)]
pub struct OutOfRangeDetector {
pub high_limit: f32,
pub low_limit: f32,
pub deadband: f32,
pub limit_enable: LimitEnable,
pub notification_class: u32,
pub notify_type: NotifyType,
pub event_enable: EventTransitionBits,
pub time_delay: u32,
pub time_delay_normal: Option<u32>,
pub event_state: EventState,
pub acked_transitions: EventTransitionBits,
pub pending: Option<PendingTransition>,
pub fault_reliability: Option<Reliability>,
}
impl Default for OutOfRangeDetector {
fn default() -> Self {
Self {
high_limit: 100.0,
low_limit: 0.0,
deadband: 1.0,
limit_enable: LimitEnable::empty(),
notification_class: 0,
notify_type: NotifyType::ALARM,
event_enable: EventTransitionBits::empty(),
time_delay: 0,
time_delay_normal: None,
event_state: EventState::NORMAL,
acked_transitions: EventTransitionBits::all(), pending: None,
fault_reliability: None,
}
}
}
impl OutOfRangeDetector {
pub const ALGORITHM: EventType = EventType::OUT_OF_RANGE;
pub fn evaluate(
&mut self,
present_value: f32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
self.probe(present_value, reliability)
}
fn fault_proposal(&self, reliability: Reliability) -> ControlFlow<Option<TransitionOutcome>> {
match fault_precedence(reliability, self.fault_reliability, self.event_state) {
FaultPrecedence::EnterFault | FaultPrecedence::ReenterFault => {
ControlFlow::Break(self.proposal(EventState::FAULT))
}
FaultPrecedence::HoldFault => ControlFlow::Break(None),
FaultPrecedence::RecoverToNormal => {
ControlFlow::Break(self.proposal(EventState::NORMAL))
}
FaultPrecedence::RunAlgorithm => ControlFlow::Continue(()),
}
}
pub fn probe(
&mut self,
present_value: f32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
let outcome = self.propose(present_value, reliability, false);
if let Some(ref outcome) = outcome {
self.confirm_transition(&outcome.change, reliability);
}
outcome
}
pub(crate) fn propose(
&mut self,
present_value: f32,
reliability: Reliability,
inhibited: bool,
) -> Option<TransitionOutcome> {
if let ControlFlow::Break(result) = self.fault_proposal(reliability) {
return result;
}
if inhibited {
return inhibited_target(self.event_state, &mut self.pending)
.and_then(|target| self.proposal(target));
}
let desired = self.compute_new_state(present_value);
if desired == self.event_state {
self.pending = None;
return None;
}
let delay = delay_toward(self.time_delay, self.time_delay_normal, desired);
if delay == 0 {
return self.proposal(desired);
}
if self.pending.as_ref().is_none_or(|p| p.state != desired) {
self.pending = Some(PendingTransition::seed(desired, delay));
}
None
}
pub fn tick(
&mut self,
present_value: f32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
let outcome = self.tick_proposal(present_value, reliability, false);
if let Some(ref outcome) = outcome {
self.confirm_transition(&outcome.change, reliability);
}
outcome
}
pub(crate) fn tick_proposal(
&mut self,
present_value: f32,
reliability: Reliability,
inhibited: bool,
) -> Option<TransitionOutcome> {
if let ControlFlow::Break(result) = self.fault_proposal(reliability) {
return result;
}
if inhibited {
return inhibited_target(self.event_state, &mut self.pending)
.and_then(|target| self.proposal(target));
}
let desired = self.compute_new_state(present_value);
if desired == self.event_state {
self.pending = None;
return None;
}
match &mut self.pending {
Some(p) if p.state == desired => {
if p.remaining > 1 {
p.remaining -= 1;
return None;
}
self.proposal(desired)
}
_ => {
let delay = delay_toward(self.time_delay, self.time_delay_normal, desired);
self.pending = Some(PendingTransition::seed(desired, delay));
None
}
}
}
fn proposal(&self, new_state: EventState) -> Option<TransitionOutcome> {
let change = EventStateChange {
from: self.event_state,
to: new_state,
};
let transition_bit = EventTransition::for_target_state(new_state).bit_mask();
let distribute = self.event_enable.contains(transition_bit);
let event_type = change.event_type(Self::ALGORITHM);
Some(TransitionOutcome {
change,
event_type,
distribute,
})
}
pub(crate) fn confirm_transition(
&mut self,
change: &EventStateChange,
reliability: Reliability,
) {
self.event_state = change.to;
self.pending = None;
self.fault_reliability = if change.to == EventState::FAULT {
Some(reliability)
} else {
None
};
}
fn compute_new_state(&self, pv: f32) -> EventState {
let high_enabled = self.limit_enable.contains(LimitEnable::HIGH_LIMIT_ENABLE);
let low_enabled = self.limit_enable.contains(LimitEnable::LOW_LIMIT_ENABLE);
match self.event_state {
s if s == EventState::NORMAL => {
if high_enabled && pv > self.high_limit {
return EventState::HIGH_LIMIT;
}
if low_enabled && pv < self.low_limit {
return EventState::LOW_LIMIT;
}
EventState::NORMAL
}
s if s == EventState::HIGH_LIMIT => {
if low_enabled && pv < self.low_limit {
return EventState::LOW_LIMIT;
}
if pv < self.high_limit - self.deadband {
return EventState::NORMAL;
}
EventState::HIGH_LIMIT
}
s if s == EventState::LOW_LIMIT => {
if high_enabled && pv > self.high_limit {
return EventState::HIGH_LIMIT;
}
if pv > self.low_limit + self.deadband {
return EventState::NORMAL;
}
EventState::LOW_LIMIT
}
_ => self.event_state, }
}
}
#[derive(Debug, Clone)]
pub struct ChangeOfStateDetector {
pub alarm_values: Vec<u32>,
pub notification_class: u32,
pub notify_type: NotifyType,
pub event_enable: EventTransitionBits,
pub time_delay: u32,
pub time_delay_normal: Option<u32>,
pub event_state: EventState,
pub acked_transitions: EventTransitionBits,
pub pending: Option<PendingTransition>,
pub fault_reliability: Option<Reliability>,
}
impl Default for ChangeOfStateDetector {
fn default() -> Self {
Self {
alarm_values: Vec::new(),
notification_class: 0,
notify_type: NotifyType::ALARM,
event_enable: EventTransitionBits::empty(),
time_delay: 0,
time_delay_normal: None,
event_state: EventState::NORMAL,
acked_transitions: EventTransitionBits::all(),
pending: None,
fault_reliability: None,
}
}
}
impl ChangeOfStateDetector {
pub const ALGORITHM: EventType = EventType::CHANGE_OF_STATE;
pub fn evaluate(
&mut self,
present_value: u32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
self.probe(present_value, reliability)
}
fn fault_proposal(&self, reliability: Reliability) -> ControlFlow<Option<TransitionOutcome>> {
match fault_precedence(reliability, self.fault_reliability, self.event_state) {
FaultPrecedence::EnterFault | FaultPrecedence::ReenterFault => {
ControlFlow::Break(self.proposal(EventState::FAULT))
}
FaultPrecedence::HoldFault => ControlFlow::Break(None),
FaultPrecedence::RecoverToNormal => {
ControlFlow::Break(self.proposal(EventState::NORMAL))
}
FaultPrecedence::RunAlgorithm => ControlFlow::Continue(()),
}
}
pub fn probe(
&mut self,
present_value: u32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
let outcome = self.propose(present_value, reliability, false);
if let Some(ref outcome) = outcome {
self.confirm_transition(&outcome.change, reliability);
}
outcome
}
pub(crate) fn propose(
&mut self,
present_value: u32,
reliability: Reliability,
inhibited: bool,
) -> Option<TransitionOutcome> {
if let ControlFlow::Break(result) = self.fault_proposal(reliability) {
return result;
}
if inhibited {
return inhibited_target(self.event_state, &mut self.pending)
.and_then(|target| self.proposal(target));
}
let desired = self.compute_new_state(present_value);
if desired == self.event_state {
self.pending = None;
return None;
}
let delay = delay_toward(self.time_delay, self.time_delay_normal, desired);
if delay == 0 {
return self.proposal(desired);
}
if self.pending.as_ref().is_none_or(|p| p.state != desired) {
self.pending = Some(PendingTransition::seed(desired, delay));
}
None
}
pub fn tick(
&mut self,
present_value: u32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
let outcome = self.tick_proposal(present_value, reliability, false);
if let Some(ref outcome) = outcome {
self.confirm_transition(&outcome.change, reliability);
}
outcome
}
pub(crate) fn tick_proposal(
&mut self,
present_value: u32,
reliability: Reliability,
inhibited: bool,
) -> Option<TransitionOutcome> {
if let ControlFlow::Break(result) = self.fault_proposal(reliability) {
return result;
}
if inhibited {
return inhibited_target(self.event_state, &mut self.pending)
.and_then(|target| self.proposal(target));
}
let desired = self.compute_new_state(present_value);
if desired == self.event_state {
self.pending = None;
return None;
}
match &mut self.pending {
Some(p) if p.state == desired => {
if p.remaining > 1 {
p.remaining -= 1;
return None;
}
self.proposal(desired)
}
_ => {
let delay = delay_toward(self.time_delay, self.time_delay_normal, desired);
self.pending = Some(PendingTransition::seed(desired, delay));
None
}
}
}
fn proposal(&self, new_state: EventState) -> Option<TransitionOutcome> {
let change = EventStateChange {
from: self.event_state,
to: new_state,
};
let transition_bit = EventTransition::for_target_state(new_state).bit_mask();
let distribute = self.event_enable.contains(transition_bit);
let event_type = change.event_type(Self::ALGORITHM);
Some(TransitionOutcome {
change,
event_type,
distribute,
})
}
pub(crate) fn confirm_transition(
&mut self,
change: &EventStateChange,
reliability: Reliability,
) {
self.event_state = change.to;
self.pending = None;
self.fault_reliability = if change.to == EventState::FAULT {
Some(reliability)
} else {
None
};
}
fn compute_new_state(&self, present_value: u32) -> EventState {
if self.alarm_values.contains(&present_value) {
EventState::OFFNORMAL
} else {
EventState::NORMAL
}
}
}
#[derive(Debug, Clone)]
pub struct CommandFailureDetector {
pub notification_class: u32,
pub notify_type: NotifyType,
pub event_enable: EventTransitionBits,
pub time_delay: u32,
pub time_delay_normal: Option<u32>,
pub event_state: EventState,
pub acked_transitions: EventTransitionBits,
pub pending: Option<PendingTransition>,
pub fault_reliability: Option<Reliability>,
}
impl Default for CommandFailureDetector {
fn default() -> Self {
Self {
notification_class: 0,
notify_type: NotifyType::ALARM,
event_enable: EventTransitionBits::empty(),
time_delay: 0,
time_delay_normal: None,
event_state: EventState::NORMAL,
acked_transitions: EventTransitionBits::all(),
pending: None,
fault_reliability: None,
}
}
}
impl CommandFailureDetector {
pub const ALGORITHM: EventType = EventType::COMMAND_FAILURE;
pub fn evaluate(
&mut self,
present_value: u32,
feedback_value: u32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
self.probe(present_value, feedback_value, reliability)
}
fn fault_proposal(&self, reliability: Reliability) -> ControlFlow<Option<TransitionOutcome>> {
match fault_precedence(reliability, self.fault_reliability, self.event_state) {
FaultPrecedence::EnterFault | FaultPrecedence::ReenterFault => {
ControlFlow::Break(self.proposal(EventState::FAULT))
}
FaultPrecedence::HoldFault => ControlFlow::Break(None),
FaultPrecedence::RecoverToNormal => {
ControlFlow::Break(self.proposal(EventState::NORMAL))
}
FaultPrecedence::RunAlgorithm => ControlFlow::Continue(()),
}
}
pub fn probe(
&mut self,
present_value: u32,
feedback_value: u32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
let outcome = self.propose(present_value, feedback_value, reliability, false);
if let Some(ref outcome) = outcome {
self.confirm_transition(&outcome.change, reliability);
}
outcome
}
pub(crate) fn propose(
&mut self,
present_value: u32,
feedback_value: u32,
reliability: Reliability,
inhibited: bool,
) -> Option<TransitionOutcome> {
if let ControlFlow::Break(result) = self.fault_proposal(reliability) {
return result;
}
if inhibited {
return inhibited_target(self.event_state, &mut self.pending)
.and_then(|target| self.proposal(target));
}
let desired = self.compute_new_state(present_value, feedback_value);
if desired == self.event_state {
self.pending = None;
return None;
}
let delay = delay_toward(self.time_delay, self.time_delay_normal, desired);
if delay == 0 {
return self.proposal(desired);
}
if self.pending.as_ref().is_none_or(|p| p.state != desired) {
self.pending = Some(PendingTransition::seed(desired, delay));
}
None
}
pub fn tick(
&mut self,
present_value: u32,
feedback_value: u32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
let outcome = self.tick_proposal(present_value, feedback_value, reliability, false);
if let Some(ref outcome) = outcome {
self.confirm_transition(&outcome.change, reliability);
}
outcome
}
pub(crate) fn tick_proposal(
&mut self,
present_value: u32,
feedback_value: u32,
reliability: Reliability,
inhibited: bool,
) -> Option<TransitionOutcome> {
if let ControlFlow::Break(result) = self.fault_proposal(reliability) {
return result;
}
if inhibited {
return inhibited_target(self.event_state, &mut self.pending)
.and_then(|target| self.proposal(target));
}
let desired = self.compute_new_state(present_value, feedback_value);
if desired == self.event_state {
self.pending = None;
return None;
}
match &mut self.pending {
Some(p) if p.state == desired => {
if p.remaining > 1 {
p.remaining -= 1;
return None;
}
self.proposal(desired)
}
_ => {
let delay = delay_toward(self.time_delay, self.time_delay_normal, desired);
self.pending = Some(PendingTransition::seed(desired, delay));
None
}
}
}
fn proposal(&self, new_state: EventState) -> Option<TransitionOutcome> {
let change = EventStateChange {
from: self.event_state,
to: new_state,
};
let transition_bit = EventTransition::for_target_state(new_state).bit_mask();
let distribute = self.event_enable.contains(transition_bit);
let event_type = change.event_type(Self::ALGORITHM);
Some(TransitionOutcome {
change,
event_type,
distribute,
})
}
pub(crate) fn confirm_transition(
&mut self,
change: &EventStateChange,
reliability: Reliability,
) {
self.event_state = change.to;
self.pending = None;
self.fault_reliability = if change.to == EventState::FAULT {
Some(reliability)
} else {
None
};
}
fn compute_new_state(&self, present_value: u32, feedback_value: u32) -> EventState {
if present_value != feedback_value {
EventState::OFFNORMAL
} else {
EventState::NORMAL
}
}
}
#[cfg(test)]
pub(crate) use history::commit_test_proposal;
pub(crate) use history::impl_builtin_intrinsic_reporting;
pub(crate) use state_reporting::impl_change_of_state_reporting;
#[cfg(test)]
mod fault_tests;
#[cfg(test)]
mod inhibit_tests;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod time_delay_normal_tests;