use anyhow::{Result, Context}; use std::collections::VecDeque; use tokio::time::{sleep, Duration}; use crate::events::{EventBus, SystemEvent, FrameCapturedEvent, MeteorDetectedEvent}; /// Configuration for the detection controller #[derive(Debug, Clone)] pub struct DetectionConfig { pub algorithm_name: String, pub brightness_threshold: f32, pub buffer_capacity: usize, pub min_event_frames: usize, pub max_event_gap_frames: usize, } impl Default for DetectionConfig { fn default() -> Self { Self { algorithm_name: "brightness_diff".to_string(), brightness_threshold: 0.3, buffer_capacity: 150, min_event_frames: 3, max_event_gap_frames: 10, } } } /// Available detection algorithms #[derive(Debug, Clone)] pub enum DetectionAlgorithm { BrightnessDiff, // Future algorithms can be added here } /// Stored frame information for analysis #[derive(Debug, Clone)] struct StoredFrame { frame_id: u64, timestamp: chrono::DateTime, width: u32, height: u32, brightness_score: f64, // Simplified representation for analysis } /// Detection controller that analyzes frames for meteors pub struct DetectionController { config: DetectionConfig, event_bus: EventBus, frame_buffer: VecDeque, last_processed_frame_id: u64, } impl DetectionController { /// Create a new detection controller pub fn new(config: DetectionConfig, event_bus: EventBus) -> Self { let buffer_capacity = config.buffer_capacity; Self { config, event_bus, frame_buffer: VecDeque::with_capacity(buffer_capacity), last_processed_frame_id: 0, } } /// Start the detection loop pub async fn run(&mut self) -> Result<()> { println!("🔍 Starting meteor detection controller..."); println!(" Buffer size: {} frames", self.config.buffer_capacity); println!(" Algorithm: {}", self.config.algorithm_name); println!(" Brightness threshold: {}", self.config.brightness_threshold); println!(" Min event frames: {}", self.config.min_event_frames); let mut event_receiver = self.event_bus.subscribe(); let check_interval = Duration::from_millis(100); // Fixed 100ms check interval println!("✅ Detection controller initialized, starting analysis loop..."); loop { tokio::select! { // Handle incoming events event_result = event_receiver.recv() => { match event_result { Ok(event) => { if let Err(e) = self.handle_event(event.as_ref()).await { eprintln!("❌ Error handling event: {}", e); } } Err(e) => { eprintln!("❌ Error receiving event: {}", e); tokio::time::sleep(Duration::from_secs(1)).await; } } } // Periodic analysis check _ = sleep(check_interval) => { if let Err(e) = self.run_detection_analysis().await { eprintln!("❌ Error in detection analysis: {}", e); } } } } } /// Handle incoming events from the event bus async fn handle_event(&mut self, event: &SystemEvent) -> Result<()> { match event { SystemEvent::FrameCaptured(frame_event) => { self.process_frame_event(frame_event.clone()).await?; } SystemEvent::SystemStarted(_) => { println!("🔍 Detection controller received system started event"); } SystemEvent::MeteorDetected(_) => { // We don't need to process our own detections } SystemEvent::EventPackageArchived(_) => { // Detection controller doesn't need to handle archived events } } Ok(()) } /// Process a captured frame event and add to buffer async fn process_frame_event(&mut self, frame_event: FrameCapturedEvent) -> Result<()> { // Calculate brightness score (simplified analysis) let brightness_score = self.calculate_brightness_score(&frame_event)?; let stored_frame = StoredFrame { frame_id: frame_event.frame_id, timestamp: frame_event.timestamp, width: frame_event.frame_data.width, height: frame_event.frame_data.height, brightness_score, }; // Add to circular buffer self.frame_buffer.push_back(stored_frame); // Maintain buffer size while self.frame_buffer.len() > self.config.buffer_capacity { self.frame_buffer.pop_front(); } // Update last processed frame ID self.last_processed_frame_id = frame_event.frame_id; if frame_event.frame_id % 50 == 0 { println!("🔍 Processed {} frames, buffer size: {}", frame_event.frame_id, self.frame_buffer.len() ); } Ok(()) } /// Calculate brightness score from frame data (simplified) fn calculate_brightness_score(&self, frame_event: &FrameCapturedEvent) -> Result { // Simplified brightness calculation based on frame data content // In our synthetic JPEG format, the brightness is encoded in the pixel values if frame_event.frame_data.len() < 8 { // Need at least header + some data return Ok(0.0); } // Skip the fake JPEG header (first 4 bytes) and footer (last 2 bytes) let data_start = 4; let frame_data_slice = frame_event.frame_data.as_slice(); let data_end = frame_data_slice.len().saturating_sub(2); if data_start >= data_end { return Ok(0.0); } // Calculate average pixel value (brightness) from the data section let pixel_data = &frame_data_slice[data_start..data_end]; let average_brightness = pixel_data.iter() .map(|&b| b as f64) .sum::() / pixel_data.len() as f64; // Normalize to 0.0-1.0 range (assuming 255 is max brightness) let score = (average_brightness / 255.0).min(1.0).max(0.0); Ok(score) } /// Run detection analysis on the frame buffer async fn run_detection_analysis(&mut self) -> Result<()> { if self.frame_buffer.len() < 10 { // Need at least 10 frames for analysis return Ok(()); } match self.config.algorithm_name.as_str() { "brightness_diff" => { self.run_brightness_diff_detection().await?; } _ => { eprintln!("Unknown detection algorithm: {}", self.config.algorithm_name); } } Ok(()) } /// Run brightness difference detection algorithm async fn run_brightness_diff_detection(&mut self) -> Result<()> { let frames: Vec<&StoredFrame> = self.frame_buffer.iter().collect(); // Need at least 10 frames for reliable detection if frames.len() < 10 { return Ok(()); } // Calculate average brightness of historical frames (excluding recent ones) let history_end = frames.len().saturating_sub(3); // Exclude last 3 frames if history_end < 5 { return Ok(()); } let historical_avg = frames[..history_end] .iter() .map(|f| f.brightness_score) .sum::() / history_end as f64; // Check recent frames for significant brightness increase for recent_frame in &frames[history_end..] { let brightness_diff = recent_frame.brightness_score - historical_avg; let relative_increase = if historical_avg > 0.0 { brightness_diff / historical_avg } else { brightness_diff }; // Use relative increase as confidence let confidence = relative_increase.max(0.0).min(1.0); // Debug output if frames.len() >= 15 { println!("🔍 DEBUG: Frame #{}, Historical avg: {:.3}, Current: {:.3}, Diff: {:.3}, Confidence: {:.3}", recent_frame.frame_id, historical_avg, recent_frame.brightness_score, brightness_diff, confidence ); } if confidence >= self.config.brightness_threshold as f64 { // Potential meteor detected! let detection_event = MeteorDetectedEvent::new( recent_frame.frame_id, recent_frame.timestamp, confidence, "brightness_diff_v1".to_string(), ); println!("🌟 METEOR DETECTED! Frame #{}, Confidence: {:.2}", recent_frame.frame_id, confidence ); self.event_bus.publish_meteor_detected(detection_event) .context("Failed to publish meteor detection event")?; // Prevent duplicate detections for a short period // by clearing recent frames from analysis break; } } Ok(()) } /// Get current buffer statistics pub fn get_stats(&self) -> DetectionStats { DetectionStats { buffer_size: self.frame_buffer.len(), buffer_capacity: self.config.buffer_capacity, last_processed_frame_id: self.last_processed_frame_id, avg_brightness: if !self.frame_buffer.is_empty() { self.frame_buffer.iter().map(|f| f.brightness_score).sum::() / self.frame_buffer.len() as f64 } else { 0.0 }, } } } /// Statistics about the detection system #[derive(Debug, Clone)] pub struct DetectionStats { pub buffer_size: usize, pub buffer_capacity: usize, pub last_processed_frame_id: u64, pub avg_brightness: f64, } #[cfg(test)] mod tests { use super::*; use crate::events::EventBus; #[test] fn test_detection_config_default() { let config = DetectionConfig::default(); assert_eq!(config.buffer_capacity, 150); assert_eq!(config.brightness_threshold, 0.3); assert_eq!(config.min_event_frames, 3); assert_eq!(config.algorithm_name, "brightness_diff"); } #[test] fn test_detection_controller_creation() { let config = DetectionConfig::default(); let event_bus = EventBus::new(100); let controller = DetectionController::new(config, event_bus); assert_eq!(controller.frame_buffer.len(), 0); assert_eq!(controller.last_processed_frame_id, 0); } #[test] fn test_detection_stats() { let config = DetectionConfig::default(); let event_bus = EventBus::new(100); let controller = DetectionController::new(config, event_bus); let stats = controller.get_stats(); assert_eq!(stats.buffer_size, 0); assert_eq!(stats.buffer_capacity, 100); assert_eq!(stats.last_processed_frame_id, 0); assert_eq!(stats.avg_brightness, 0.0); } }