1use bacnet_encoding::constructed::{decode_authentication_factor, decode_bacnet_property_value};
8use bacnet_types::constructed::{
9 BACnetDeviceObjectReference, BACnetPropertyValue, ChangeOfValueChoice, NotificationParameters,
10};
11use bacnet_types::enums::{EventType, ObjectType};
12use bacnet_types::primitives::{Date, PropertyValue, Time};
13
14use super::commands::factor_text;
15use super::constructed::object_reference;
16use super::fields::{
17 FieldList, bits_value, encoded_value, named, property_state_value, reference_value,
18};
19use super::primitive::{date_time, hex};
20use super::{Datum, Fields, Floats, StateTexts, ValueFormat};
21use crate::label::kebab;
22
23const ELEMENT: &str = "notification-parameters";
25
26const MAX_PROPERTY_VALUES: usize = 256;
29
30#[derive(Debug, Clone, PartialEq)]
32pub struct EventValues {
33 pub event_type: String,
35 pub details: String,
38 pub data: Datum,
43}
44
45impl EventValues {
46 pub fn new(parameters: &NotificationParameters) -> Self {
48 Self::with_floats(parameters, Floats::Exact)
49 }
50
51 pub fn with_floats(parameters: &NotificationParameters, floats: Floats) -> Self {
53 let texts = StateTexts::default();
54 let format = ValueFormat::new(ObjectType::DEVICE, &texts, floats).whole_lists();
55 let (event_type, details, fields) = render(&format, parameters);
56 let text = if details.is_empty() {
57 event_type.clone()
58 } else {
59 format!("{event_type}: {details}")
60 };
61 Self {
62 event_type,
63 details,
64 data: Datum::Constructed {
65 element: ELEMENT.into(),
66 text,
67 fields: Some(fields),
68 },
69 }
70 }
71
72 pub fn text(&self) -> &str {
74 match &self.data {
75 Datum::Constructed { text, .. } => text,
76 _ => &self.details,
77 }
78 }
79}
80
81fn event_type(parameters: &NotificationParameters) -> u32 {
83 use NotificationParameters as N;
84 match parameters {
85 N::ChangeOfBitstring { .. } => 0,
86 N::ChangeOfState { .. } => 1,
87 N::ChangeOfValue { .. } => 2,
88 N::CommandFailure { .. } => 3,
89 N::FloatingLimit { .. } => 4,
90 N::OutOfRange { .. } => 5,
91 N::ComplexEventType { .. } => 6,
92 N::ChangeOfLifeSafety { .. } => 8,
93 N::Extended { .. } => 9,
94 N::BufferReady { .. } => 10,
95 N::UnsignedRange { .. } => 11,
96 N::AccessEvent { .. } => 13,
97 N::DoubleOutOfRange { .. } => 14,
98 N::SignedOutOfRange { .. } => 15,
99 N::UnsignedOutOfRange { .. } => 16,
100 N::ChangeOfCharacterstring { .. } => 17,
101 N::ChangeOfStatusFlags { .. } => 18,
102 N::ChangeOfReliability { .. } => 19,
103 N::ChangeOfDiscreteValue { .. } => 21,
104 N::ChangeOfTimer { .. } => 22,
105 }
106}
107
108fn enumerated(value: u32, name: impl std::fmt::Display) -> (String, Datum) {
110 let name = kebab(name);
111 (name.clone(), named(value, name))
112}
113
114fn date_and_time(date: &Date, time: &Time) -> (String, Datum) {
116 let text = date_time(date, time);
117 let datum = Datum::Constructed {
118 element: "date-time".into(),
119 text: text.clone(),
120 fields: None,
121 };
122 (text, datum)
123}
124
125fn object(reference: &BACnetDeviceObjectReference) -> (String, Datum) {
127 let text = object_reference(reference);
128 let datum = Datum::Constructed {
129 element: "device-object-reference".into(),
130 text: text.clone(),
131 fields: None,
132 };
133 (text, datum)
134}
135
136fn property_value(format: &ValueFormat<'_>, value: &BACnetPropertyValue) -> (String, Datum) {
139 let (shown, datum) = encoded_value(format, &value.value);
140 let mut property = kebab(value.property_identifier);
141 if let Some(index) = value.property_array_index {
142 property.push_str(&format!("[{index}]"));
143 }
144 let mut text = format!("{property} = {shown}");
145 if let Some(priority) = value.priority {
146 text.push_str(&format!(" @{priority}"));
147 }
148 let fields = Fields::new(vec![
149 (
150 "property_identifier".into(),
151 named(
152 value.property_identifier.to_raw(),
153 kebab(value.property_identifier),
154 ),
155 ),
156 (
157 "property_array_index".into(),
158 value
159 .property_array_index
160 .map_or(Datum::Null, |index| Datum::Unsigned(u64::from(index))),
161 ),
162 ("value".into(), datum),
163 (
164 "priority".into(),
165 value
166 .priority
167 .map_or(Datum::Null, |priority| Datum::Unsigned(u64::from(priority))),
168 ),
169 ]);
170 let datum = Datum::Constructed {
171 element: "property-value".into(),
172 text: text.clone(),
173 fields: Some(fields),
174 };
175 (text, datum)
176}
177
178fn property_values(format: &ValueFormat<'_>, octets: &[u8]) -> Option<Vec<(String, Datum)>> {
181 let mut values = Vec::new();
182 let mut offset = 0;
183 while offset < octets.len() {
184 if values.len() >= MAX_PROPERTY_VALUES {
185 return None;
186 }
187 let (value, next) = decode_bacnet_property_value(octets, offset).ok()?;
188 values.push(property_value(format, &value));
189 offset = next;
190 }
191 Some(values)
192}
193
194fn render(
196 format: &ValueFormat<'_>,
197 parameters: &NotificationParameters,
198) -> (String, String, Fields) {
199 use NotificationParameters as N;
200 let raw = event_type(parameters);
201 let name = match parameters {
204 N::ComplexEventType { .. } => "complex-event-type".to_string(),
205 _ => kebab(EventType::from_raw(raw)),
206 };
207 let mut f = FieldList::new(format);
208 f.quiet("event_type", named(raw, name.clone()));
209 match parameters {
210 N::ChangeOfBitstring {
211 referenced_bitstring,
212 status_flags,
213 } => {
214 let (unused, data) = referenced_bitstring;
215 f.labelled("referenced_bitstring", "bits", bits_value(*unused, data));
216 f.status_flags("status_flags", "flags", *status_flags);
217 }
218 N::ChangeOfState {
219 new_state,
220 status_flags,
221 } => {
222 f.labelled("new_state", "state", property_state_value(new_state));
223 f.status_flags("status_flags", "flags", *status_flags);
224 }
225 N::ChangeOfValue {
226 new_value,
227 status_flags,
228 } => {
229 match new_value {
230 ChangeOfValueChoice::ChangedBits { unused_bits, data } => {
231 f.labelled(
232 "changed_bits",
233 "changed bits",
234 bits_value(*unused_bits, data),
235 );
236 }
237 ChangeOfValueChoice::ChangedValue(value) => {
238 f.real("changed_value", "value", *value);
239 }
240 }
241 f.status_flags("status_flags", "flags", *status_flags);
242 }
243 N::CommandFailure {
244 command_value,
245 status_flags,
246 feedback_value,
247 } => {
248 f.labelled(
249 "command_value",
250 "commanded",
251 encoded_value(format, command_value),
252 );
253 f.status_flags("status_flags", "flags", *status_flags);
254 f.labelled(
255 "feedback_value",
256 "feedback",
257 encoded_value(format, feedback_value),
258 );
259 }
260 N::FloatingLimit {
261 reference_value,
262 status_flags,
263 setpoint_value,
264 error_limit,
265 } => {
266 f.real("reference_value", "value", *reference_value);
267 f.status_flags("status_flags", "flags", *status_flags);
268 f.real("setpoint_value", "setpoint", *setpoint_value);
269 f.real("error_limit", "error limit", *error_limit);
270 }
271 N::OutOfRange {
272 exceeding_value,
273 status_flags,
274 deadband,
275 exceeded_limit,
276 } => {
277 f.real("exceeding_value", "exceeding value", *exceeding_value);
278 f.status_flags("status_flags", "flags", *status_flags);
279 f.real("deadband", "deadband", *deadband);
280 f.real("exceeded_limit", "limit", *exceeded_limit);
281 }
282 N::ComplexEventType { property_values } => {
283 let values = property_values
284 .iter()
285 .map(|value| property_value(format, value))
286 .collect();
287 f.list("property_values", "values", values);
288 }
289 N::ChangeOfLifeSafety {
290 new_state,
291 new_mode,
292 status_flags,
293 operation_expected,
294 } => {
295 f.labelled(
296 "new_state",
297 "state",
298 enumerated(new_state.to_raw(), new_state),
299 );
300 f.labelled("new_mode", "mode", enumerated(new_mode.to_raw(), new_mode));
301 f.status_flags("status_flags", "flags", *status_flags);
302 f.labelled(
303 "operation_expected",
304 "operation",
305 enumerated(operation_expected.to_raw(), operation_expected),
306 );
307 }
308 N::Extended {
309 vendor_id,
310 extended_event_type,
311 parameters,
312 } => {
313 f.quiet("vendor_id", Datum::Unsigned(u64::from(*vendor_id)));
314 f.field(
315 "extended_event_type",
316 format!("vendor {vendor_id} type {extended_event_type}"),
317 Datum::Unsigned(u64::from(*extended_event_type)),
318 );
319 f.labelled(
320 "parameters",
321 "parameters",
322 encoded_value(format, parameters),
323 );
324 }
325 N::BufferReady {
326 buffer_property,
327 previous_notification,
328 current_notification,
329 } => {
330 f.labelled(
331 "buffer_property",
332 "buffer",
333 reference_value(buffer_property),
334 );
335 f.unsigned(
336 "previous_notification",
337 "previous",
338 u64::from(*previous_notification),
339 );
340 f.unsigned(
341 "current_notification",
342 "current",
343 u64::from(*current_notification),
344 );
345 }
346 N::UnsignedRange {
347 exceeding_value,
348 status_flags,
349 exceeded_limit,
350 } => {
351 f.unsigned("exceeding_value", "exceeding value", *exceeding_value);
352 f.status_flags("status_flags", "flags", *status_flags);
353 f.unsigned("exceeded_limit", "limit", *exceeded_limit);
354 }
355 N::AccessEvent {
356 access_event,
357 status_flags,
358 access_event_tag,
359 access_event_time,
360 access_credential,
361 authentication_factor,
362 } => {
363 f.labelled(
364 "access_event",
365 "event",
366 enumerated(access_event.to_raw(), access_event),
367 );
368 f.status_flags("status_flags", "flags", *status_flags);
369 f.unsigned("access_event_tag", "tag", u64::from(*access_event_tag));
370 let (date, time) = access_event_time;
371 f.labelled("access_event_time", "time", date_and_time(date, time));
372 f.labelled("access_credential", "credential", object(access_credential));
373 match authentication_factor {
374 Some(octets) => {
375 let pair = match decode_authentication_factor(octets, 0) {
376 Ok((factor, end)) if end == octets.len() => {
377 let text = factor_text(&factor);
378 let datum = Datum::Constructed {
379 element: "authentication-factor".into(),
380 text: text.clone(),
381 fields: None,
382 };
383 (text, datum)
384 }
385 _ => encoded_value(format, octets),
386 };
387 f.labelled("authentication_factor", "factor", pair);
388 }
389 None => f.quiet("authentication_factor", Datum::Null),
390 }
391 }
392 N::DoubleOutOfRange {
393 exceeding_value,
394 status_flags,
395 deadband,
396 exceeded_limit,
397 } => {
398 f.double("exceeding_value", "exceeding value", *exceeding_value);
399 f.status_flags("status_flags", "flags", *status_flags);
400 f.double("deadband", "deadband", *deadband);
401 f.double("exceeded_limit", "limit", *exceeded_limit);
402 }
403 N::SignedOutOfRange {
404 exceeding_value,
405 status_flags,
406 deadband,
407 exceeded_limit,
408 } => {
409 f.signed("exceeding_value", "exceeding value", *exceeding_value);
410 f.status_flags("status_flags", "flags", *status_flags);
411 f.unsigned("deadband", "deadband", *deadband);
412 f.signed("exceeded_limit", "limit", *exceeded_limit);
413 }
414 N::UnsignedOutOfRange {
415 exceeding_value,
416 status_flags,
417 deadband,
418 exceeded_limit,
419 } => {
420 f.unsigned("exceeding_value", "exceeding value", *exceeding_value);
421 f.status_flags("status_flags", "flags", *status_flags);
422 f.unsigned("deadband", "deadband", *deadband);
423 f.unsigned("exceeded_limit", "limit", *exceeded_limit);
424 }
425 N::ChangeOfCharacterstring {
426 changed_value,
427 status_flags,
428 alarm_value,
429 } => {
430 f.value(
431 "changed_value",
432 "value",
433 &PropertyValue::CharacterString(changed_value.clone()),
434 );
435 f.status_flags("status_flags", "flags", *status_flags);
436 f.value(
437 "alarm_value",
438 "alarm value",
439 &PropertyValue::CharacterString(alarm_value.clone()),
440 );
441 }
442 N::ChangeOfStatusFlags {
443 present_value,
444 referenced_flags,
445 } => {
446 match present_value {
447 Some(octets) => f.labelled("present_value", "value", encoded_value(format, octets)),
448 None => f.quiet("present_value", Datum::Null),
449 }
450 f.status_flags("referenced_flags", "flags", *referenced_flags);
451 }
452 N::ChangeOfReliability {
453 reliability,
454 status_flags,
455 property_values: octets,
456 } => {
457 f.labelled(
458 "reliability",
459 "reliability",
460 enumerated(reliability.to_raw(), reliability),
461 );
462 f.status_flags("status_flags", "flags", *status_flags);
463 match property_values(format, octets) {
464 Some(values) => f.list("property_values", "values", values),
465 None => f.field(
466 "property_values",
467 format!("values {}", super::raw_text(octets)),
468 Datum::Raw {
469 raw: hex(octets, ""),
470 },
471 ),
472 }
473 }
474 N::ChangeOfDiscreteValue {
475 new_value,
476 status_flags,
477 } => {
478 f.labelled("new_value", "value", encoded_value(format, new_value));
479 f.status_flags("status_flags", "flags", *status_flags);
480 }
481 N::ChangeOfTimer {
482 new_state,
483 status_flags,
484 update_time,
485 last_state_change,
486 initial_timeout,
487 expiration_time,
488 } => {
489 f.labelled(
490 "new_state",
491 "state",
492 enumerated(new_state.to_raw(), new_state),
493 );
494 f.status_flags("status_flags", "flags", *status_flags);
495 let (date, time) = update_time;
496 f.labelled("update_time", "updated", date_and_time(date, time));
497 match last_state_change {
498 Some(change) => f.labelled(
499 "last_state_change",
500 "last change",
501 enumerated(change.to_raw(), change),
502 ),
503 None => f.quiet("last_state_change", Datum::Null),
504 }
505 match initial_timeout {
506 Some(seconds) => f.field(
507 "initial_timeout",
508 format!("timeout {seconds} s"),
509 Datum::Unsigned(u64::from(*seconds)),
510 ),
511 None => f.quiet("initial_timeout", Datum::Null),
512 }
513 match expiration_time {
514 Some((date, time)) => {
515 f.labelled("expiration_time", "expires", date_and_time(date, time))
516 }
517 None => f.quiet("expiration_time", Datum::Null),
518 }
519 }
520 }
521 let (details, fields) = f.finish();
522 (name, details, fields)
523}
524
525#[cfg(test)]
526#[path = "notification_tests.rs"]
527mod tests;