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