use bacnet_types::bitstring::EventTransitionBits;
use bacnet_types::enums::{ErrorClass, ErrorCode, EventState, PropertyIdentifier, Reliability};
use bacnet_types::error::Error;
use bacnet_types::primitives::{BACnetTimeStamp, PropertyValue};
use super::history::{EventHistory, EventTransitionState};
use super::options::ReportingOptions;
use super::{
ChangeOfStateDetector, EnrollmentSummaryCapability, EventStateChange, EventTransitionCommit,
EventTransitionCommitError, TransitionOutcome,
};
use crate::common::{self, read_generic_event_properties, write_generic_event_properties};
#[derive(Debug, Clone)]
pub(crate) struct ChangeOfStateReporting {
event_detector: ChangeOfStateDetector,
event_history: EventHistory,
event_detection_enable: bool,
states: ListedStates,
}
impl ChangeOfStateReporting {
pub(crate) fn new(states: ListedStates) -> Self {
Self {
event_detector: ChangeOfStateDetector::default(),
event_history: EventHistory::default(),
event_detection_enable: true,
states,
}
}
pub(crate) fn event_state(&self) -> EventState {
self.event_detector.event_state
}
pub(crate) fn alarm_values(&self) -> &[u32] {
&self.event_detector.alarm_values
}
pub(crate) fn set_alarm_values(&mut self, values: Vec<u32>) -> Result<(), Error> {
self.event_detector.alarm_values = checked_raw_list(values, self.states)?;
Ok(())
}
pub(crate) fn read(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Option<Result<PropertyValue, Error>> {
if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
return Some(Ok(PropertyValue::Boolean(self.event_detection_enable)));
}
if property == PropertyIdentifier::ALARM_VALUES {
return Some(Ok(enumerated_list_value(&self.event_detector.alarm_values)));
}
if let Some(result) = read_generic_event_properties!(self, property) {
return Some(result);
}
self.event_history.read(property, array_index)
}
pub(crate) fn write(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: &PropertyValue,
) -> Option<Result<(), Error>> {
if property == PropertyIdentifier::ALARM_VALUES {
return Some(
enumerated_list(array_index, value, self.states).map(|values| {
self.event_detector.alarm_values = values;
}),
);
}
if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
let PropertyValue::Boolean(enable) = *value else {
return Some(Err(common::invalid_data_type_error()));
};
self.event_detection_enable = enable;
if !enable {
self.reset();
}
return Some(Ok(()));
}
if let Some(result) =
self.event_history
.write(property, array_index, value, self.event_detection_enable)
{
return Some(result);
}
write_generic_event_properties!(self, property, value.clone())
}
fn reset(&mut self) {
self.event_detector.event_state = EventState::NORMAL;
self.event_detector.acked_transitions = EventTransitionBits::all();
self.event_detector.pending = None;
self.event_detector.fault_reliability = None;
self.event_history.reset();
}
pub(crate) fn propose(
&mut self,
watched: u32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
if !self.event_detection_enable {
return None;
}
let inhibited = self.event_history.options.inhibited();
self.event_detector.propose(watched, reliability, inhibited)
}
pub(crate) fn tick(
&mut self,
watched: u32,
reliability: Reliability,
) -> Option<TransitionOutcome> {
if !self.event_detection_enable {
return None;
}
let inhibited = self.event_history.options.inhibited();
self.event_detector
.tick_proposal(watched, reliability, inhibited)
}
pub(crate) fn commit(
&mut self,
commit: EventTransitionCommit,
reliability: Reliability,
) -> Result<(), EventTransitionCommitError> {
let change = commit.change.clone();
EventTransitionState::new(
&mut self.event_detector.event_state,
&mut self.event_detector.acked_transitions,
&mut self.event_history,
)
.commit(commit)?;
self.event_detector.confirm_transition(&change, reliability);
Ok(())
}
pub(crate) fn acknowledge(
&mut self,
event_state: EventState,
timestamp: &BACnetTimeStamp,
) -> Result<Option<EventStateChange>, Error> {
if !self.event_detection_enable {
return Err(Error::Protocol {
class: ErrorClass::OBJECT.to_raw() as u32,
code: ErrorCode::NO_ALARM_CONFIGURED.to_raw() as u32,
});
}
self.event_history.acknowledge_correlated_detailed(
&mut self.event_detector.acked_transitions,
event_state,
timestamp,
)
}
pub(crate) fn enrollment_summary(&self) -> EnrollmentSummaryCapability {
EnrollmentSummaryCapability {
event_type: ChangeOfStateDetector::ALGORITHM,
last_transition: self.event_history.last_transition(),
}
}
pub(crate) fn options(&mut self) -> &mut ReportingOptions {
&mut self.event_history.options
}
pub(crate) fn inhibit_reference(
&self,
) -> Option<bacnet_types::constructed::BACnetObjectPropertyReference> {
self.event_history.options.inhibit_reference().cloned()
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ListedStates {
pub(crate) normal: u32,
pub(crate) in_range: fn(u32) -> bool,
}
impl ListedStates {
pub(crate) fn admits(self, raw: u32) -> bool {
raw != self.normal && (self.in_range)(raw)
}
}
pub(crate) fn enumerated_list(
array_index: Option<u32>,
value: &PropertyValue,
states: ListedStates,
) -> Result<Vec<u32>, Error> {
if array_index.is_some() {
return Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::PROPERTY_IS_NOT_AN_ARRAY.to_raw() as u32,
});
}
let items = match value {
PropertyValue::List(items) => items.as_slice(),
single => std::slice::from_ref(single),
};
let cap = crate::multistate::MAX_ALARM_VALUES;
if items.len() > cap {
let full = Error::Protocol {
class: ErrorClass::RESOURCES.to_raw() as u32,
code: ErrorCode::NO_SPACE_TO_WRITE_PROPERTY.to_raw() as u32,
};
return Err(common::at_list_element(full, cap));
}
items
.iter()
.enumerate()
.map(|(index, item)| {
match *item {
PropertyValue::Enumerated(raw) if states.admits(raw) => Ok(raw),
PropertyValue::Enumerated(_) => Err(common::value_out_of_range_error()),
_ => Err(common::invalid_data_type_error()),
}
.map_err(|error| common::at_list_element(error, index))
})
.collect()
}
pub(crate) fn checked_raw_list(values: Vec<u32>, states: ListedStates) -> Result<Vec<u32>, Error> {
let values = values.into_iter().map(PropertyValue::Enumerated).collect();
enumerated_list(None, &PropertyValue::List(values), states)
}
pub(crate) fn enumerated_list_value(values: &[u32]) -> PropertyValue {
PropertyValue::List(
values
.iter()
.copied()
.map(PropertyValue::Enumerated)
.collect(),
)
}
macro_rules! impl_change_of_state_reporting {
($reporting:ident, $watched:path, $reliability:path) => {
fn enrollment_summary_capability_internal(
&self,
) -> Option<$crate::event::EnrollmentSummaryCapability> {
Some(self.$reporting.enrollment_summary())
}
fn evaluate_intrinsic_reporting(&mut self) -> Option<$crate::event::TransitionOutcome> {
let (watched, reliability) = ($watched(self), $reliability(self));
self.$reporting.propose(watched, reliability)
}
fn tick_intrinsic_reporting(&mut self) -> Option<$crate::event::TransitionOutcome> {
let (watched, reliability) = ($watched(self), $reliability(self));
self.$reporting.tick(watched, reliability)
}
fn commit_event_transition_internal(
&mut self,
commit: $crate::event::EventTransitionCommit,
) -> Result<(), $crate::event::EventTransitionCommitError> {
let reliability = $reliability(self);
self.$reporting.commit(commit, reliability)
}
fn acknowledge_alarm_correlated_internal(
&mut self,
event_state: bacnet_types::enums::EventState,
timestamp: &bacnet_types::primitives::BACnetTimeStamp,
) -> Result<(), bacnet_types::error::Error> {
self.$reporting
.acknowledge(event_state, timestamp)
.map(|_| ())
}
fn acknowledge_alarm_correlated_detailed_internal(
&mut self,
event_state: bacnet_types::enums::EventState,
timestamp: &bacnet_types::primitives::BACnetTimeStamp,
) -> Result<Option<$crate::event::EventStateChange>, bacnet_types::error::Error> {
self.$reporting.acknowledge(event_state, timestamp)
}
fn event_algorithm_inhibit_reference_internal(
&self,
) -> Option<bacnet_types::constructed::BACnetObjectPropertyReference> {
self.$reporting.inhibit_reference()
}
fn follow_event_algorithm_inhibit_internal(&mut self, inhibit: bool) -> bool {
self.$reporting.options().follow(inhibit)
}
};
}
pub(crate) use impl_change_of_state_reporting;