bacnet_objects/database/event_log.rs
1//! The event notifications the device builds, and those it receives, into
2//! its Event Log objects (Clause 12.27). The caller owns synchronization.
3
4use bacnet_types::constructed::{
5 BACnetDeviceObjectPropertyReference, BACnetEventLogRecord, EventLogDatum,
6 EventNotificationRequest, NotificationParameters,
7};
8use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier};
9use bacnet_types::error::Error;
10use bacnet_types::primitives::ObjectIdentifier;
11use tracing::{debug, warn};
12
13use super::ObjectDatabase;
14use crate::device_reference::decode_reference;
15
16impl ObjectDatabase {
17 /// Record an event notification this device generated in each of its
18 /// Event Log objects, as a notification record stamped with the Device
19 /// clock's local date and time.
20 ///
21 /// The server calls this for each notification it builds once the
22 /// recipient lookup has read the Notification Class. Each log's own
23 /// lifecycle decides what it keeps, as for a record the application adds
24 /// through `add_record`: a disabled log ignores it, and a full log drops
25 /// its oldest record or, with Stop_When_Full, stops and records that
26 /// instead.
27 ///
28 /// Two kinds go in no log, so that a report can't prompt the next without
29 /// end, through the log it watches or crosswise through another log
30 /// watched in turn. One is a BUFFER_READY report on an Event Log's buffer
31 /// (see `reports_an_event_log_buffer`), as for received notifications. The
32 /// other is any report from an Event Enrollment of this device monitoring
33 /// a property of an Event Log here: whatever its algorithm, it watches
34 /// something each record changes, such as Total_Record_Count, so it isn't
35 /// only BUFFER_READY that would answer its own record. A Trend Log's or
36 /// Trend Log Multiple's report is logged like any other notification:
37 /// those logs take no notifications, so it can't count toward the next.
38 ///
39 /// Without a valid Device clock no record can carry its timestamp, so
40 /// nothing is logged. A log that refuses the record keeps its state; the
41 /// refusal goes to the trace log and the other logs still take it. The
42 /// notification is stored as given, Process Identifier included: Clause
43 /// 12.27 leaves that parameter of a locally generated record to the device.
44 pub fn log_event_notification(&mut self, notification: &EventNotificationRequest) {
45 let logs = self.find_by_type(ObjectType::EVENT_LOG);
46 if logs.is_empty() {
47 return;
48 }
49 if reports_an_event_log_buffer(notification)
50 || self.enrollment_watches_an_event_log(notification.event_object_identifier)
51 {
52 debug!(
53 event_object = %notification.event_object_identifier,
54 "Report on an Event Log not logged"
55 );
56 return;
57 }
58 self.log_notification(logs, notification);
59 }
60
61 /// Whether any Event Log has opted in to the notifications the device
62 /// receives. The server asks before it decodes one.
63 pub fn collects_received_event_notifications(&self) -> bool {
64 self.find_by_type(ObjectType::EVENT_LOG).iter().any(|oid| {
65 self.get(oid)
66 .is_some_and(|log| log.logs_received_event_notifications_internal())
67 })
68 }
69
70 /// Whether [`log_received_event_notification`](Self::log_received_event_notification)
71 /// would record `notification`, received from another device, anywhere:
72 /// some Event Log has opted in, and the notification is one a log may
73 /// take. The server asks before it spends any of a source's allowance.
74 pub fn takes_received_event_notification(
75 &self,
76 notification: &EventNotificationRequest,
77 ) -> bool {
78 self.collects_received_event_notifications() && self.may_log_received(notification)
79 }
80
81 /// Record an event notification this device received, as it decoded, in
82 /// each Event Log that has opted in with
83 /// [`EventLogObject::set_log_received_notifications`](crate::event_log::EventLogObject::set_log_received_notifications),
84 /// stamped with the Device clock's local date and time. Each log's
85 /// lifecycle decides what it keeps, as for the device's own
86 /// notifications, and each record counts toward the log's
87 /// Notification_Threshold.
88 ///
89 /// Two kinds are kept out. A BUFFER_READY report on an Event Log's buffer
90 /// in any device isn't logged (see `reports_an_event_log_buffer`), whatever
91 /// object reports it: another vendor's Event Enrollment running the
92 /// algorithm on its Event Log names the log only there. Logging it could
93 /// prompt a report here that, logged there in turn, prompts the next. Any
94 /// other notification about an Event Log is logged. And one whose
95 /// Initiating Device Identifier names this device isn't logged a second
96 /// time: the device logged it when it built it.
97 ///
98 /// This records whatever it is given. The server calls it only for a
99 /// notification that decoded in full, after holding each source to a few
100 /// records a second, so a flood of them can't push the device's own
101 /// records out of a log faster than that bound.
102 pub fn log_received_event_notification(&mut self, notification: &EventNotificationRequest) {
103 let logs = self.received_notification_logs();
104 if !logs.is_empty() && self.may_log_received(notification) {
105 self.log_notification(logs, notification);
106 }
107 }
108
109 /// The Event Logs that take received notifications.
110 fn received_notification_logs(&self) -> Vec<ObjectIdentifier> {
111 let mut logs = self.find_by_type(ObjectType::EVENT_LOG);
112 logs.retain(|oid| {
113 self.get(oid)
114 .is_some_and(|log| log.logs_received_event_notifications_internal())
115 });
116 logs
117 }
118
119 /// Whether a received notification is one a log may take: it isn't a
120 /// BUFFER_READY report on an Event Log's buffer, and doesn't claim to
121 /// come from this device.
122 fn may_log_received(&self, notification: &EventNotificationRequest) -> bool {
123 !reports_an_event_log_buffer(notification)
124 && self.local_device().identifier() != Some(notification.initiating_device_identifier)
125 }
126
127 /// Add `notification` to each of `logs` as a notification record stamped
128 /// with the Device clock, which has to be valid.
129 fn log_notification(
130 &mut self,
131 logs: Vec<ObjectIdentifier>,
132 notification: &EventNotificationRequest,
133 ) {
134 let Some(frame) = self
135 .clock_frame()
136 .filter(|frame| frame.is_valid_actual_datetime())
137 else {
138 debug!(
139 event_object = %notification.event_object_identifier,
140 "No valid Device clock; event notification not logged"
141 );
142 return;
143 };
144 for oid in logs {
145 let Some(log) = self.get_mut(&oid) else {
146 continue;
147 };
148 let record = BACnetEventLogRecord {
149 date: frame.local_date,
150 time: frame.local_time,
151 log_datum: EventLogDatum::Notification(notification.clone()),
152 };
153 match log.add_event_log_record(record) {
154 Ok(()) => {}
155 Err(error) if is_unsupported(&error) => {
156 debug!(log = %oid, "Event Log object takes no records");
157 }
158 Err(error) => {
159 warn!(log = %oid, %error, "Event Log refused an event notification record");
160 }
161 }
162 }
163 }
164
165 /// Whether `event_object` is an Event Enrollment of this device whose
166 /// Object_Property_Reference names an Event Log here.
167 fn enrollment_watches_an_event_log(&self, event_object: ObjectIdentifier) -> bool {
168 match event_object.object_type() {
169 ObjectType::EVENT_ENROLLMENT => self
170 .get(&event_object)
171 .and_then(|enrollment| {
172 enrollment
173 .read_property(PropertyIdentifier::OBJECT_PROPERTY_REFERENCE, None)
174 .ok()
175 })
176 .and_then(|value| {
177 decode_reference::<BACnetDeviceObjectPropertyReference>(&value).ok()
178 })
179 .is_some_and(|reference| {
180 reference.object_identifier.object_type() == ObjectType::EVENT_LOG
181 && self.local_device().is_local(reference.device_identifier)
182 }),
183 _ => false,
184 }
185 }
186}
187
188/// Whether `notification` is a BUFFER_READY report whose Buffer_Property
189/// names an Event Log, in this device or any other. No Event Log records one:
190/// each record a log takes moves its Total_Record_Count, so a log taking
191/// reports on a log's buffer could set off the next report, here or in the
192/// other device, and that report the next, without end.
193fn reports_an_event_log_buffer(notification: &EventNotificationRequest) -> bool {
194 matches!(
195 ¬ification.event_values,
196 Some(NotificationParameters::BufferReady { buffer_property, .. })
197 if buffer_property.object_identifier.object_type() == ObjectType::EVENT_LOG
198 )
199}
200
201/// The trait default's answer: an object of type Event Log that has no
202/// record insertion.
203fn is_unsupported(error: &Error) -> bool {
204 matches!(error, Error::Protocol { class, code }
205 if *class == ErrorClass::OBJECT.to_raw() as u32
206 && *code == ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED.to_raw() as u32)
207}
208
209#[cfg(test)]
210#[path = "event_log_tests.rs"]
211mod tests;