1use bacnet_types::bitstring::EventTransitionBits;
24use bacnet_types::constructed::BACnetObjectPropertyReference;
25use bacnet_types::enums::{
26 ErrorClass, ErrorCode, EventState, EventType, NotifyType, PropertyIdentifier as P,
27};
28use bacnet_types::error::Error;
29use bacnet_types::primitives::{BACnetTimeStamp, PropertyValue};
30
31use crate::common;
32use crate::event::history::{EventHistory, EventTransitionState};
33use crate::event::options::REPORTING_OPTION_METADATA as OPTIONS;
34use crate::event::{
35 EnrollmentSummaryCapability, EventStateChange, EventTransitionCommit,
36 EventTransitionCommitError, TransitionOutcome,
37};
38use crate::property_metadata::{
39 PropertyConformance::Optional,
40 PropertyMetadata,
41 PropertyPresenceCondition::{IntrinsicReportingOptional, IntrinsicReportingRequired},
42 PropertyWriteCapability::{Always, ReadOnly},
43};
44
45#[doc(hidden)]
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct BufferReadyReport {
50 pub previous_notification: u32,
53 pub current_notification: u32,
55}
56
57const fn required(
60 property: P,
61 write: crate::property_metadata::PropertyWriteCapability,
62) -> PropertyMetadata {
63 PropertyMetadata::new(property, Optional, Some(IntrinsicReportingRequired), write)
64}
65
66const fn permitted(
69 property: P,
70 write: crate::property_metadata::PropertyWriteCapability,
71) -> PropertyMetadata {
72 PropertyMetadata::new(property, Optional, Some(IntrinsicReportingOptional), write)
73}
74
75pub(crate) const BUFFER_READY_METADATA: [PropertyMetadata; 13] = [
80 required(P::NOTIFICATION_THRESHOLD, Always),
81 required(P::RECORDS_SINCE_NOTIFICATION, ReadOnly),
82 required(P::LAST_NOTIFY_RECORD, ReadOnly),
83 required(P::NOTIFICATION_CLASS, Always),
84 required(P::EVENT_ENABLE, Always),
85 required(P::ACKED_TRANSITIONS, ReadOnly),
86 required(P::NOTIFY_TYPE, Always),
87 required(P::EVENT_TIME_STAMPS, ReadOnly),
88 permitted(P::EVENT_MESSAGE_TEXTS, ReadOnly),
89 OPTIONS[0],
90 required(P::EVENT_DETECTION_ENABLE, Always),
91 OPTIONS[1],
92 OPTIONS[2],
93];
94
95pub(crate) fn records_between(from: u32, to: u32) -> u32 {
99 if to >= from {
100 to - from
101 } else {
102 (u64::from(to) + u64::from(u32::MAX) - u64::from(from)) as u32
104 }
105}
106
107#[derive(Debug, Clone)]
110pub(crate) struct BufferReadyReporting {
111 notification_threshold: u32,
114 last_notify_record: u32,
117 count_restarted_at: u32,
120 notification_class: u32,
121 event_enable: EventTransitionBits,
122 notify_type: NotifyType,
123 event_state: EventState,
125 acked_transitions: EventTransitionBits,
126 history: EventHistory,
127 event_detection_enable: bool,
129 report: Option<BufferReadyReport>,
131}
132
133impl Default for BufferReadyReporting {
134 fn default() -> Self {
138 Self {
139 notification_threshold: 0,
140 last_notify_record: 0,
141 count_restarted_at: 0,
142 notification_class: 0,
143 event_enable: EventTransitionBits::all(),
144 notify_type: NotifyType::EVENT,
145 event_state: EventState::NORMAL,
146 acked_transitions: EventTransitionBits::all(),
147 history: EventHistory::default(),
148 event_detection_enable: true,
149 report: None,
150 }
151 }
152}
153
154impl BufferReadyReporting {
155 pub(crate) fn event_state(&self) -> EventState {
157 self.event_state
158 }
159
160 pub(crate) fn set_notification_threshold(&mut self, threshold: u32) {
161 self.notification_threshold = threshold;
162 }
163
164 pub(crate) fn set_notification_class(&mut self, class: u32) {
165 self.notification_class = class;
166 }
167
168 pub(crate) fn set_event_enable(&mut self, enable: EventTransitionBits) {
169 self.event_enable = enable;
170 }
171
172 pub(crate) fn set_notify_type(&mut self, notify_type: NotifyType) {
173 self.notify_type = notify_type;
174 }
175
176 pub(crate) fn restart_count(&mut self, total: u32) {
180 self.count_restarted_at = total;
181 }
182
183 pub(crate) fn read(
186 &self,
187 property: P,
188 array_index: Option<u32>,
189 total: u32,
190 ) -> Option<Result<PropertyValue, Error>> {
191 let unsigned = |value: u32| Some(Ok(PropertyValue::Unsigned(u64::from(value))));
192 let transitions = |bits: EventTransitionBits| {
193 Some(Ok(PropertyValue::BitString {
194 unused_bits: 5,
195 data: vec![bits.to_bacnet()],
196 }))
197 };
198 match property {
199 P::EVENT_STATE => Some(Ok(PropertyValue::Enumerated(self.event_state.to_raw()))),
200 P::EVENT_DETECTION_ENABLE => {
201 Some(Ok(PropertyValue::Boolean(self.event_detection_enable)))
202 }
203 P::NOTIFICATION_THRESHOLD => unsigned(self.notification_threshold),
204 P::RECORDS_SINCE_NOTIFICATION => {
205 unsigned(records_between(self.count_restarted_at, total))
206 }
207 P::LAST_NOTIFY_RECORD => unsigned(self.last_notify_record),
208 P::NOTIFICATION_CLASS => unsigned(self.notification_class),
209 P::EVENT_ENABLE => transitions(self.event_enable),
210 P::ACKED_TRANSITIONS => transitions(self.acked_transitions),
211 P::NOTIFY_TYPE => Some(Ok(PropertyValue::Enumerated(self.notify_type.to_raw()))),
212 _ => self.history.read(property, array_index),
213 }
214 }
215
216 pub(crate) fn write(
229 &mut self,
230 property: P,
231 array_index: Option<u32>,
232 value: &PropertyValue,
233 total: u32,
234 ) -> Option<Result<(), Error>> {
235 if let Some(result) =
236 self.history
237 .write(property, array_index, value, self.event_detection_enable)
238 {
239 return Some(result);
240 }
241 let result = match property {
242 P::EVENT_DETECTION_ENABLE => boolean(value).map(|enable| {
243 if !enable {
244 self.event_state = EventState::NORMAL;
245 self.acked_transitions = EventTransitionBits::all();
246 self.history.reset();
247 } else if !self.event_detection_enable {
248 self.last_notify_record = total;
249 self.count_restarted_at = total;
250 }
251 self.event_detection_enable = enable;
252 }),
253 P::NOTIFICATION_THRESHOLD => {
254 unsigned32(value).map(|threshold| self.notification_threshold = threshold)
255 }
256 P::NOTIFICATION_CLASS => unsigned32(value).map(|class| self.notification_class = class),
257 P::EVENT_ENABLE => match value {
258 PropertyValue::BitString { unused_bits, data } => {
259 common::check_fixed_width_bit_string(*unused_bits, data, 3).map(|byte| {
260 self.event_enable = EventTransitionBits::from_bacnet(&[byte]);
261 })
262 }
263 _ => Err(common::invalid_data_type_error()),
264 },
265 P::NOTIFY_TYPE => match *value {
266 PropertyValue::Enumerated(raw) => {
267 let notify_type = NotifyType::from_raw(raw);
268 if NotifyType::ALL_NAMED.iter().any(|&(_, n)| n == notify_type) {
269 self.notify_type = notify_type;
270 Ok(())
271 } else {
272 Err(common::value_out_of_range_error())
273 }
274 }
275 _ => Err(common::invalid_data_type_error()),
276 },
277 P::ACKED_TRANSITIONS => Err(common::write_access_denied_error()),
278 _ => return None,
279 };
280 Some(result)
281 }
282
283 pub(crate) fn propose(&self, total: u32) -> Option<TransitionOutcome> {
292 let due = self.event_detection_enable
293 && !self.history.options.inhibited()
294 && self.notification_threshold > 0
295 && records_between(self.last_notify_record, total) >= self.notification_threshold;
296 due.then(|| TransitionOutcome {
297 change: EventStateChange {
298 from: EventState::NORMAL,
299 to: EventState::NORMAL,
300 },
301 event_type: EventType::BUFFER_READY,
302 distribute: self.event_enable.contains(EventTransitionBits::TO_NORMAL),
303 })
304 }
305
306 pub(crate) fn commit(
311 &mut self,
312 commit: EventTransitionCommit,
313 total: u32,
314 ) -> Result<(), EventTransitionCommitError> {
315 if commit.change.to != EventState::NORMAL {
316 return Err(EventTransitionCommitError::Unsupported);
317 }
318 EventTransitionState::new(
319 &mut self.event_state,
320 &mut self.acked_transitions,
321 &mut self.history,
322 )
323 .commit(commit)?;
324 self.report = Some(BufferReadyReport {
325 previous_notification: self.last_notify_record,
326 current_notification: total,
327 });
328 self.last_notify_record = total;
329 self.count_restarted_at = total;
330 Ok(())
331 }
332
333 pub(crate) fn report(&self) -> Option<BufferReadyReport> {
335 self.report
336 }
337
338 pub(crate) fn acknowledge(
341 &mut self,
342 event_state: EventState,
343 timestamp: &BACnetTimeStamp,
344 ) -> Result<Option<EventStateChange>, Error> {
345 if !self.event_detection_enable {
346 return Err(common::protocol_error(
347 ErrorClass::OBJECT,
348 ErrorCode::NO_ALARM_CONFIGURED,
349 ));
350 }
351 self.history.acknowledge_correlated_detailed(
352 &mut self.acked_transitions,
353 event_state,
354 timestamp,
355 )
356 }
357
358 pub(crate) fn inhibit_reference(&self) -> Option<BACnetObjectPropertyReference> {
360 self.history.options.inhibit_reference().cloned()
361 }
362
363 pub(crate) fn follow_inhibit(&mut self, inhibit: bool) -> bool {
366 self.history.options.follow(inhibit)
367 }
368
369 pub(crate) fn enrollment_summary(&self) -> EnrollmentSummaryCapability {
371 EnrollmentSummaryCapability {
372 event_type: EventType::BUFFER_READY,
373 last_transition: self.history.last_transition(),
374 }
375 }
376}
377
378fn boolean(value: &PropertyValue) -> Result<bool, Error> {
379 match *value {
380 PropertyValue::Boolean(value) => Ok(value),
381 _ => Err(common::invalid_data_type_error()),
382 }
383}
384
385fn unsigned32(value: &PropertyValue) -> Result<u32, Error> {
386 match *value {
387 PropertyValue::Unsigned(value) => common::u64_to_u32(value),
388 _ => Err(common::invalid_data_type_error()),
389 }
390}
391
392macro_rules! impl_buffer_ready_reporting {
396 ($reporting:ident, $buffer:ident) => {
397 fn enrollment_summary_capability_internal(
398 &self,
399 ) -> Option<$crate::event::EnrollmentSummaryCapability> {
400 Some(self.$reporting.enrollment_summary())
401 }
402
403 fn evaluate_intrinsic_reporting(&mut self) -> Option<$crate::event::TransitionOutcome> {
404 self.$reporting.propose(self.$buffer.total_record_count())
405 }
406
407 fn tick_intrinsic_reporting(&mut self) -> Option<$crate::event::TransitionOutcome> {
408 self.$reporting.propose(self.$buffer.total_record_count())
409 }
410
411 fn commit_event_transition_internal(
412 &mut self,
413 commit: $crate::event::EventTransitionCommit,
414 ) -> Result<(), $crate::event::EventTransitionCommitError> {
415 let total = self.$buffer.total_record_count();
416 self.$reporting.commit(commit, total)
417 }
418
419 fn acknowledge_alarm_correlated_internal(
420 &mut self,
421 event_state: bacnet_types::enums::EventState,
422 timestamp: &bacnet_types::primitives::BACnetTimeStamp,
423 ) -> Result<(), bacnet_types::error::Error> {
424 self.$reporting
425 .acknowledge(event_state, timestamp)
426 .map(|_| ())
427 }
428
429 fn acknowledge_alarm_correlated_detailed_internal(
430 &mut self,
431 event_state: bacnet_types::enums::EventState,
432 timestamp: &bacnet_types::primitives::BACnetTimeStamp,
433 ) -> Result<Option<$crate::event::EventStateChange>, bacnet_types::error::Error> {
434 self.$reporting.acknowledge(event_state, timestamp)
435 }
436
437 fn buffer_ready_report_internal(&self) -> Option<$crate::log_reporting::BufferReadyReport> {
438 self.$reporting.report()
439 }
440
441 fn event_algorithm_inhibit_reference_internal(
442 &self,
443 ) -> Option<bacnet_types::constructed::BACnetObjectPropertyReference> {
444 self.$reporting.inhibit_reference()
445 }
446
447 fn follow_event_algorithm_inhibit_internal(&mut self, inhibit: bool) -> bool {
448 self.$reporting.follow_inhibit(inhibit)
449 }
450 };
451}
452
453pub(crate) use impl_buffer_ready_reporting;
454
455#[cfg(test)]
456#[path = "log_reporting_tests.rs"]
457mod tests;