use std::collections::HashSet;
use std::future::Future;
use bacnet_services::alarm_event::{EventSummary, GetEventInformationAck};
use bacnet_services::alarm_summary::AlarmSummaryEntry;
use bacnet_services::enrollment_summary::EnrollmentSummaryEntry;
use bacnet_types::bitstring::EventTransitionBits;
use bacnet_types::enums::{ObjectType, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier, PropertyValue};
use serde::Serialize;
use crate::label::object_label;
use super::{Datum, Floats, StateTexts, ValueFormat, constructed};
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Transitions<T> {
pub to_offnormal: T,
pub to_fault: T,
pub to_normal: T,
}
impl<T> Transitions<T> {
fn from_array([to_offnormal, to_fault, to_normal]: [T; 3]) -> Self {
Self {
to_offnormal,
to_fault,
to_normal,
}
}
fn as_array(&self) -> [&T; 3] {
[&self.to_offnormal, &self.to_fault, &self.to_normal]
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct EventSummaryData {
pub object: Datum,
pub event_state: Datum,
pub acknowledged_transitions: Datum,
pub event_enable: Datum,
pub notify_type: Datum,
pub event_timestamps: Transitions<Datum>,
pub event_priorities: Transitions<Datum>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct EventInformationData {
pub summaries: Vec<EventSummaryData>,
pub more_events: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct AlarmSummaryData {
pub object: Datum,
pub alarm_state: Datum,
pub acknowledged_transitions: Datum,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct EnrollmentSummaryData {
pub object: Datum,
pub event_type: Datum,
pub event_state: Datum,
pub priority: Datum,
pub notification_class: Datum,
}
fn named<R>(f: impl FnOnce(&ValueFormat<'_>) -> R) -> R {
let texts = StateTexts::default();
f(&ValueFormat::new(ObjectType::DEVICE, &texts, Floats::Exact))
}
fn object(format: &ValueFormat<'_>, object: ObjectIdentifier) -> Datum {
format.element_data(
PropertyIdentifier::OBJECT_IDENTIFIER,
&PropertyValue::ObjectIdentifier(object),
)
}
fn enumerated(format: &ValueFormat<'_>, property: PropertyIdentifier, raw: u32) -> Datum {
format.element_data(property, &PropertyValue::Enumerated(raw))
}
fn transitions(
format: &ValueFormat<'_>,
property: PropertyIdentifier,
bits: EventTransitionBits,
) -> Datum {
let value = PropertyValue::BitString {
unused_bits: 5,
data: vec![bits.to_bacnet()],
};
format.element_data(property, &value)
}
pub(super) fn timestamp(stamp: &BACnetTimeStamp) -> Datum {
Datum::Constructed {
element: "time-stamp".into(),
text: constructed::timestamp_text(stamp),
fields: None,
}
}
impl EventSummaryData {
pub const HEADERS: [&'static str; 9] = [
"Object",
"State",
"Acked",
"Enabled",
"Notify",
"To-offnormal",
"To-fault",
"To-normal",
"Priorities",
];
pub fn new(summary: &EventSummary) -> Self {
named(|format| Self {
object: object(format, summary.object_identifier),
event_state: enumerated(
format,
PropertyIdentifier::EVENT_STATE,
summary.event_state.to_raw(),
),
acknowledged_transitions: transitions(
format,
PropertyIdentifier::ACKED_TRANSITIONS,
summary.acknowledged_transitions,
),
event_enable: transitions(
format,
PropertyIdentifier::EVENT_ENABLE,
summary.event_enable,
),
notify_type: enumerated(
format,
PropertyIdentifier::NOTIFY_TYPE,
summary.notify_type.to_raw(),
),
event_timestamps: Transitions::from_array(
summary.event_timestamps.each_ref().map(timestamp),
),
event_priorities: Transitions::from_array(
summary
.event_priorities
.map(|priority| Datum::Unsigned(u64::from(priority))),
),
})
}
pub fn cells(&self) -> Vec<String> {
let [offnormal, fault, normal] = self.event_timestamps.as_array().map(cell);
let priorities = self.event_priorities.as_array().map(cell).join("/");
vec![
cell(&self.object),
cell(&self.event_state),
cell(&self.acknowledged_transitions),
cell(&self.event_enable),
cell(&self.notify_type),
offnormal,
fault,
normal,
priorities,
]
}
}
impl EventInformationData {
pub fn new(ack: &GetEventInformationAck) -> Self {
Self::from_summaries(&ack.list_of_event_summaries, ack.more_events)
}
pub fn from_summaries(summaries: &[EventSummary], more_events: bool) -> Self {
Self {
summaries: summaries.iter().map(EventSummaryData::new).collect(),
more_events,
}
}
}
impl AlarmSummaryData {
pub const HEADERS: [&'static str; 3] = ["Object", "State", "Acked"];
pub fn new(entry: &AlarmSummaryEntry) -> Self {
named(|format| Self {
object: object(format, entry.object_identifier),
alarm_state: enumerated(
format,
PropertyIdentifier::EVENT_STATE,
entry.alarm_state.to_raw(),
),
acknowledged_transitions: transitions(
format,
PropertyIdentifier::ACKED_TRANSITIONS,
entry.acknowledged_transitions,
),
})
}
pub fn cells(&self) -> Vec<String> {
vec![
cell(&self.object),
cell(&self.alarm_state),
cell(&self.acknowledged_transitions),
]
}
}
impl EnrollmentSummaryData {
pub const HEADERS: [&'static str; 5] = [
"Object",
"Event type",
"State",
"Priority",
"Notification class",
];
pub fn new(entry: &EnrollmentSummaryEntry) -> Self {
named(|format| Self {
object: object(format, entry.object_identifier),
event_type: enumerated(
format,
PropertyIdentifier::EVENT_TYPE,
entry.event_type.to_raw(),
),
event_state: enumerated(
format,
PropertyIdentifier::EVENT_STATE,
entry.event_state.to_raw(),
),
priority: Datum::Unsigned(u64::from(entry.priority)),
notification_class: entry
.notification_class
.map_or(Datum::Null, |class| Datum::Unsigned(u64::from(class))),
})
}
pub fn cells(&self) -> Vec<String> {
vec![
cell(&self.object),
cell(&self.event_type),
cell(&self.event_state),
cell(&self.priority),
cell(&self.notification_class),
]
}
}
pub(crate) fn cell(datum: &Datum) -> String {
match datum {
Datum::Null => "-".into(),
Datum::Unsigned(n) => n.to_string(),
Datum::Object {
object_type,
instance,
..
} => format!("{object_type} {instance}"),
Datum::Enumerated { value, name } => name.clone().unwrap_or_else(|| value.to_string()),
Datum::Bits {
names: Some(names), ..
} if names.is_empty() => "none".into(),
Datum::Bits {
names: Some(names), ..
} => names.join(", "),
Datum::Bits { bits, .. } => format!("bits({bits})"),
Datum::Constructed { text, .. } => text.clone(),
Datum::Boolean(b) => b.to_string(),
Datum::Signed(n) => n.to_string(),
Datum::Real(v) => v.to_string(),
Datum::Double(v) => v.to_string(),
Datum::NonFinite { float } => float.clone(),
Datum::Text(text) => text.clone(),
Datum::Date { date } => date.clone(),
Datum::Time { time } => time.clone(),
Datum::Octets { octets } => format!("0x{octets}"),
Datum::Raw { raw } => format!("[raw {raw}]"),
Datum::List(items) => items.iter().map(cell).collect::<Vec<_>>().join(", "),
}
}
pub async fn read_event_information<F, Fut>(
max: usize,
fetch: F,
) -> Result<(Vec<EventSummary>, bool), Error>
where
F: FnMut(Option<ObjectIdentifier>) -> Fut,
Fut: Future<Output = Result<GetEventInformationAck, Error>>,
{
read_event_information_until(max, || false, fetch).await
}
pub async fn read_event_information_until<S, F, Fut>(
max: usize,
mut stop: S,
mut fetch: F,
) -> Result<(Vec<EventSummary>, bool), Error>
where
S: FnMut() -> bool,
F: FnMut(Option<ObjectIdentifier>) -> Fut,
Fut: Future<Output = Result<GetEventInformationAck, Error>>,
{
let mut summaries: Vec<EventSummary> = Vec::new();
let mut seen = HashSet::new();
let mut after = None;
loop {
if after.is_some() && stop() {
return Ok((summaries, true));
}
let ack = fetch(after).await?;
for summary in ack.list_of_event_summaries {
if !seen.insert(summary.object_identifier.encode()) {
return Err(Error::Encoding(format!(
"GetEventInformation named {} again",
object_label(summary.object_identifier)
)));
}
summaries.push(summary);
}
if summaries.len() >= max {
let more = ack.more_events || summaries.len() > max;
summaries.truncate(max);
return Ok((summaries, more));
}
if !ack.more_events {
return Ok((summaries, false));
}
match summaries.last().map(|summary| summary.object_identifier) {
Some(last) if after != Some(last) => after = Some(last),
_ => {
return Err(Error::decoding(
0,
"GetEventInformation said more events remain but sent no new object",
));
}
}
}
}
#[cfg(test)]
#[path = "alarms_tests.rs"]
mod tests;