1use bacnet_types::enums::{EventState, EventType};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct EventStateChange {
12 pub from: EventState,
14 pub to: EventState,
16}
17
18impl EventStateChange {
19 pub fn event_type(&self) -> EventType {
24 if self.from == EventState::HIGH_LIMIT
25 || self.from == EventState::LOW_LIMIT
26 || self.to == EventState::HIGH_LIMIT
27 || self.to == EventState::LOW_LIMIT
28 {
29 EventType::OUT_OF_RANGE
30 } else {
31 EventType::CHANGE_OF_STATE
32 }
33 }
34
35 pub fn transition(&self) -> EventTransition {
41 if self.to == EventState::NORMAL {
42 EventTransition::ToNormal
43 } else if self.to == EventState::FAULT {
44 EventTransition::ToFault
45 } else {
46 EventTransition::ToOffnormal
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum EventTransition {
54 ToOffnormal,
56 ToFault,
58 ToNormal,
60}
61
62impl EventTransition {
63 pub fn bit_mask(self) -> u8 {
67 match self {
68 EventTransition::ToOffnormal => 0x01,
69 EventTransition::ToFault => 0x02,
70 EventTransition::ToNormal => 0x04,
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct LimitEnable {
80 pub low_limit_enable: bool,
81 pub high_limit_enable: bool,
82}
83
84impl LimitEnable {
85 pub const NONE: Self = Self {
86 low_limit_enable: false,
87 high_limit_enable: false,
88 };
89
90 pub const BOTH: Self = Self {
91 low_limit_enable: true,
92 high_limit_enable: true,
93 };
94
95 pub fn to_bits(self) -> u8 {
97 let mut bits = 0u8;
98 if self.low_limit_enable {
99 bits |= 0x80; }
101 if self.high_limit_enable {
102 bits |= 0x40; }
104 bits
105 }
106
107 pub fn from_bits(byte: u8) -> Self {
109 Self {
110 low_limit_enable: byte & 0x80 != 0,
111 high_limit_enable: byte & 0x40 != 0,
112 }
113 }
114}
115
116#[derive(Debug, Clone)]
126pub struct OutOfRangeDetector {
127 pub high_limit: f32,
128 pub low_limit: f32,
129 pub deadband: f32,
130 pub limit_enable: LimitEnable,
131 pub notification_class: u32,
132 pub notify_type: u32,
133 pub event_enable: u8,
134 pub time_delay: u32,
135 pub event_state: EventState,
136 pub acked_transitions: u8,
139}
140
141impl Default for OutOfRangeDetector {
142 fn default() -> Self {
143 Self {
144 high_limit: 100.0,
145 low_limit: 0.0,
146 deadband: 1.0,
147 limit_enable: LimitEnable::NONE,
148 notification_class: 0,
149 notify_type: 0, event_enable: 0,
151 time_delay: 0,
152 event_state: EventState::NORMAL,
153 acked_transitions: 0b111, }
155 }
156}
157
158impl OutOfRangeDetector {
159 const TO_OFFNORMAL: u8 = 0x01;
161 const TO_FAULT: u8 = 0x02;
162 const TO_NORMAL: u8 = 0x04;
163
164 pub fn evaluate(&mut self, present_value: f32) -> Option<EventStateChange> {
172 let new_state = self.compute_new_state(present_value);
173 if new_state != self.event_state {
174 let change = EventStateChange {
175 from: self.event_state,
176 to: new_state,
177 };
178 self.event_state = new_state;
179
180 let enabled = match new_state {
182 s if s == EventState::NORMAL => self.event_enable & Self::TO_NORMAL != 0,
183 s if s == EventState::HIGH_LIMIT || s == EventState::LOW_LIMIT => {
184 self.event_enable & Self::TO_OFFNORMAL != 0
185 }
186 _ => self.event_enable & Self::TO_FAULT != 0,
187 };
188
189 if enabled {
190 Some(change)
191 } else {
192 None
193 }
194 } else {
195 None
196 }
197 }
198
199 fn compute_new_state(&self, pv: f32) -> EventState {
200 let high_enabled = self.limit_enable.high_limit_enable;
201 let low_enabled = self.limit_enable.low_limit_enable;
202
203 match self.event_state {
204 s if s == EventState::NORMAL => {
205 if high_enabled && pv > self.high_limit {
207 return EventState::HIGH_LIMIT;
208 }
209 if low_enabled && pv < self.low_limit {
210 return EventState::LOW_LIMIT;
211 }
212 EventState::NORMAL
213 }
214 s if s == EventState::HIGH_LIMIT => {
215 if low_enabled && pv < self.low_limit {
217 return EventState::LOW_LIMIT;
218 }
219 if pv < self.high_limit - self.deadband {
221 return EventState::NORMAL;
222 }
223 EventState::HIGH_LIMIT
224 }
225 s if s == EventState::LOW_LIMIT => {
226 if high_enabled && pv > self.high_limit {
228 return EventState::HIGH_LIMIT;
229 }
230 if pv > self.low_limit + self.deadband {
232 return EventState::NORMAL;
233 }
234 EventState::LOW_LIMIT
235 }
236 _ => self.event_state, }
238 }
239}
240
241#[derive(Debug, Clone)]
251pub struct ChangeOfStateDetector {
252 pub alarm_values: Vec<u32>,
254 pub notification_class: u32,
255 pub notify_type: u32,
256 pub event_enable: u8,
257 pub time_delay: u32,
258 pub event_state: EventState,
259 pub acked_transitions: u8,
260}
261
262impl Default for ChangeOfStateDetector {
263 fn default() -> Self {
264 Self {
265 alarm_values: Vec::new(),
266 notification_class: 0,
267 notify_type: 0,
268 event_enable: 0,
269 time_delay: 0,
270 event_state: EventState::NORMAL,
271 acked_transitions: 0b111,
272 }
273 }
274}
275
276impl ChangeOfStateDetector {
277 const TO_OFFNORMAL: u8 = 0x01;
278 const TO_FAULT: u8 = 0x02;
279 const TO_NORMAL: u8 = 0x04;
280
281 pub fn evaluate(&mut self, present_value: u32) -> Option<EventStateChange> {
286 let is_alarm = self.alarm_values.contains(&present_value);
287 let new_state = if is_alarm {
288 EventState::OFFNORMAL
289 } else {
290 EventState::NORMAL
291 };
292
293 if new_state != self.event_state {
294 let change = EventStateChange {
295 from: self.event_state,
296 to: new_state,
297 };
298 self.event_state = new_state;
299
300 let enabled = match new_state {
301 s if s == EventState::NORMAL => self.event_enable & Self::TO_NORMAL != 0,
302 s if s == EventState::OFFNORMAL => self.event_enable & Self::TO_OFFNORMAL != 0,
303 _ => self.event_enable & Self::TO_FAULT != 0,
304 };
305
306 if enabled {
307 Some(change)
308 } else {
309 None
310 }
311 } else {
312 None
313 }
314 }
315}
316
317#[derive(Debug, Clone)]
322pub struct CommandFailureDetector {
323 pub notification_class: u32,
324 pub notify_type: u32,
325 pub event_enable: u8,
326 pub time_delay: u32,
327 pub event_state: EventState,
328 pub acked_transitions: u8,
329}
330
331impl Default for CommandFailureDetector {
332 fn default() -> Self {
333 Self {
334 notification_class: 0,
335 notify_type: 0,
336 event_enable: 0,
337 time_delay: 0,
338 event_state: EventState::NORMAL,
339 acked_transitions: 0b111,
340 }
341 }
342}
343
344impl CommandFailureDetector {
345 const TO_OFFNORMAL: u8 = 0x01;
346 #[allow(dead_code)]
347 const TO_FAULT: u8 = 0x02;
348 const TO_NORMAL: u8 = 0x04;
349
350 pub fn evaluate(
354 &mut self,
355 present_value: u32,
356 feedback_value: u32,
357 ) -> Option<EventStateChange> {
358 let new_state = if present_value != feedback_value {
359 EventState::OFFNORMAL
360 } else {
361 EventState::NORMAL
362 };
363
364 if new_state != self.event_state {
365 let change = EventStateChange {
366 from: self.event_state,
367 to: new_state,
368 };
369 self.event_state = new_state;
370
371 let enabled = match new_state {
372 s if s == EventState::NORMAL => self.event_enable & Self::TO_NORMAL != 0,
373 s if s == EventState::OFFNORMAL => self.event_enable & Self::TO_OFFNORMAL != 0,
374 _ => false,
375 };
376
377 if enabled {
378 Some(change)
379 } else {
380 None
381 }
382 } else {
383 None
384 }
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 fn make_detector() -> OutOfRangeDetector {
393 OutOfRangeDetector {
394 high_limit: 80.0,
395 low_limit: 20.0,
396 deadband: 2.0,
397 limit_enable: LimitEnable::BOTH,
398 notification_class: 1,
399 notify_type: 0,
400 event_enable: 0x07, time_delay: 0,
402 event_state: EventState::NORMAL,
403 acked_transitions: 0b111,
404 }
405 }
406
407 #[test]
408 fn normal_stays_normal_within_limits() {
409 let mut det = make_detector();
410 assert!(det.evaluate(50.0).is_none());
411 assert_eq!(det.event_state, EventState::NORMAL);
412 }
413
414 #[test]
415 fn normal_to_high_limit() {
416 let mut det = make_detector();
417 let change = det.evaluate(81.0).unwrap();
418 assert_eq!(change.from, EventState::NORMAL);
419 assert_eq!(change.to, EventState::HIGH_LIMIT);
420 assert_eq!(det.event_state, EventState::HIGH_LIMIT);
421 }
422
423 #[test]
424 fn normal_to_low_limit() {
425 let mut det = make_detector();
426 let change = det.evaluate(19.0).unwrap();
427 assert_eq!(change.from, EventState::NORMAL);
428 assert_eq!(change.to, EventState::LOW_LIMIT);
429 assert_eq!(det.event_state, EventState::LOW_LIMIT);
430 }
431
432 #[test]
433 fn at_boundary_no_transition() {
434 let mut det = make_detector();
435 assert!(det.evaluate(80.0).is_none());
437 assert!(det.evaluate(20.0).is_none());
439 }
440
441 #[test]
442 fn high_limit_to_normal_with_deadband() {
443 let mut det = make_detector();
444 det.evaluate(81.0); assert!(det.evaluate(79.0).is_none());
448
449 let change = det.evaluate(77.0).unwrap();
451 assert_eq!(change.from, EventState::HIGH_LIMIT);
452 assert_eq!(change.to, EventState::NORMAL);
453 }
454
455 #[test]
456 fn low_limit_to_normal_with_deadband() {
457 let mut det = make_detector();
458 det.evaluate(19.0); assert!(det.evaluate(21.0).is_none());
462
463 let change = det.evaluate(23.0).unwrap();
465 assert_eq!(change.from, EventState::LOW_LIMIT);
466 assert_eq!(change.to, EventState::NORMAL);
467 }
468
469 #[test]
470 fn high_limit_to_low_limit_direct() {
471 let mut det = make_detector();
472 det.evaluate(81.0); let change = det.evaluate(19.0).unwrap();
476 assert_eq!(change.from, EventState::HIGH_LIMIT);
477 assert_eq!(change.to, EventState::LOW_LIMIT);
478 }
479
480 #[test]
481 fn low_limit_to_high_limit_direct() {
482 let mut det = make_detector();
483 det.evaluate(19.0); let change = det.evaluate(81.0).unwrap();
487 assert_eq!(change.from, EventState::LOW_LIMIT);
488 assert_eq!(change.to, EventState::HIGH_LIMIT);
489 }
490
491 #[test]
492 fn high_limit_disabled_no_transition() {
493 let mut det = make_detector();
494 det.limit_enable.high_limit_enable = false;
495
496 assert!(det.evaluate(100.0).is_none());
498 }
499
500 #[test]
501 fn low_limit_disabled_no_transition() {
502 let mut det = make_detector();
503 det.limit_enable.low_limit_enable = false;
504
505 assert!(det.evaluate(0.0).is_none());
507 }
508
509 #[test]
510 fn both_limits_disabled() {
511 let mut det = make_detector();
512 det.limit_enable = LimitEnable::NONE;
513 assert!(det.evaluate(100.0).is_none());
514 assert!(det.evaluate(0.0).is_none());
515 }
516
517 #[test]
518 fn limit_enable_bits_round_trip() {
519 let le = LimitEnable::BOTH;
520 let bits = le.to_bits();
521 let decoded = LimitEnable::from_bits(bits);
522 assert_eq!(decoded, le);
523
524 let le = LimitEnable {
525 low_limit_enable: true,
526 high_limit_enable: false,
527 };
528 let bits = le.to_bits();
529 let decoded = LimitEnable::from_bits(bits);
530 assert_eq!(decoded, le);
531 }
532
533 #[test]
534 fn deadband_at_exact_boundary() {
535 let mut det = make_detector();
536 det.evaluate(81.0); assert!(det.evaluate(78.0).is_none());
540
541 let change = det.evaluate(77.99).unwrap();
543 assert_eq!(change.to, EventState::NORMAL);
544 }
545
546 #[test]
547 fn event_state_change_derives_event_type() {
548 use bacnet_types::enums::EventType;
549
550 let change = EventStateChange {
551 from: EventState::NORMAL,
552 to: EventState::HIGH_LIMIT,
553 };
554 assert_eq!(change.event_type(), EventType::OUT_OF_RANGE);
555 }
556
557 #[test]
558 fn event_state_change_to_normal_from_high() {
559 use bacnet_types::enums::EventType;
560
561 let change = EventStateChange {
562 from: EventState::HIGH_LIMIT,
563 to: EventState::NORMAL,
564 };
565 assert_eq!(change.event_type(), EventType::OUT_OF_RANGE);
566 }
567
568 #[test]
569 fn event_enable_zero_suppresses_all_notifications() {
570 let mut det = make_detector();
571 det.event_enable = 0x00; assert!(det.evaluate(81.0).is_none());
575 assert_eq!(det.event_state, EventState::HIGH_LIMIT); assert!(det.evaluate(50.0).is_none());
578 assert_eq!(det.event_state, EventState::NORMAL); assert!(det.evaluate(19.0).is_none());
581 assert_eq!(det.event_state, EventState::LOW_LIMIT); }
583
584 #[test]
585 fn event_enable_to_normal_only() {
586 let mut det = make_detector();
587 det.event_enable = 0x04; assert!(det.evaluate(81.0).is_none());
591 assert_eq!(det.event_state, EventState::HIGH_LIMIT);
592
593 let change = det.evaluate(50.0).unwrap();
595 assert_eq!(change.from, EventState::HIGH_LIMIT);
596 assert_eq!(change.to, EventState::NORMAL);
597
598 assert!(det.evaluate(19.0).is_none());
600 assert_eq!(det.event_state, EventState::LOW_LIMIT);
601
602 let change = det.evaluate(50.0).unwrap();
604 assert_eq!(change.from, EventState::LOW_LIMIT);
605 assert_eq!(change.to, EventState::NORMAL);
606 }
607
608 #[test]
609 fn event_enable_to_offnormal_only() {
610 let mut det = make_detector();
611 det.event_enable = 0x01; let change = det.evaluate(81.0).unwrap();
615 assert_eq!(change.to, EventState::HIGH_LIMIT);
616
617 assert!(det.evaluate(50.0).is_none());
619 assert_eq!(det.event_state, EventState::NORMAL);
620 }
621
622 #[test]
623 fn event_state_change_generic() {
624 use bacnet_types::enums::EventType;
625
626 let change = EventStateChange {
627 from: EventState::NORMAL,
628 to: EventState::NORMAL,
629 };
630 assert_eq!(change.event_type(), EventType::CHANGE_OF_STATE);
631 }
632
633 #[test]
636 fn cos_normal_when_no_alarm_values() {
637 let mut det = ChangeOfStateDetector {
638 event_enable: 0x07,
639 ..Default::default()
640 };
641 assert!(det.evaluate(0).is_none()); }
643
644 #[test]
645 fn cos_normal_to_offnormal() {
646 let mut det = ChangeOfStateDetector {
647 alarm_values: vec![1], event_enable: 0x07,
649 ..Default::default()
650 };
651 let change = det.evaluate(1).unwrap();
652 assert_eq!(change.from, EventState::NORMAL);
653 assert_eq!(change.to, EventState::OFFNORMAL);
654 }
655
656 #[test]
657 fn cos_offnormal_to_normal() {
658 let mut det = ChangeOfStateDetector {
659 alarm_values: vec![1],
660 event_enable: 0x07,
661 ..Default::default()
662 };
663 det.evaluate(1); let change = det.evaluate(0).unwrap(); assert_eq!(change.from, EventState::OFFNORMAL);
666 assert_eq!(change.to, EventState::NORMAL);
667 }
668
669 #[test]
670 fn cos_stays_offnormal_while_in_alarm() {
671 let mut det = ChangeOfStateDetector {
672 alarm_values: vec![1],
673 event_enable: 0x07,
674 ..Default::default()
675 };
676 det.evaluate(1); assert!(det.evaluate(1).is_none()); }
679
680 #[test]
681 fn cos_multistate_alarm_values() {
682 let mut det = ChangeOfStateDetector {
683 alarm_values: vec![3, 5, 7], event_enable: 0x07,
685 ..Default::default()
686 };
687 assert!(det.evaluate(1).is_none()); let change = det.evaluate(5).unwrap();
689 assert_eq!(change.to, EventState::OFFNORMAL);
690 assert!(det.evaluate(3).is_none()); let change = det.evaluate(2).unwrap();
692 assert_eq!(change.to, EventState::NORMAL);
693 }
694
695 #[test]
698 fn cmdfail_matching_stays_normal() {
699 let mut det = CommandFailureDetector {
700 event_enable: 0x07,
701 ..Default::default()
702 };
703 assert!(det.evaluate(1, 1).is_none()); }
705
706 #[test]
707 fn cmdfail_mismatch_goes_offnormal() {
708 let mut det = CommandFailureDetector {
709 event_enable: 0x07,
710 ..Default::default()
711 };
712 let change = det.evaluate(1, 0).unwrap(); assert_eq!(change.to, EventState::OFFNORMAL);
714 }
715
716 #[test]
717 fn cmdfail_match_restores_normal() {
718 let mut det = CommandFailureDetector {
719 event_enable: 0x07,
720 ..Default::default()
721 };
722 det.evaluate(1, 0); let change = det.evaluate(1, 1).unwrap(); assert_eq!(change.to, EventState::NORMAL);
725 }
726}