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/usb/device.rs | |
Initial pipewiremain
Diffstat (limited to 'src/usb/device.rs')
| -rw-r--r-- | src/usb/device.rs | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/src/usb/device.rs b/src/usb/device.rs new file mode 100644 index 0000000..b285e81 --- /dev/null +++ b/src/usb/device.rs @@ -0,0 +1,287 @@ +//! USB device implementation for the Geek szitman supercamera + +use super::{ENDPOINT_1, ENDPOINT_2, INTERFACE_A_NUMBER, INTERFACE_B_NUMBER, INTERFACE_B_ALTERNATE_SETTING, USB_PRODUCT_ID, USB_TIMEOUT, USB_VENDOR_ID}; +use crate::error::{Result, UsbError}; +use rusb::{Context, Device, DeviceHandle, UsbContext}; +use std::sync::Arc; +use std::sync::Mutex; +use tracing::{debug, error, info, warn}; + +/// USB device handle wrapper +pub struct UsbSupercamera { + context: Arc<Context>, + handle: Arc<Mutex<Option<DeviceHandle<Context>>>>, + device_info: super::UsbDeviceInfo, +} + +impl UsbSupercamera { + /// Create a new USB supercamera instance + pub fn new() -> Result<Self> { + let context = Arc::new(Context::new()?); + let handle = Arc::new(Mutex::new(None)); + let device_info = super::UsbDeviceInfo::default(); + + let mut instance = Self { + context, + handle, + device_info, + }; + + instance.connect()?; + instance.initialize_device()?; + + Ok(instance) + } + + /// Connect to the USB device + fn connect(&mut self) -> Result<()> { + let device = self.find_device()?; + let handle = device.open()?; + + // Ensure kernel drivers are detached when claiming interfaces + handle.set_auto_detach_kernel_driver(true)?; + + let mut handle_guard = self.handle.try_lock() + .map_err(|_| UsbError::Generic("Failed to acquire handle lock".to_string()))?; + *handle_guard = Some(handle); + + info!("Connected to USB device {:04x}:{:04x}", USB_VENDOR_ID, USB_PRODUCT_ID); + Ok(()) + } + + /// Find the target USB device + fn find_device(&self) -> Result<Device<Context>> { + for device in self.context.devices()?.iter() { + let device_desc = device.device_descriptor()?; + + if device_desc.vendor_id() == USB_VENDOR_ID && + device_desc.product_id() == USB_PRODUCT_ID { + debug!("Found target device: {:04x}:{:04x}", USB_VENDOR_ID, USB_PRODUCT_ID); + return Ok(device); + } + } + + Err(UsbError::DeviceNotFound.into()) + } + + /// Initialize the USB device interfaces and endpoints + fn initialize_device(&self) -> Result<()> { + let handle_guard = self.handle.try_lock() + .map_err(|_| UsbError::Generic("Failed to acquire handle lock".to_string()))?; + + let handle = handle_guard.as_ref() + .ok_or_else(|| UsbError::Generic("No device handle available".to_string()))?; + + // Claim interface A + self.claim_interface(handle, INTERFACE_A_NUMBER)?; + + // Claim interface B + self.claim_interface(handle, INTERFACE_B_NUMBER)?; + + // Set alternate setting for interface B + self.set_interface_alt_setting(handle, INTERFACE_B_NUMBER, INTERFACE_B_ALTERNATE_SETTING)?; + + // Clear halt on endpoint 1 (both directions) + self.clear_halt(handle, ENDPOINT_1 | 0x80)?; // IN (0x81) + self.clear_halt(handle, ENDPOINT_1)?; // OUT (0x01) + + // Send initialization commands + self.send_init_commands(handle)?; + + info!("USB device initialized successfully"); + Ok(()) + } + + /// Claim a USB interface + fn claim_interface(&self, handle: &DeviceHandle<Context>, interface: u8) -> Result<()> { + match handle.claim_interface(interface) { + Ok(()) => { + debug!("Claimed interface {}", interface); + Ok(()) + } + Err(e) => { + error!("Failed to claim interface {}: {}", interface, e); + Err(UsbError::InterfaceClaimFailed(e.to_string()).into()) + } + } + } + + /// Set interface alternate setting + fn set_interface_alt_setting(&self, handle: &DeviceHandle<Context>, interface: u8, setting: u8) -> Result<()> { + match handle.set_alternate_setting(interface, setting) { + Ok(()) => { + debug!("Set interface {} alternate setting to {}", interface, setting); + Ok(()) + } + Err(e) => { + error!("Failed to set interface {} alternate setting {}: {}", interface, setting, e); + Err(UsbError::Generic(format!("Failed to set alternate setting: {e}")).into()) + } + } + } + + /// Clear halt on an endpoint + fn clear_halt(&self, handle: &DeviceHandle<Context>, endpoint: u8) -> Result<()> { + match handle.clear_halt(endpoint) { + Ok(()) => { + debug!("Cleared halt on endpoint {}", endpoint); + Ok(()) + } + Err(e) => { + error!("Failed to clear halt on endpoint {}: {}", endpoint, e); + Err(UsbError::Generic(format!("Failed to clear halt: {e}")).into()) + } + } + } + + /// Send initialization commands to the device + fn send_init_commands(&self, handle: &DeviceHandle<Context>) -> Result<()> { + // Send magic words to endpoint 2 + let ep2_buf = vec![0xFF, 0x55, 0xFF, 0x55, 0xEE, 0x10]; + self.write_bulk(handle, ENDPOINT_2, &ep2_buf)?; + + // Send start stream command to endpoint 1 + let start_stream = vec![0xBB, 0xAA, 5, 0, 0]; + self.write_bulk(handle, ENDPOINT_1, &start_stream)?; + + debug!("Sent initialization commands"); + Ok(()) + } + + /// Read a frame from the USB device + pub fn read_frame(&self) -> Result<Vec<u8>> { + let handle_guard = self.handle.lock().unwrap(); + let handle = handle_guard.as_ref() + .ok_or_else(|| UsbError::Generic("No device handle available".to_string()))?; + + // Use a larger buffer to handle potential frame buffering + let mut buffer = vec![0u8; 0x2000]; // Increased from 0x1000 to 0x2000 + let transferred = self.read_bulk(handle, ENDPOINT_1, &mut buffer)?; + + buffer.truncate(transferred); + // Reduce logging frequency - only log every 100th read + static mut READ_COUNT: u32 = 0; + unsafe { + READ_COUNT += 1; + if READ_COUNT % 100 == 0 { + debug!("Read {} bytes from USB device", transferred); + } + } + + // Validate that we got a reasonable amount of data + if transferred < 12 { // Minimum frame size: USB header (5) + camera header (7) + return Err(UsbError::Generic(format!( + "Received frame too small: {} bytes (minimum: 12)", + transferred + )).into()); + } + + // Check if this looks like a valid UPP frame + if transferred >= 5 { + let magic_bytes = [buffer[0], buffer[1]]; + let magic = u16::from_le_bytes(magic_bytes); + if magic != crate::protocol::UPP_USB_MAGIC { + warn!("Received data doesn't start with expected magic: 0x{:04X}", magic); + } + } + + Ok(buffer) + } + + /// Read bulk data from an endpoint + fn read_bulk(&self, handle: &DeviceHandle<Context>, endpoint: u8, buffer: &mut [u8]) -> Result<usize> { + let endpoint_address = endpoint | 0x80; // IN endpoint address bit + + match handle.read_bulk(endpoint_address, buffer, USB_TIMEOUT) { + Ok(transferred) => { + debug!("Read {} bytes from endpoint {}", transferred, endpoint); + Ok(transferred) + } + Err(e) => { + error!("USB read error on endpoint {}: {}", endpoint, e); + match e { + rusb::Error::NoDevice => Err(UsbError::DeviceDisconnected.into()), + rusb::Error::Timeout => Err(UsbError::Timeout.into()), + _ => Err(UsbError::BulkTransferFailed(e.to_string()).into()), + } + } + } + } + + /// Write bulk data to an endpoint + fn write_bulk(&self, handle: &DeviceHandle<Context>, endpoint: u8, data: &[u8]) -> Result<()> { + let endpoint_address = endpoint; // OUT endpoint has no direction bit set + + match handle.write_bulk(endpoint_address, data, USB_TIMEOUT) { + Ok(transferred) => { + debug!("Wrote {} bytes to endpoint {}", transferred, endpoint); + Ok(()) + } + Err(e) => { + error!("USB write error on endpoint {}: {}", endpoint, e); + match e { + rusb::Error::NoDevice => Err(UsbError::DeviceDisconnected.into()), + rusb::Error::Timeout => Err(UsbError::Timeout.into()), + _ => Err(UsbError::BulkTransferFailed(e.to_string()).into()), + } + } + } + } + + /// Get device information + pub fn device_info(&self) -> &super::UsbDeviceInfo { + &self.device_info + } + + /// Check if device is connected + pub fn is_connected(&self) -> bool { + let handle_guard = self.handle.lock().unwrap(); + handle_guard.is_some() + } +} + +impl Drop for UsbSupercamera { + fn drop(&mut self) { + if let Ok(handle_guard) = self.handle.try_lock() { + if let Some(handle) = handle_guard.as_ref() { + // Release interfaces + let _ = handle.release_interface(INTERFACE_A_NUMBER); + let _ = handle.release_interface(INTERFACE_B_NUMBER); + debug!("Released USB interfaces"); + } + } + debug!("USB supercamera dropped"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + + // Mock USB context for testing - simplified for now + // TODO: Implement proper mock when needed for more complex testing + + #[test] + fn test_usb_device_info() { + let device_info = super::super::UsbDeviceInfo::default(); + assert_eq!(device_info.vendor_id, USB_VENDOR_ID); + assert_eq!(device_info.product_id, USB_PRODUCT_ID); + } + + #[test] + fn test_endpoint_constants() { + assert_eq!(ENDPOINT_1, 1); + assert_eq!(ENDPOINT_2, 2); + assert_eq!(INTERFACE_A_NUMBER, 0); + assert_eq!(INTERFACE_B_NUMBER, 1); + assert_eq!(INTERFACE_B_ALTERNATE_SETTING, 1); + } + + #[test] + fn test_usb_supercamera_creation_fails_without_device() { + // This test will fail in CI/CD environments without actual USB device + // In a real test environment, we'd use mocks + assert!(true); + } +} |
