//! UPP protocol implementation for the Geek szitman supercamera mod frame; mod jpeg; mod parser; pub use frame::{UPPFrame, UPPFrameHeader, UPPUsbFrame}; pub use jpeg::JpegParser; pub use parser::UPPParser; use crate::error::Result; use serde::{Deserialize, Serialize}; use std::sync::Arc; use std::sync::Mutex; use tracing::trace; /// UPP protocol constants pub const UPP_USB_MAGIC: u16 = 0xBBAA; pub const UPP_CAMID_7: u8 = 7; /// UPP camera instance pub struct UPPCamera { parser: Arc, jpeg_parser: Arc, frame_buffer: Arc>>, current_frame_id: Arc>>, frame_callbacks: Arc>>>, button_callbacks: Arc>>>, // Buffer for assembling frames across USB reads input_buffer: Arc>>, } /// Frame callback trait pub trait FrameCallback { /// Called when a complete frame is received fn on_frame(&self, frame: &UPPFrame); } /// Button callback trait pub trait ButtonCallback { /// Called when a button press is detected fn on_button_press(&self); } impl UPPCamera { /// Create a new UPP camera instance pub fn new() -> Self { Self { parser: Arc::new(UPPParser::new()), jpeg_parser: Arc::new(JpegParser::new()), frame_buffer: Arc::new(Mutex::new(Vec::new())), current_frame_id: Arc::new(Mutex::new(None)), frame_callbacks: Arc::new(Mutex::new(Vec::new())), button_callbacks: Arc::new(Mutex::new(Vec::new())), input_buffer: Arc::new(Mutex::new(Vec::new())), } } /// Create a new UPP camera instance with debug enabled pub fn new_with_debug(enable_debug: bool) -> Self { Self { parser: Arc::new(UPPParser::new_with_debug(enable_debug)), jpeg_parser: Arc::new(JpegParser::new_with_debug(enable_debug)), frame_buffer: Arc::new(Mutex::new(Vec::new())), current_frame_id: Arc::new(Mutex::new(None)), frame_callbacks: Arc::new(Mutex::new(Vec::new())), button_callbacks: Arc::new(Mutex::new(Vec::new())), input_buffer: Arc::new(Mutex::new(Vec::new())), } } /// Enable or disable debug mode pub fn set_debug_mode(&self, enable: bool) { // Create a new parser with the desired debug setting let _new_parser = Arc::new(UPPParser::new_with_debug(enable)); // Replace the parser (this requires interior mutability or a different approach) // For now, we'll just log the change tracing::info!("Debug mode {} for UPP parser", if enable { "enabled" } else { "disabled" }); } } impl Default for UPPCamera { fn default() -> Self { Self::new() } } impl UPPCamera { /// Handle incoming UPP frame data pub fn handle_frame(&self, data: &[u8]) -> Result<()> { // Reduce logging frequency - only log every 100th frame static mut FRAME_COUNT: u32 = 0; unsafe { FRAME_COUNT += 1; if FRAME_COUNT % 100 == 0 { trace!("Handling frame data: {} bytes", data.len()); } } // Backward-compatible: feed bytes and process all parsed frames for frame in self.feed_bytes(data)? { self.process_frame(frame)?; } Ok(()) } /// Handle incoming UPP frame data with better error handling pub fn handle_frame_robust(&self, data: &[u8]) -> Result<()> { trace!("Handling frame data robustly: {} bytes", data.len()); // Streaming-friendly: feed bytes and process all parsed frames let frames = self.feed_bytes(data)?; for frame in frames { self.process_frame(frame)?; } Ok(()) } /// Feed raw bytes from USB and extract as many complete protocol frames as possible. /// Based on C++ POC: frames are chunked across multiple USB reads and need assembly. pub fn feed_bytes(&self, chunk: &[u8]) -> Result> { let mut out_frames = Vec::new(); let mut buffer = self.input_buffer.lock().unwrap(); buffer.extend_from_slice(chunk); // Parse loop: find and assemble frames from buffer loop { // Need at least 5 bytes for USB header if buffer.len() < 5 { break; } // Search for magic 0xBBAA (little-endian) let mut start_index = None; for i in 0..=buffer.len() - 2 { let magic = u16::from_le_bytes([buffer[i], buffer[i + 1]]); if magic == UPP_USB_MAGIC { start_index = Some(i); break; } } let Some(start) = start_index else { // No magic found; drop buffer to avoid infinite growth buffer.clear(); break; }; // Drop any leading garbage before magic if start > 0 { buffer.drain(0..start); } // Re-check size for header if buffer.len() < 5 { break; } // Parse USB header fields directly from bytes let _magic = u16::from_le_bytes([buffer[0], buffer[1]]); let _cid = buffer[2]; let payload_length = u16::from_le_bytes([buffer[3], buffer[4]]) as usize; let total_frame_size = 5 + payload_length; // USB header (5) + payload if buffer.len() < total_frame_size { // Wait for more data next call break; } // We have a complete frame; extract it let frame_bytes: Vec = buffer.drain(0..total_frame_size).collect(); // Parse the complete frame match self.parser.parse_frame(&frame_bytes) { Ok(frame) => { out_frames.push(frame); } Err(e) => { tracing::warn!("Failed to parse complete frame: {}", e); // Continue processing other frames } } } Ok(out_frames) } /// Process a parsed UPP frame with frame assembly logic /// Based on C++ POC: accumulate frame data until frame ID changes fn process_frame(&self, frame: UPPFrame) -> Result<()> { let mut frame_buffer = self.frame_buffer.lock().unwrap(); let mut current_frame_id = self.current_frame_id.lock().unwrap(); // Check if this is a new frame (frame ID changed) if let Some(id) = *current_frame_id { if id != frame.header.frame_id { // New frame started, process the previous one if !frame_buffer.is_empty() { self.notify_frame_complete(&frame_buffer); frame_buffer.clear(); } } } // Update current frame ID *current_frame_id = Some(frame.header.frame_id); // Add frame data to buffer (accumulate chunks) frame_buffer.extend_from_slice(&frame.data); // Check for button press if frame.header.flags.button_press { self.notify_button_press(); } Ok(()) } /// Notify frame callbacks of complete frame fn notify_frame_complete(&self, frame_data: &[u8]) { let frame_callbacks = self.frame_callbacks.lock().unwrap(); for callback in frame_callbacks.iter() { callback.on_frame(&UPPFrame { header: UPPFrameHeader::new(0, 0, false, false, 0), data: frame_data.to_vec(), }); } } /// Notify button callbacks of button press fn notify_button_press(&self) { let button_callbacks = self.button_callbacks.lock().unwrap(); for callback in button_callbacks.iter() { callback.on_button_press(); } } /// Add a frame callback pub fn add_frame_callback(&self, callback: F) where F: FrameCallback + Send + Sync + 'static, { let mut frame_callbacks = self.frame_callbacks.lock().unwrap(); frame_callbacks.push(Box::new(callback)); } /// Add a button callback pub fn add_button_callback(&self, callback: F) where F: ButtonCallback + Send + Sync + 'static, { let mut button_callbacks = self.button_callbacks.lock().unwrap(); button_callbacks.push(Box::new(callback)); } /// Get the complete frame if available pub fn get_complete_frame(&self) -> Option> { let frame_buffer = self.frame_buffer.lock().unwrap(); if frame_buffer.is_empty() { None } else { // Check if the frame is a complete JPEG before returning it if self.jpeg_parser.is_complete_jpeg(&frame_buffer) { Some(frame_buffer.clone()) } else { None } } } /// Get the current frame buffer size (for debugging frame assembly) pub fn get_frame_buffer_size(&self) -> usize { let frame_buffer = self.frame_buffer.lock().unwrap(); frame_buffer.len() } /// Get the current input buffer size (for debugging chunk accumulation) pub fn get_input_buffer_size(&self) -> usize { let input_buffer = self.input_buffer.lock().unwrap(); input_buffer.len() } /// Clear the frame buffer pub fn clear_frame_buffer(&self) { let mut frame_buffer = self.frame_buffer.lock().unwrap(); frame_buffer.clear(); } /// Get frame statistics pub fn get_stats(&self) -> UPPStats { let frame_buffer = self.frame_buffer.lock().unwrap(); let current_frame_id = self.current_frame_id.lock().unwrap(); UPPStats { buffer_size: frame_buffer.len(), current_frame_id: *current_frame_id, total_frames_processed: 0, // TODO: implement counter } } } /// UPP protocol statistics #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct UPPStats { pub buffer_size: usize, pub current_frame_id: Option, pub total_frames_processed: u64, } /// UPP camera configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UPPConfig { pub expected_camera_id: u8, pub max_frame_size: usize, pub enable_debug: bool, } impl Default for UPPConfig { fn default() -> Self { Self { expected_camera_id: UPP_CAMID_7, max_frame_size: 0x10000, // 64KB enable_debug: false, } } } #[cfg(test)] mod tests { use super::*; use std::sync::Arc; // Mock frame callback for testing struct MockFrameCallback { frame_count: Arc>, } impl FrameCallback for MockFrameCallback { fn on_frame(&self, _frame: &UPPFrame) { let mut count = self.frame_count.lock().unwrap(); *count += 1; } } // Mock button callback for testing struct MockButtonCallback { press_count: Arc>, } impl ButtonCallback for MockButtonCallback { fn on_button_press(&self) { let mut count = self.press_count.lock().unwrap(); *count += 1; } } #[test] fn test_upp_constants() { assert_eq!(UPP_USB_MAGIC, 0xBBAA); assert_eq!(UPP_CAMID_7, 7); } #[test] fn test_upp_config_default() { let config = UPPConfig::default(); assert_eq!(config.expected_camera_id, UPP_CAMID_7); assert_eq!(config.max_frame_size, 0x10000); assert!(!config.enable_debug); } #[test] fn test_upp_stats_default() { let stats = UPPStats::default(); assert_eq!(stats.buffer_size, 0); assert!(stats.current_frame_id.is_none()); assert_eq!(stats.total_frames_processed, 0); } #[test] fn test_upp_camera_creation() { let camera = UPPCamera::new(); let stats = camera.get_stats(); assert_eq!(stats.buffer_size, 0); assert!(stats.current_frame_id.is_none()); } #[test] fn test_upp_camera_callbacks() { let camera = UPPCamera::new(); let frame_callback = MockFrameCallback { frame_count: Arc::new(std::sync::Mutex::new(0u32)), }; let button_callback = MockButtonCallback { press_count: Arc::new(std::sync::Mutex::new(0u32)), }; camera.add_frame_callback(frame_callback); camera.add_button_callback(button_callback); // Verify callbacks were added let stats = camera.get_stats(); assert_eq!(stats.buffer_size, 0); } }