2025-08-12 07:21:41 +08:00

337 lines
11 KiB
Rust

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<chrono::Utc>,
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<StoredFrame>,
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<f64> {
// 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::<f64>() / 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::<f64>() / 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::<f64>()
/ 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);
}
}