use std::borrow::Cow;
use bacnet_types::bitstring::EventTransitionBits;
use bacnet_types::enums::{EventState, NotifyType, ObjectType, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use crate::common;
use crate::event::history::EventHistory;
use crate::property_metadata::PropertyMetadata;
use crate::traits::BACnetObject;
use super::metadata;
pub struct AlertEnrollmentObject {
oid: ObjectIdentifier,
name: String,
description: String,
pub(super) event_state: EventState,
pub present_value: ObjectIdentifier,
pub event_detection_enable: bool,
pub(super) acked_transitions: EventTransitionBits,
pub(super) event_history: EventHistory,
pub event_enable: EventTransitionBits,
pub notification_class: u32,
notify_type: NotifyType,
}
impl AlertEnrollmentObject {
pub fn new(
instance: u32,
name: impl Into<String>,
initial_source: ObjectIdentifier,
) -> Result<Self, Error> {
let oid = ObjectIdentifier::new(ObjectType::ALERT_ENROLLMENT, instance)?;
Ok(Self {
oid,
name: name.into(),
description: String::new(),
event_state: EventState::NORMAL,
present_value: initial_source,
event_detection_enable: true,
acked_transitions: EventTransitionBits::all(),
event_history: EventHistory::default(),
event_enable: EventTransitionBits::all(),
notification_class: 0,
notify_type: NotifyType::ALARM,
})
}
pub fn record_alert_source(&mut self, source: ObjectIdentifier) {
self.present_value = source;
}
pub fn set_event_detection_enable(&mut self, enabled: bool) {
if !enabled || !self.event_detection_enable {
self.event_state = EventState::NORMAL;
self.acked_transitions = EventTransitionBits::all();
self.event_history.reset();
}
self.event_detection_enable = enabled;
}
fn read_alert_common_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Option<Result<PropertyValue, Error>> {
match property {
PropertyIdentifier::OBJECT_IDENTIFIER => {
Some(Ok(PropertyValue::ObjectIdentifier(self.oid)))
}
PropertyIdentifier::OBJECT_NAME => {
Some(Ok(PropertyValue::CharacterString(self.name.clone())))
}
PropertyIdentifier::DESCRIPTION => {
Some(Ok(PropertyValue::CharacterString(self.description.clone())))
}
PropertyIdentifier::PROPERTY_LIST => {
let properties = self.property_list();
Some(common::read_property_list_property(
&properties,
array_index,
))
}
_ => None,
}
}
}
impl BACnetObject for AlertEnrollmentObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
&self.name
}
fn read_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
if property == PropertyIdentifier::EVENT_TIME_STAMPS {
if !self.event_detection_enable {
return EventHistory::default()
.read(property, array_index)
.expect("EventHistory handles Event_Time_Stamps");
}
return self
.event_history
.read(property, array_index)
.expect("EventHistory handles Event_Time_Stamps");
}
if let Some(result) = self.read_alert_common_property(property, array_index) {
return result;
}
match property {
p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
ObjectType::ALERT_ENROLLMENT.to_raw(),
)),
p if p == PropertyIdentifier::PRESENT_VALUE => {
Ok(PropertyValue::ObjectIdentifier(self.present_value))
}
p if p == PropertyIdentifier::EVENT_DETECTION_ENABLE => {
Ok(PropertyValue::Boolean(self.event_detection_enable))
}
p if p == PropertyIdentifier::EVENT_ENABLE => Ok(PropertyValue::BitString {
unused_bits: 5,
data: vec![self.event_enable.to_bacnet()],
}),
p if p == PropertyIdentifier::NOTIFICATION_CLASS => {
Ok(PropertyValue::Unsigned(self.notification_class as u64))
}
p if p == PropertyIdentifier::NOTIFY_TYPE => {
Ok(PropertyValue::Enumerated(self.notify_type.to_raw()))
}
p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(
if self.event_detection_enable {
self.event_state
} else {
EventState::NORMAL
}
.to_raw(),
)),
p if p == PropertyIdentifier::ACKED_TRANSITIONS => Ok(PropertyValue::BitString {
unused_bits: 5,
data: vec![if self.event_detection_enable {
self.acked_transitions
} else {
EventTransitionBits::all()
}
.to_bacnet()],
}),
_ => Err(common::unknown_property_error()),
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
_array_index: Option<u32>,
value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
if property == PropertyIdentifier::EVENT_DETECTION_ENABLE {
if let PropertyValue::Boolean(v) = value {
self.set_event_detection_enable(v);
return Ok(());
}
return Err(common::invalid_data_type_error());
}
if property == PropertyIdentifier::EVENT_ENABLE {
if let PropertyValue::BitString { unused_bits, data } = &value {
let byte = common::check_fixed_width_bit_string(*unused_bits, data, 3)?;
self.event_enable = EventTransitionBits::from_bacnet(&[byte]);
return Ok(());
}
return Err(common::invalid_data_type_error());
}
if property == PropertyIdentifier::NOTIFICATION_CLASS {
if let PropertyValue::Unsigned(v) = value {
self.notification_class = common::u64_to_u32(v)?;
return Ok(());
}
return Err(common::invalid_data_type_error());
}
if property == PropertyIdentifier::NOTIFY_TYPE {
if let PropertyValue::Enumerated(v) = value {
let notify_type = NotifyType::from_raw(v);
if notify_type != NotifyType::ALARM && notify_type != NotifyType::EVENT {
return Err(common::value_out_of_range_error());
}
self.notify_type = notify_type;
return Ok(());
}
return Err(common::invalid_data_type_error());
}
if let Some(result) = common::write_description(&mut self.description, property, &value) {
return result;
}
Err(crate::common::unhandled_write_error(
self.property_metadata().as_ref(),
property,
_array_index,
))
}
fn set_event_state_internal(&mut self, state: EventState) -> Result<(), Error> {
if !self.event_detection_enable && state != EventState::NORMAL {
return Err(common::write_access_denied_error());
}
self.event_state = state;
Ok(())
}
fn set_acked_transitions_internal(
&mut self,
transition_bit: EventTransitionBits,
acknowledged: bool,
) -> Result<(), Error> {
if !self.event_detection_enable {
return Err(common::write_access_denied_error());
}
let transition_bit = transition_bit & EventTransitionBits::all();
if acknowledged {
self.acked_transitions.insert(transition_bit);
} else {
self.acked_transitions
.remove(transition_bit.difference(EventTransitionBits::TO_NORMAL));
}
self.acked_transitions
.insert(EventTransitionBits::TO_NORMAL);
Ok(())
}
fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
matches!(
property,
PropertyIdentifier::DESCRIPTION
| PropertyIdentifier::EVENT_DETECTION_ENABLE
| PropertyIdentifier::EVENT_ENABLE
| PropertyIdentifier::NOTIFICATION_CLASS
| PropertyIdentifier::NOTIFY_TYPE
)
}
fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
Cow::Borrowed(metadata::ALERT_ENROLLMENT_PROPERTIES)
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(metadata::ALERT_ENROLLMENT_PROPERTIES)
}
}