diff options
| author | Dawid Rycerz <dawid@rycerz.xyz> | 2026-02-08 12:44:10 +0100 |
|---|---|---|
| committer | Dawid Rycerz <dawid@rycerz.xyz> | 2026-02-08 12:44:10 +0100 |
| commit | 0c20fb86633104744dbccf30ad732296694fff1b (patch) | |
| tree | 02ffb8494086960b4a84decf3bdc2c8c61bfc4f6 /src/protocol/mod.rs | |
Initial pipewiremain
Diffstat (limited to 'src/protocol/mod.rs')
| -rw-r--r-- | src/protocol/mod.rs | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs new file mode 100644 index 0000000..cf0426e --- /dev/null +++ b/src/protocol/mod.rs @@ -0,0 +1,409 @@ +//! 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<UPPParser>, + jpeg_parser: Arc<JpegParser>, + frame_buffer: Arc<Mutex<Vec<u8>>>, + current_frame_id: Arc<Mutex<Option<u8>>>, + frame_callbacks: Arc<Mutex<Vec<Box<dyn FrameCallback + Send + Sync>>>>, + button_callbacks: Arc<Mutex<Vec<Box<dyn ButtonCallback + Send + Sync>>>>, + // Buffer for assembling frames across USB reads + input_buffer: Arc<Mutex<Vec<u8>>>, +} + +/// 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<Vec<UPPFrame>> { + 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<u8> = 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<F>(&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<F>(&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<Vec<u8>> { + 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<u8>, + 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<std::sync::Mutex<u32>>, + } + + 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<std::sync::Mutex<u32>>, + } + + 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); + } +} |
