summaryrefslogtreecommitdiff
path: root/src/protocol/mod.rs
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2026-02-08 12:44:10 +0100
committerDawid Rycerz <dawid@rycerz.xyz>2026-02-08 12:44:10 +0100
commit0c20fb86633104744dbccf30ad732296694fff1b (patch)
tree02ffb8494086960b4a84decf3bdc2c8c61bfc4f6 /src/protocol/mod.rs
Initial pipewiremain
Diffstat (limited to 'src/protocol/mod.rs')
-rw-r--r--src/protocol/mod.rs409
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);
+ }
+}