1use std::collections::HashSet;
14use std::future::Future;
15
16use bacnet_services::alarm_event::{EventSummary, GetEventInformationAck};
17use bacnet_services::alarm_summary::AlarmSummaryEntry;
18use bacnet_services::enrollment_summary::EnrollmentSummaryEntry;
19use bacnet_types::bitstring::EventTransitionBits;
20use bacnet_types::enums::{ObjectType, PropertyIdentifier};
21use bacnet_types::error::Error;
22use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier, PropertyValue};
23use serde::Serialize;
24
25use crate::label::object_label;
26
27use super::{Datum, Floats, StateTexts, ValueFormat, constructed};
28
29#[derive(Debug, Clone, PartialEq, Serialize)]
32pub struct Transitions<T> {
33 pub to_offnormal: T,
35 pub to_fault: T,
37 pub to_normal: T,
39}
40
41impl<T> Transitions<T> {
42 fn from_array([to_offnormal, to_fault, to_normal]: [T; 3]) -> Self {
43 Self {
44 to_offnormal,
45 to_fault,
46 to_normal,
47 }
48 }
49
50 fn as_array(&self) -> [&T; 3] {
51 [&self.to_offnormal, &self.to_fault, &self.to_normal]
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize)]
57pub struct EventSummaryData {
58 pub object: Datum,
60 pub event_state: Datum,
62 pub acknowledged_transitions: Datum,
64 pub event_enable: Datum,
66 pub notify_type: Datum,
68 pub event_timestamps: Transitions<Datum>,
71 pub event_priorities: Transitions<Datum>,
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize)]
77pub struct EventInformationData {
78 pub summaries: Vec<EventSummaryData>,
80 pub more_events: bool,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize)]
87pub struct AlarmSummaryData {
88 pub object: Datum,
90 pub alarm_state: Datum,
92 pub acknowledged_transitions: Datum,
94}
95
96#[derive(Debug, Clone, PartialEq, Serialize)]
98pub struct EnrollmentSummaryData {
99 pub object: Datum,
101 pub event_type: Datum,
103 pub event_state: Datum,
105 pub priority: Datum,
107 pub notification_class: Datum,
110}
111
112fn named<R>(f: impl FnOnce(&ValueFormat<'_>) -> R) -> R {
115 let texts = StateTexts::default();
116 f(&ValueFormat::new(ObjectType::DEVICE, &texts, Floats::Exact))
117}
118
119fn object(format: &ValueFormat<'_>, object: ObjectIdentifier) -> Datum {
120 format.element_data(
121 PropertyIdentifier::OBJECT_IDENTIFIER,
122 &PropertyValue::ObjectIdentifier(object),
123 )
124}
125
126fn enumerated(format: &ValueFormat<'_>, property: PropertyIdentifier, raw: u32) -> Datum {
127 format.element_data(property, &PropertyValue::Enumerated(raw))
128}
129
130fn transitions(
132 format: &ValueFormat<'_>,
133 property: PropertyIdentifier,
134 bits: EventTransitionBits,
135) -> Datum {
136 let value = PropertyValue::BitString {
137 unused_bits: 5,
138 data: vec![bits.to_bacnet()],
139 };
140 format.element_data(property, &value)
141}
142
143pub(super) fn timestamp(stamp: &BACnetTimeStamp) -> Datum {
144 Datum::Constructed {
145 element: "time-stamp".into(),
146 text: constructed::timestamp_text(stamp),
147 fields: None,
148 }
149}
150
151impl EventSummaryData {
152 pub const HEADERS: [&'static str; 9] = [
154 "Object",
155 "State",
156 "Acked",
157 "Enabled",
158 "Notify",
159 "To-offnormal",
160 "To-fault",
161 "To-normal",
162 "Priorities",
163 ];
164
165 pub fn new(summary: &EventSummary) -> Self {
167 named(|format| Self {
168 object: object(format, summary.object_identifier),
169 event_state: enumerated(
170 format,
171 PropertyIdentifier::EVENT_STATE,
172 summary.event_state.to_raw(),
173 ),
174 acknowledged_transitions: transitions(
175 format,
176 PropertyIdentifier::ACKED_TRANSITIONS,
177 summary.acknowledged_transitions,
178 ),
179 event_enable: transitions(
180 format,
181 PropertyIdentifier::EVENT_ENABLE,
182 summary.event_enable,
183 ),
184 notify_type: enumerated(
185 format,
186 PropertyIdentifier::NOTIFY_TYPE,
187 summary.notify_type.to_raw(),
188 ),
189 event_timestamps: Transitions::from_array(
190 summary.event_timestamps.each_ref().map(timestamp),
191 ),
192 event_priorities: Transitions::from_array(
193 summary
194 .event_priorities
195 .map(|priority| Datum::Unsigned(u64::from(priority))),
196 ),
197 })
198 }
199
200 pub fn cells(&self) -> Vec<String> {
203 let [offnormal, fault, normal] = self.event_timestamps.as_array().map(cell);
204 let priorities = self.event_priorities.as_array().map(cell).join("/");
205 vec![
206 cell(&self.object),
207 cell(&self.event_state),
208 cell(&self.acknowledged_transitions),
209 cell(&self.event_enable),
210 cell(&self.notify_type),
211 offnormal,
212 fault,
213 normal,
214 priorities,
215 ]
216 }
217}
218
219impl EventInformationData {
220 pub fn new(ack: &GetEventInformationAck) -> Self {
222 Self::from_summaries(&ack.list_of_event_summaries, ack.more_events)
223 }
224
225 pub fn from_summaries(summaries: &[EventSummary], more_events: bool) -> Self {
228 Self {
229 summaries: summaries.iter().map(EventSummaryData::new).collect(),
230 more_events,
231 }
232 }
233}
234
235impl AlarmSummaryData {
236 pub const HEADERS: [&'static str; 3] = ["Object", "State", "Acked"];
238
239 pub fn new(entry: &AlarmSummaryEntry) -> Self {
241 named(|format| Self {
242 object: object(format, entry.object_identifier),
243 alarm_state: enumerated(
244 format,
245 PropertyIdentifier::EVENT_STATE,
246 entry.alarm_state.to_raw(),
247 ),
248 acknowledged_transitions: transitions(
249 format,
250 PropertyIdentifier::ACKED_TRANSITIONS,
251 entry.acknowledged_transitions,
252 ),
253 })
254 }
255
256 pub fn cells(&self) -> Vec<String> {
258 vec![
259 cell(&self.object),
260 cell(&self.alarm_state),
261 cell(&self.acknowledged_transitions),
262 ]
263 }
264}
265
266impl EnrollmentSummaryData {
267 pub const HEADERS: [&'static str; 5] = [
269 "Object",
270 "Event type",
271 "State",
272 "Priority",
273 "Notification class",
274 ];
275
276 pub fn new(entry: &EnrollmentSummaryEntry) -> Self {
278 named(|format| Self {
279 object: object(format, entry.object_identifier),
280 event_type: enumerated(
281 format,
282 PropertyIdentifier::EVENT_TYPE,
283 entry.event_type.to_raw(),
284 ),
285 event_state: enumerated(
286 format,
287 PropertyIdentifier::EVENT_STATE,
288 entry.event_state.to_raw(),
289 ),
290 priority: Datum::Unsigned(u64::from(entry.priority)),
291 notification_class: entry
292 .notification_class
293 .map_or(Datum::Null, |class| Datum::Unsigned(u64::from(class))),
294 })
295 }
296
297 pub fn cells(&self) -> Vec<String> {
300 vec![
301 cell(&self.object),
302 cell(&self.event_type),
303 cell(&self.event_state),
304 cell(&self.priority),
305 cell(&self.notification_class),
306 ]
307 }
308}
309
310pub(crate) fn cell(datum: &Datum) -> String {
316 match datum {
317 Datum::Null => "-".into(),
318 Datum::Unsigned(n) => n.to_string(),
319 Datum::Object {
320 object_type,
321 instance,
322 ..
323 } => format!("{object_type} {instance}"),
324 Datum::Enumerated { value, name } => name.clone().unwrap_or_else(|| value.to_string()),
325 Datum::Bits {
326 names: Some(names), ..
327 } if names.is_empty() => "none".into(),
328 Datum::Bits {
329 names: Some(names), ..
330 } => names.join(", "),
331 Datum::Bits { bits, .. } => format!("bits({bits})"),
332 Datum::Constructed { text, .. } => text.clone(),
333 Datum::Boolean(b) => b.to_string(),
334 Datum::Signed(n) => n.to_string(),
335 Datum::Real(v) => v.to_string(),
336 Datum::Double(v) => v.to_string(),
337 Datum::NonFinite { float } => float.clone(),
338 Datum::Text(text) => text.clone(),
339 Datum::Date { date } => date.clone(),
340 Datum::Time { time } => time.clone(),
341 Datum::Octets { octets } => format!("0x{octets}"),
342 Datum::Raw { raw } => format!("[raw {raw}]"),
343 Datum::List(items) => items.iter().map(cell).collect::<Vec<_>>().join(", "),
344 }
345}
346
347pub async fn read_event_information<F, Fut>(
356 max: usize,
357 fetch: F,
358) -> Result<(Vec<EventSummary>, bool), Error>
359where
360 F: FnMut(Option<ObjectIdentifier>) -> Fut,
361 Fut: Future<Output = Result<GetEventInformationAck, Error>>,
362{
363 read_event_information_until(max, || false, fetch).await
364}
365
366pub async fn read_event_information_until<S, F, Fut>(
371 max: usize,
372 mut stop: S,
373 mut fetch: F,
374) -> Result<(Vec<EventSummary>, bool), Error>
375where
376 S: FnMut() -> bool,
377 F: FnMut(Option<ObjectIdentifier>) -> Fut,
378 Fut: Future<Output = Result<GetEventInformationAck, Error>>,
379{
380 let mut summaries: Vec<EventSummary> = Vec::new();
381 let mut seen = HashSet::new();
382 let mut after = None;
383 loop {
384 if after.is_some() && stop() {
385 return Ok((summaries, true));
386 }
387 let ack = fetch(after).await?;
388 for summary in ack.list_of_event_summaries {
389 if !seen.insert(summary.object_identifier.encode()) {
390 return Err(Error::Encoding(format!(
391 "GetEventInformation named {} again",
392 object_label(summary.object_identifier)
393 )));
394 }
395 summaries.push(summary);
396 }
397 if summaries.len() >= max {
398 let more = ack.more_events || summaries.len() > max;
399 summaries.truncate(max);
400 return Ok((summaries, more));
401 }
402 if !ack.more_events {
403 return Ok((summaries, false));
404 }
405 match summaries.last().map(|summary| summary.object_identifier) {
408 Some(last) if after != Some(last) => after = Some(last),
409 _ => {
410 return Err(Error::decoding(
411 0,
412 "GetEventInformation said more events remain but sent no new object",
413 ));
414 }
415 }
416 }
417}
418
419#[cfg(test)]
420#[path = "alarms_tests.rs"]
421mod tests;