use bacnet_types::constructed::{
BACnetAuditNotification, BACnetRecipient, EventNotificationRequest,
};
use bacnet_types::enums::{NotifyType, ObjectType, PropertyIdentifier};
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use serde::Serialize;
use super::alarms::timestamp;
use super::notification::EventValues;
use super::primitive::hex;
use super::{Datum, Floats, StateTexts, ValueFormat};
use crate::label::kebab;
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct EventNotificationData {
pub process_identifier: Datum,
pub initiating_device: Datum,
pub event_object: Datum,
pub timestamp: Datum,
pub notification_class: Datum,
pub priority: Datum,
pub event_type: Datum,
pub message_text: Datum,
pub notify_type: Datum,
pub ack_required: Datum,
pub from_state: Datum,
pub to_state: Datum,
pub event_values: Datum,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct AuditNotificationData {
pub operation: Datum,
pub source_device: Datum,
pub source_object: Datum,
pub source_timestamp: Datum,
pub source_comment: Datum,
pub source_user_id: Datum,
pub source_user_role: Datum,
pub invoke_id: Datum,
pub target_device: Datum,
pub target_object: Datum,
pub target_property: Datum,
pub target_array_index: Datum,
pub target_priority: Datum,
pub target_timestamp: Datum,
pub target_comment: Datum,
pub target_value: Datum,
pub current_value: Datum,
pub result: Option<AuditResult>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct AuditResult {
pub error_class: Datum,
pub error_code: Datum,
}
fn named<R>(object_type: ObjectType, f: impl FnOnce(&ValueFormat<'_>) -> R) -> R {
let texts = StateTexts::default();
f(&ValueFormat::new(object_type, &texts, Floats::Exact).whole_lists())
}
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 by_name(value: u32, name: Option<&str>) -> Datum {
Datum::Enumerated {
value,
name: name.map(kebab),
}
}
fn number(value: Option<impl Into<u64>>) -> Datum {
value.map_or(Datum::Null, |n| Datum::Unsigned(n.into()))
}
fn text(text: Option<&String>) -> Datum {
text.map_or(Datum::Null, |text| Datum::Text(text.clone()))
}
fn recipient(format: &ValueFormat<'_>, recipient: &BACnetRecipient) -> Datum {
match recipient {
BACnetRecipient::Device(device) => object(format, *device),
BACnetRecipient::Address(address) => Datum::Constructed {
element: "recipient".into(),
text: format!(
"network {} mac {}",
address.network_number,
hex(&address.mac_address, ":")
),
fields: None,
},
}
}
impl EventNotificationData {
pub fn new(notification: &EventNotificationRequest) -> Self {
let ack = notification.notify_type == NotifyType::ACK_NOTIFICATION;
named(ObjectType::DEVICE, |format| Self {
process_identifier: Datum::Unsigned(u64::from(notification.process_identifier)),
initiating_device: object(format, notification.initiating_device_identifier),
event_object: object(format, notification.event_object_identifier),
timestamp: timestamp(¬ification.timestamp),
notification_class: Datum::Unsigned(u64::from(notification.notification_class)),
priority: Datum::Unsigned(u64::from(notification.priority)),
event_type: enumerated(
format,
PropertyIdentifier::EVENT_TYPE,
notification.event_type.to_raw(),
),
message_text: text(notification.message_text.as_ref()),
notify_type: enumerated(
format,
PropertyIdentifier::NOTIFY_TYPE,
notification.notify_type.to_raw(),
),
ack_required: if ack {
Datum::Null
} else {
Datum::Boolean(notification.ack_required)
},
from_state: if ack {
Datum::Null
} else {
enumerated(
format,
PropertyIdentifier::EVENT_STATE,
notification.from_state.to_raw(),
)
},
to_state: enumerated(
format,
PropertyIdentifier::EVENT_STATE,
notification.to_state.to_raw(),
),
event_values: notification
.event_values
.as_ref()
.map_or(Datum::Null, |values| EventValues::new(values).data),
})
}
}
impl AuditNotificationData {
pub fn new(notification: &BACnetAuditNotification) -> Self {
let object_type = notification
.target_object
.map_or(ObjectType::DEVICE, |target| target.object_type());
let property = notification.target_property.as_ref();
named(object_type, |format| {
let value = |data: Option<&Vec<u8>>| match (data, property) {
(Some(data), Some(property)) => format.data(property.property_identifier, data),
(Some(data), None) => Datum::Octets {
octets: hex(data, ""),
},
(None, _) => Datum::Null,
};
let maybe_object = |target: Option<ObjectIdentifier>| {
target.map_or(Datum::Null, |target| object(format, target))
};
Self {
operation: by_name(
notification.operation.to_raw(),
notification.operation.name(),
),
source_device: recipient(format, ¬ification.source_device),
source_object: maybe_object(notification.source_object),
source_timestamp: notification
.source_timestamp
.as_ref()
.map_or(Datum::Null, timestamp),
source_comment: text(notification.source_comment.as_ref()),
source_user_id: number(notification.source_user_id),
source_user_role: number(notification.source_user_role),
invoke_id: number(notification.invoke_id),
target_device: recipient(format, ¬ification.target_device),
target_object: maybe_object(notification.target_object),
target_property: property.map_or(Datum::Null, |property| {
by_name(
property.property_identifier.to_raw(),
property.property_identifier.name(),
)
}),
target_array_index: number(property.and_then(|p| p.property_array_index)),
target_priority: number(notification.target_priority),
target_timestamp: notification
.target_timestamp
.as_ref()
.map_or(Datum::Null, timestamp),
target_comment: text(notification.target_comment.as_ref()),
target_value: value(notification.target_value.as_ref()),
current_value: value(notification.current_value.as_ref()),
result: notification.result.map(|(class, code)| AuditResult {
error_class: by_name(u32::from(class.to_raw()), class.name()),
error_code: by_name(u32::from(code.to_raw()), code.name()),
}),
}
})
}
}
#[cfg(test)]
#[path = "log_records_tests.rs"]
mod tests;