//! Clean room monitoring — ADR-041 Category 5 Industrial module. //! //! Personnel count and movement tracking for cleanroom contamination control //! per ISO 14644 standards. //! //! Features: //! - Real-time occupancy count tracking //! - Configurable maximum occupancy enforcement (default 4) //! - Turbulent motion detection (rapid movement that disturbs laminar airflow) //! - Periodic compliance reports //! //! Budget: L (<2 ms per frame). Event IDs 520-523. /// Default maximum allowed occupancy. const DEFAULT_MAX_OCCUPANCY: u8 = 4; /// Motion energy threshold for turbulent movement. /// Normal cleanroom movement is slow and deliberate. const TURBULENT_MOTION_THRESH: f32 = 0.6; /// Debounce frames for occupancy violation. const VIOLATION_DEBOUNCE: u8 = 10; /// Debounce frames for turbulent motion. const TURBULENT_DEBOUNCE: u8 = 3; /// Compliance report interval (frames, ~30 seconds at 20 Hz). const COMPLIANCE_REPORT_INTERVAL: u32 = 600; /// Cooldown after occupancy violation alert (frames). const VIOLATION_COOLDOWN: u16 = 200; /// Cooldown after turbulent motion alert (frames). const TURBULENT_COOLDOWN: u16 = 100; /// Event IDs (520-series: Industrial/Clean Room). pub const EVENT_OCCUPANCY_COUNT: i32 = 520; pub const EVENT_OCCUPANCY_VIOLATION: i32 = 521; pub const EVENT_TURBULENT_MOTION: i32 = 522; pub const EVENT_COMPLIANCE_REPORT: i32 = 523; /// Clean room monitor. pub struct CleanRoomMonitor { /// Maximum allowed occupancy. max_occupancy: u8, /// Current smoothed person count. current_count: u8, /// Previous reported count (for change detection). prev_count: u8, /// Occupancy violation debounce counter. violation_debounce: u8, /// Turbulent motion debounce counter. turbulent_debounce: u8, /// Violation cooldown. violation_cooldown: u16, /// Turbulent cooldown. turbulent_cooldown: u16, /// Frame counter. frame_count: u32, /// Frames in compliance (occupancy <= max). compliant_frames: u32, /// Total frames while room is occupied. occupied_frames: u32, /// Total violation events. total_violations: u32, /// Total turbulent events. total_turbulent: u32, } impl CleanRoomMonitor { pub const fn new() -> Self { Self { max_occupancy: DEFAULT_MAX_OCCUPANCY, current_count: 0, prev_count: 0, violation_debounce: 0, turbulent_debounce: 0, violation_cooldown: 0, turbulent_cooldown: 0, frame_count: 0, compliant_frames: 0, occupied_frames: 0, total_violations: 0, total_turbulent: 0, } } /// Create with custom maximum occupancy. pub const fn with_max_occupancy(max: u8) -> Self { Self { max_occupancy: max, current_count: 0, prev_count: 0, violation_debounce: 0, turbulent_debounce: 0, violation_cooldown: 0, turbulent_cooldown: 0, frame_count: 0, compliant_frames: 0, occupied_frames: 0, total_violations: 0, total_turbulent: 0, } } /// Process one frame. /// /// # Arguments /// - `n_persons`: host-reported person count /// - `presence`: host-reported presence flag (0/1) /// - `motion_energy`: host-reported motion energy /// /// Returns events as `(event_id, value)` pairs. pub fn process_frame( &mut self, n_persons: i32, presence: i32, motion_energy: f32, ) -> &[(i32, f32)] { self.frame_count += 1; if self.violation_cooldown > 0 { self.violation_cooldown -= 1; } if self.turbulent_cooldown > 0 { self.turbulent_cooldown -= 1; } // Clamp person count to reasonable range. let count = if n_persons < 0 { 0u8 } else if n_persons > 255 { 255u8 } else { n_persons as u8 }; self.prev_count = self.current_count; self.current_count = count; // Track compliance. if count > 0 { self.occupied_frames += 1; if count <= self.max_occupancy { self.compliant_frames += 1; } } static mut EVENTS: [(i32, f32); 4] = [(0, 0.0); 4]; let mut n_events = 0usize; // --- Step 1: Emit count changes --- if count != self.prev_count && n_events < 4 { unsafe { EVENTS[n_events] = (EVENT_OCCUPANCY_COUNT, count as f32); } n_events += 1; } // --- Step 2: Occupancy violation --- if count > self.max_occupancy { self.violation_debounce = self.violation_debounce.saturating_add(1); if self.violation_debounce >= VIOLATION_DEBOUNCE && self.violation_cooldown == 0 && n_events < 4 { self.total_violations += 1; self.violation_cooldown = VIOLATION_COOLDOWN; // Value encodes: count * 10 + max_allowed. let val = count as f32; unsafe { EVENTS[n_events] = (EVENT_OCCUPANCY_VIOLATION, val); } n_events += 1; } } else { self.violation_debounce = 0; } // --- Step 3: Turbulent motion detection --- if motion_energy > TURBULENT_MOTION_THRESH && presence > 0 { self.turbulent_debounce = self.turbulent_debounce.saturating_add(1); if self.turbulent_debounce >= TURBULENT_DEBOUNCE && self.turbulent_cooldown == 0 && n_events < 4 { self.total_turbulent += 1; self.turbulent_cooldown = TURBULENT_COOLDOWN; unsafe { EVENTS[n_events] = (EVENT_TURBULENT_MOTION, motion_energy); } n_events += 1; } } else { self.turbulent_debounce = 0; } // --- Step 4: Periodic compliance report --- if self.frame_count % COMPLIANCE_REPORT_INTERVAL == 0 && n_events < 4 { let compliance_pct = if self.occupied_frames > 0 { (self.compliant_frames as f32 / self.occupied_frames as f32) * 100.0 } else { 100.0 }; unsafe { EVENTS[n_events] = (EVENT_COMPLIANCE_REPORT, compliance_pct); } n_events += 1; } unsafe { &EVENTS[..n_events] } } /// Current occupancy count. pub fn current_count(&self) -> u8 { self.current_count } /// Maximum allowed occupancy. pub fn max_occupancy(&self) -> u8 { self.max_occupancy } /// Whether currently in violation. pub fn is_in_violation(&self) -> bool { self.current_count > self.max_occupancy } /// Compliance percentage (0-100). pub fn compliance_percent(&self) -> f32 { if self.occupied_frames == 0 { return 100.0; } (self.compliant_frames as f32 / self.occupied_frames as f32) * 100.0 } /// Total number of violation events. pub fn total_violations(&self) -> u32 { self.total_violations } } #[cfg(test)] mod tests { use super::*; #[test] fn test_init_state() { let mon = CleanRoomMonitor::new(); assert_eq!(mon.current_count(), 0); assert_eq!(mon.max_occupancy(), DEFAULT_MAX_OCCUPANCY); assert!(!mon.is_in_violation()); assert!((mon.compliance_percent() - 100.0).abs() < 0.01); } #[test] fn test_custom_max_occupancy() { let mon = CleanRoomMonitor::with_max_occupancy(2); assert_eq!(mon.max_occupancy(), 2); } #[test] fn test_occupancy_count_change() { let mut mon = CleanRoomMonitor::new(); // First frame with 2 persons. let events = mon.process_frame(2, 1, 0.1); let mut count_event = false; for &(et, val) in events { if et == EVENT_OCCUPANCY_COUNT { count_event = true; assert!((val - 2.0).abs() < 0.01); } } assert!(count_event, "should emit count change event"); assert_eq!(mon.current_count(), 2); } #[test] fn test_occupancy_violation() { let mut mon = CleanRoomMonitor::with_max_occupancy(3); let mut violation_detected = false; // Feed frames with 5 persons (over limit of 3). for _ in 0..20 { let events = mon.process_frame(5, 1, 0.1); for &(et, _) in events { if et == EVENT_OCCUPANCY_VIOLATION { violation_detected = true; } } } assert!(violation_detected, "violation should be detected when over max"); assert!(mon.is_in_violation()); assert!(mon.total_violations() >= 1); } #[test] fn test_no_violation_under_limit() { let mut mon = CleanRoomMonitor::with_max_occupancy(4); for _ in 0..50 { let events = mon.process_frame(3, 1, 0.1); for &(et, _) in events { assert!(et != EVENT_OCCUPANCY_VIOLATION, "no violation when under limit"); } } assert!(!mon.is_in_violation()); } #[test] fn test_turbulent_motion() { let mut mon = CleanRoomMonitor::new(); let mut turbulent_detected = false; // Feed frames with high motion energy. for _ in 0..10 { let events = mon.process_frame(2, 1, 0.8); for &(et, val) in events { if et == EVENT_TURBULENT_MOTION { turbulent_detected = true; assert!(val > TURBULENT_MOTION_THRESH); } } } assert!(turbulent_detected, "turbulent motion should be detected"); } #[test] fn test_compliance_report() { let mut mon = CleanRoomMonitor::with_max_occupancy(4); let mut compliance_reported = false; // Run for COMPLIANCE_REPORT_INTERVAL frames. for _ in 0..COMPLIANCE_REPORT_INTERVAL + 1 { let events = mon.process_frame(3, 1, 0.1); for &(et, val) in events { if et == EVENT_COMPLIANCE_REPORT { compliance_reported = true; assert!((val - 100.0).abs() < 0.01, "should be 100% compliant"); } } } assert!(compliance_reported, "compliance report should be emitted periodically"); } #[test] fn test_compliance_degrades_with_violations() { let mut mon = CleanRoomMonitor::with_max_occupancy(2); // 50 frames compliant. for _ in 0..50 { mon.process_frame(1, 1, 0.1); } // 50 frames in violation. for _ in 0..50 { mon.process_frame(5, 1, 0.1); } let pct = mon.compliance_percent(); assert!(pct < 100.0 && pct > 0.0, "compliance should be partial, got {}%", pct); assert!((pct - 50.0).abs() < 1.0, "expect ~50% compliance, got {}%", pct); } }