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 restart_from(&mut self, total: u32) {
188 self.last_notify_record = total;
189 self.count_restarted_at = total;
190 }
191
192 pub(crate) fn counts(&self) -> (u32, u32) {
195 (self.last_notify_record, self.count_restarted_at)
196 }
197
198 pub(crate) fn notification_threshold(&self) -> u32 {
200 self.notification_threshold
201 }
202
203 pub(crate) fn resume(&mut self, last_notify_record: u32, count_restarted_at: u32) {
207 self.last_notify_record = last_notify_record;
208 self.count_restarted_at = count_restarted_at;
209 }
210
211 pub(crate) fn read(
214 &self,
215 property: P,
216 array_index: Option<u32>,
217 total: u32,
218 ) -> Option<Result<PropertyValue, Error>> {
219 let unsigned = |value: u32| Some(Ok(PropertyValue::Unsigned(u64::from(value))));
220 let transitions = |bits: EventTransitionBits| {
221 Some(Ok(PropertyValue::BitString {
222 unused_bits: 5,
223 data: vec![bits.to_bacnet()],
224 }))
225 };
226 match property {
227 P::EVENT_STATE => Some(Ok(PropertyValue::Enumerated(self.event_state.to_raw()))),
228 P::EVENT_DETECTION_ENABLE => {
229 Some(Ok(PropertyValue::Boolean(self.event_detection_enable)))
230 }
231 P::NOTIFICATION_THRESHOLD => unsigned(self.notification_threshold),
232 P::RECORDS_SINCE_NOTIFICATION => {
233 unsigned(records_between(self.count_restarted_at, total))
234 }
235 P::LAST_NOTIFY_RECORD => unsigned(self.last_notify_record),
236 P::NOTIFICATION_CLASS => unsigned(self.notification_class),
237 P::EVENT_ENABLE => transitions(self.event_enable),
238 P::ACKED_TRANSITIONS => transitions(self.acked_transitions),
239 P::NOTIFY_TYPE => Some(Ok(PropertyValue::Enumerated(self.notify_type.to_raw()))),
240 _ => self.history.read(property, array_index),
241 }
242 }
243
244 pub(crate) fn write(
257 &mut self,
258 property: P,
259 array_index: Option<u32>,
260 value: &PropertyValue,
261 total: u32,
262 ) -> Option<Result<(), Error>> {
263 if let Some(result) =
264 self.history
265 .write(property, array_index, value, self.event_detection_enable)
266 {
267 return Some(result);
268 }
269 let result = match property {
270 P::EVENT_DETECTION_ENABLE => boolean(value).map(|enable| {
271 if !enable {
272 self.event_state = EventState::NORMAL;
273 self.acked_transitions = EventTransitionBits::all();
274 self.history.reset();
275 } else if !self.event_detection_enable {
276 self.last_notify_record = total;
277 self.count_restarted_at = total;
278 }
279 self.event_detection_enable = enable;
280 }),
281 P::NOTIFICATION_THRESHOLD => {
282 unsigned32(value).map(|threshold| self.notification_threshold = threshold)
283 }
284 P::NOTIFICATION_CLASS => unsigned32(value).map(|class| self.notification_class = class),
285 P::EVENT_ENABLE => match value {
286 PropertyValue::BitString { unused_bits, data } => {
287 common::check_fixed_width_bit_string(*unused_bits, data, 3).map(|byte| {
288 self.event_enable = EventTransitionBits::from_bacnet(&[byte]);
289 })
290 }
291 _ => Err(common::invalid_data_type_error()),
292 },
293 P::NOTIFY_TYPE => match *value {
294 PropertyValue::Enumerated(raw) => {
295 let notify_type = NotifyType::from_raw(raw);
296 if NotifyType::ALL_NAMED.iter().any(|&(_, n)| n == notify_type) {
297 self.notify_type = notify_type;
298 Ok(())
299 } else {
300 Err(common::value_out_of_range_error())
301 }
302 }
303 _ => Err(common::invalid_data_type_error()),
304 },
305 P::ACKED_TRANSITIONS => Err(common::write_access_denied_error()),
306 _ => return None,
307 };
308 Some(result)
309 }
310
311 pub(crate) fn propose(&self, total: u32) -> Option<TransitionOutcome> {
320 let due = self.event_detection_enable
321 && !self.history.options.inhibited()
322 && self.notification_threshold > 0
323 && records_between(self.last_notify_record, total) >= self.notification_threshold;
324 due.then(|| TransitionOutcome {
325 change: EventStateChange {
326 from: EventState::NORMAL,
327 to: EventState::NORMAL,
328 },
329 event_type: EventType::BUFFER_READY,
330 distribute: self.event_enable.contains(EventTransitionBits::TO_NORMAL),
331 })
332 }
333
334 pub(crate) fn commit(
339 &mut self,
340 commit: EventTransitionCommit,
341 total: u32,
342 ) -> Result<(), EventTransitionCommitError> {
343 if commit.change.to != EventState::NORMAL {
344 return Err(EventTransitionCommitError::Unsupported);
345 }
346 EventTransitionState::new(
347 &mut self.event_state,
348 &mut self.acked_transitions,
349 &mut self.history,
350 )
351 .commit(commit)?;
352 self.report = Some(BufferReadyReport {
353 previous_notification: self.last_notify_record,
354 current_notification: total,
355 });
356 self.last_notify_record = total;
357 self.count_restarted_at = total;
358 Ok(())
359 }
360
361 pub(crate) fn report(&self) -> Option<BufferReadyReport> {
363 self.report
364 }
365
366 pub(crate) fn acknowledge(
369 &mut self,
370 event_state: EventState,
371 timestamp: &BACnetTimeStamp,
372 ) -> Result<Option<EventStateChange>, Error> {
373 if !self.event_detection_enable {
374 return Err(common::protocol_error(
375 ErrorClass::OBJECT,
376 ErrorCode::NO_ALARM_CONFIGURED,
377 ));
378 }
379 self.history.acknowledge_correlated_detailed(
380 &mut self.acked_transitions,
381 event_state,
382 timestamp,
383 )
384 }
385
386 pub(crate) fn inhibit_reference(&self) -> Option<BACnetObjectPropertyReference> {
388 self.history.options.inhibit_reference().cloned()
389 }
390
391 pub(crate) fn follow_inhibit(&mut self, inhibit: bool) -> bool {
394 self.history.options.follow(inhibit)
395 }
396
397 pub(crate) fn enrollment_summary(&self) -> EnrollmentSummaryCapability {
399 EnrollmentSummaryCapability {
400 event_type: EventType::BUFFER_READY,
401 last_transition: self.history.last_transition(),
402 }
403 }
404}
405
406fn boolean(value: &PropertyValue) -> Result<bool, Error> {
407 match *value {
408 PropertyValue::Boolean(value) => Ok(value),
409 _ => Err(common::invalid_data_type_error()),
410 }
411}
412
413fn unsigned32(value: &PropertyValue) -> Result<u32, Error> {
414 match *value {
415 PropertyValue::Unsigned(value) => common::u64_to_u32(value),
416 _ => Err(common::invalid_data_type_error()),
417 }
418}
419
420macro_rules! impl_buffer_ready_reporting {
424 ($reporting:ident, $buffer:ident) => {
425 fn enrollment_summary_capability_internal(
426 &self,
427 ) -> Option<$crate::event::EnrollmentSummaryCapability> {
428 Some(self.$reporting.enrollment_summary())
429 }
430
431 fn evaluate_intrinsic_reporting(&mut self) -> Option<$crate::event::TransitionOutcome> {
432 self.$reporting.propose(self.$buffer.total_record_count())
433 }
434
435 fn tick_intrinsic_reporting(&mut self) -> Option<$crate::event::TransitionOutcome> {
436 self.$reporting.propose(self.$buffer.total_record_count())
437 }
438
439 fn commit_event_transition_internal(
440 &mut self,
441 commit: $crate::event::EventTransitionCommit,
442 ) -> Result<(), $crate::event::EventTransitionCommitError> {
443 let total = self.$buffer.total_record_count();
444 self.$reporting.commit(commit, total)
445 }
446
447 fn acknowledge_alarm_correlated_internal(
448 &mut self,
449 event_state: bacnet_types::enums::EventState,
450 timestamp: &bacnet_types::primitives::BACnetTimeStamp,
451 ) -> Result<(), bacnet_types::error::Error> {
452 self.$reporting
453 .acknowledge(event_state, timestamp)
454 .map(|_| ())
455 }
456
457 fn acknowledge_alarm_correlated_detailed_internal(
458 &mut self,
459 event_state: bacnet_types::enums::EventState,
460 timestamp: &bacnet_types::primitives::BACnetTimeStamp,
461 ) -> Result<Option<$crate::event::EventStateChange>, bacnet_types::error::Error> {
462 self.$reporting.acknowledge(event_state, timestamp)
463 }
464
465 fn buffer_ready_report_internal(&self) -> Option<$crate::log_reporting::BufferReadyReport> {
466 self.$reporting.report()
467 }
468
469 fn event_algorithm_inhibit_reference_internal(
470 &self,
471 ) -> Option<bacnet_types::constructed::BACnetObjectPropertyReference> {
472 self.$reporting.inhibit_reference()
473 }
474
475 fn follow_event_algorithm_inhibit_internal(&mut self, inhibit: bool) -> bool {
476 self.$reporting.follow_inhibit(inhibit)
477 }
478 };
479}
480
481pub(crate) use impl_buffer_ready_reporting;
482
483#[cfg(test)]
484#[path = "log_reporting_tests.rs"]
485mod tests;