1use bacnet_types::constructed::{
13 BACnetAuditNotification, BACnetRecipient, EventNotificationRequest,
14};
15use bacnet_types::enums::{NotifyType, ObjectType, PropertyIdentifier};
16use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
17use serde::Serialize;
18
19use super::alarms::timestamp;
20use super::notification::EventValues;
21use super::primitive::hex;
22use super::{Datum, Floats, StateTexts, ValueFormat};
23use crate::label::kebab;
24
25#[derive(Debug, Clone, PartialEq, Serialize)]
27pub struct EventNotificationData {
28 pub process_identifier: Datum,
30 pub initiating_device: Datum,
32 pub event_object: Datum,
34 pub timestamp: Datum,
36 pub notification_class: Datum,
38 pub priority: Datum,
40 pub event_type: Datum,
42 pub message_text: Datum,
44 pub notify_type: Datum,
46 pub ack_required: Datum,
49 pub from_state: Datum,
52 pub to_state: Datum,
54 pub event_values: Datum,
57}
58
59#[derive(Debug, Clone, PartialEq, Serialize)]
61pub struct AuditNotificationData {
62 pub operation: Datum,
64 pub source_device: Datum,
66 pub source_object: Datum,
68 pub source_timestamp: Datum,
70 pub source_comment: Datum,
72 pub source_user_id: Datum,
74 pub source_user_role: Datum,
76 pub invoke_id: Datum,
78 pub target_device: Datum,
80 pub target_object: Datum,
82 pub target_property: Datum,
84 pub target_array_index: Datum,
86 pub target_priority: Datum,
88 pub target_timestamp: Datum,
90 pub target_comment: Datum,
92 pub target_value: Datum,
94 pub current_value: Datum,
96 pub result: Option<AuditResult>,
99}
100
101#[derive(Debug, Clone, PartialEq, Serialize)]
103pub struct AuditResult {
104 pub error_class: Datum,
106 pub error_code: Datum,
108}
109
110fn named<R>(object_type: ObjectType, f: impl FnOnce(&ValueFormat<'_>) -> R) -> R {
113 let texts = StateTexts::default();
114 f(&ValueFormat::new(object_type, &texts, Floats::Exact).whole_lists())
115}
116
117fn object(format: &ValueFormat<'_>, object: ObjectIdentifier) -> Datum {
118 format.element_data(
119 PropertyIdentifier::OBJECT_IDENTIFIER,
120 &PropertyValue::ObjectIdentifier(object),
121 )
122}
123
124fn enumerated(format: &ValueFormat<'_>, property: PropertyIdentifier, raw: u32) -> Datum {
125 format.element_data(property, &PropertyValue::Enumerated(raw))
126}
127
128fn by_name(value: u32, name: Option<&str>) -> Datum {
130 Datum::Enumerated {
131 value,
132 name: name.map(kebab),
133 }
134}
135
136fn number(value: Option<impl Into<u64>>) -> Datum {
137 value.map_or(Datum::Null, |n| Datum::Unsigned(n.into()))
138}
139
140fn text(text: Option<&String>) -> Datum {
141 text.map_or(Datum::Null, |text| Datum::Text(text.clone()))
142}
143
144fn recipient(format: &ValueFormat<'_>, recipient: &BACnetRecipient) -> Datum {
145 match recipient {
146 BACnetRecipient::Device(device) => object(format, *device),
147 BACnetRecipient::Address(address) => Datum::Constructed {
148 element: "recipient".into(),
149 text: format!(
150 "network {} mac {}",
151 address.network_number,
152 hex(&address.mac_address, ":")
153 ),
154 fields: None,
155 },
156 }
157}
158
159impl EventNotificationData {
160 pub fn new(notification: &EventNotificationRequest) -> Self {
162 let ack = notification.notify_type == NotifyType::ACK_NOTIFICATION;
165 named(ObjectType::DEVICE, |format| Self {
166 process_identifier: Datum::Unsigned(u64::from(notification.process_identifier)),
167 initiating_device: object(format, notification.initiating_device_identifier),
168 event_object: object(format, notification.event_object_identifier),
169 timestamp: timestamp(¬ification.timestamp),
170 notification_class: Datum::Unsigned(u64::from(notification.notification_class)),
171 priority: Datum::Unsigned(u64::from(notification.priority)),
172 event_type: enumerated(
173 format,
174 PropertyIdentifier::EVENT_TYPE,
175 notification.event_type.to_raw(),
176 ),
177 message_text: text(notification.message_text.as_ref()),
178 notify_type: enumerated(
179 format,
180 PropertyIdentifier::NOTIFY_TYPE,
181 notification.notify_type.to_raw(),
182 ),
183 ack_required: if ack {
184 Datum::Null
185 } else {
186 Datum::Boolean(notification.ack_required)
187 },
188 from_state: if ack {
189 Datum::Null
190 } else {
191 enumerated(
192 format,
193 PropertyIdentifier::EVENT_STATE,
194 notification.from_state.to_raw(),
195 )
196 },
197 to_state: enumerated(
198 format,
199 PropertyIdentifier::EVENT_STATE,
200 notification.to_state.to_raw(),
201 ),
202 event_values: notification
203 .event_values
204 .as_ref()
205 .map_or(Datum::Null, |values| EventValues::new(values).data),
206 })
207 }
208}
209
210impl AuditNotificationData {
211 pub fn new(notification: &BACnetAuditNotification) -> Self {
214 let object_type = notification
215 .target_object
216 .map_or(ObjectType::DEVICE, |target| target.object_type());
217 let property = notification.target_property.as_ref();
218 named(object_type, |format| {
219 let value = |data: Option<&Vec<u8>>| match (data, property) {
220 (Some(data), Some(property)) => format.data(property.property_identifier, data),
221 (Some(data), None) => Datum::Octets {
222 octets: hex(data, ""),
223 },
224 (None, _) => Datum::Null,
225 };
226 let maybe_object = |target: Option<ObjectIdentifier>| {
227 target.map_or(Datum::Null, |target| object(format, target))
228 };
229 Self {
230 operation: by_name(
231 notification.operation.to_raw(),
232 notification.operation.name(),
233 ),
234 source_device: recipient(format, ¬ification.source_device),
235 source_object: maybe_object(notification.source_object),
236 source_timestamp: notification
237 .source_timestamp
238 .as_ref()
239 .map_or(Datum::Null, timestamp),
240 source_comment: text(notification.source_comment.as_ref()),
241 source_user_id: number(notification.source_user_id),
242 source_user_role: number(notification.source_user_role),
243 invoke_id: number(notification.invoke_id),
244 target_device: recipient(format, ¬ification.target_device),
245 target_object: maybe_object(notification.target_object),
246 target_property: property.map_or(Datum::Null, |property| {
247 by_name(
248 property.property_identifier.to_raw(),
249 property.property_identifier.name(),
250 )
251 }),
252 target_array_index: number(property.and_then(|p| p.property_array_index)),
253 target_priority: number(notification.target_priority),
254 target_timestamp: notification
255 .target_timestamp
256 .as_ref()
257 .map_or(Datum::Null, timestamp),
258 target_comment: text(notification.target_comment.as_ref()),
259 target_value: value(notification.target_value.as_ref()),
260 current_value: value(notification.current_value.as_ref()),
261 result: notification.result.map(|(class, code)| AuditResult {
262 error_class: by_name(u32::from(class.to_raw()), class.name()),
263 error_code: by_name(u32::from(code.to_raw()), code.name()),
264 }),
265 }
266 })
267 }
268}
269
270#[cfg(test)]
271#[path = "log_records_tests.rs"]
272mod tests;