summaryrefslogtreecommitdiff
path: root/src/lib.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/lib.rs
Initial pipewiremain
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs217
1 files changed, 217 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..ab42e1c
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,217 @@
+//! Geek szitman supercamera - Rust implementation
+//!
+//! This crate provides a Rust implementation of the Geek szitman supercamera
+//! endoscope viewer with PipeWire support and preparation for V4L2 fallback.
+//!
+//! # Features
+//!
+//! - USB communication with the endoscope device
+//! - PipeWire video streaming
+//! - UPP protocol implementation
+//! - JPEG frame processing
+//! - Modular architecture for maintainability
+//!
+//! # Example
+//!
+//! ```rust,no_run
+//! use geek_szitman_supercamera::SuperCamera;
+//!
+//! fn main() -> Result<(), Box<dyn std::error::Error>> {
+//! let camera = SuperCamera::new()?;
+//! camera.start_stream()?;
+//! Ok(())
+//! }
+//! ```
+
+pub mod error;
+pub mod protocol;
+pub mod usb;
+pub mod utils;
+pub mod video;
+
+pub use error::{Error, Result};
+pub use protocol::UPPCamera;
+pub use usb::UsbSupercamera;
+pub use video::{VideoBackend, VideoBackendTrait};
+
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::sync::RwLock;
+use std::thread;
+use std::time::Duration;
+use tracing::{info, warn};
+
+/// Main camera controller that orchestrates all components
+pub struct SuperCamera {
+ usb_camera: Arc<UsbSupercamera>,
+ protocol: Arc<UPPCamera>,
+ video_backend: Arc<Mutex<Box<dyn VideoBackendTrait>>>,
+ is_running: Arc<RwLock<bool>>,
+ usb_thread: Arc<Mutex<Option<thread::JoinHandle<()>>>>,
+}
+
+impl SuperCamera {
+ /// Create a new SuperCamera instance
+ pub fn new() -> Result<Self> {
+ Self::with_backend(crate::video::VideoBackendType::PipeWire)
+ }
+
+ /// Create a new SuperCamera instance with specified backend
+ pub fn with_backend(backend_type: crate::video::VideoBackendType) -> Result<Self> {
+ let usb_camera = Arc::new(UsbSupercamera::new()?);
+ let protocol = Arc::new(UPPCamera::new_with_debug(true)); // Enable debug by default
+
+ // Initialize video backend based on choice
+ let video_backend = Arc::new(Mutex::new(VideoBackend::from_type(backend_type)?));
+
+ Ok(Self {
+ usb_camera,
+ protocol,
+ video_backend,
+ is_running: Arc::new(RwLock::new(false)),
+ usb_thread: Arc::new(Mutex::new(None)),
+ })
+ }
+
+ /// Start the camera stream
+ pub fn start_stream(&self) -> Result<()> {
+ let mut is_running = self.is_running.write().unwrap();
+ if *is_running {
+ warn!("Camera stream is already running");
+ return Ok(());
+ }
+
+ info!("Starting camera stream...");
+ *is_running = true;
+ drop(is_running);
+
+ // Ensure video backend is initialized before pushing frames
+ {
+ let mut backend = self.video_backend.lock().unwrap();
+ backend.initialize()?;
+ }
+
+ // Start USB reading loop in a separate thread
+ let usb_camera = Arc::clone(&self.usb_camera);
+ let protocol = Arc::clone(&self.protocol);
+ let video_backend = Arc::clone(&self.video_backend);
+ let is_running = Arc::clone(&self.is_running);
+
+ let handle = thread::spawn(move || {
+ Self::usb_read_loop(usb_camera, protocol, video_backend, is_running);
+ });
+
+ // Store the thread handle
+ let mut usb_thread = self.usb_thread.lock().unwrap();
+ *usb_thread = Some(handle);
+
+ Ok(())
+ }
+
+ /// Stop the camera stream
+ pub fn stop_stream(&self) -> Result<()> {
+ let mut is_running = self.is_running.write().unwrap();
+ if !*is_running {
+ warn!("Camera stream is not running");
+ return Ok(());
+ }
+
+ info!("Stopping camera stream...");
+ *is_running = false;
+ Ok(())
+ }
+
+ /// Check if the camera stream is running
+ pub fn is_running(&self) -> bool {
+ *self.is_running.read().unwrap()
+ }
+
+ /// Main USB reading loop
+ fn usb_read_loop(
+ usb_camera: Arc<UsbSupercamera>,
+ protocol: Arc<UPPCamera>,
+ video_backend: Arc<Mutex<Box<dyn VideoBackendTrait>>>,
+ is_running: Arc<RwLock<bool>>,
+ ) {
+ let mut frame_count = 0u32;
+
+ while *is_running.read().unwrap() {
+ match usb_camera.read_frame() {
+ Ok(data) => {
+ frame_count += 1;
+ // Reduce logging frequency - only log every 100th frame
+ if frame_count % 100 == 0 {
+ tracing::debug!("Received frame {} ({} bytes)", frame_count, data.len());
+ }
+
+ // Process frame through protocol
+ if let Err(e) = protocol.handle_frame_robust(&data) {
+ tracing::error!("Protocol error: {}", e);
+
+ // Log additional frame information for debugging
+ if data.len() >= 5 {
+ let magic_bytes = [data[0], data[1]];
+ let magic = u16::from_le_bytes(magic_bytes);
+ let cid = if data.len() >= 3 { data[2] } else { 0 };
+ let length_bytes = if data.len() >= 5 { [data[3], data[4]] } else { [0, 0] };
+ let length = u16::from_le_bytes(length_bytes);
+
+ tracing::debug!(
+ "Frame header: magic=0x{:04X}, cid={}, length={}, actual_size={}",
+ magic, cid, length, data.len()
+ );
+ }
+
+ continue;
+ }
+
+ // Send to video backend if frame is complete
+ if let Some(frame) = protocol.get_complete_frame() {
+ let backend = video_backend.lock().unwrap();
+ if let Err(e) = backend.push_frame(&frame) {
+ tracing::error!("Video backend error: {}", e);
+ }
+ }
+ }
+ Err(e) => {
+ tracing::error!("USB read error: {}", e);
+ // Check if it's a USB error that indicates disconnection
+ if let crate::error::Error::Usb(usb_err) = &e {
+ if usb_err.is_device_disconnected() {
+ tracing::warn!("Device disconnected, stopping stream");
+ break;
+ }
+ }
+ // Use standard library sleep instead of tokio
+ std::thread::sleep(Duration::from_millis(100));
+ }
+ }
+ }
+
+ info!("USB reading loop stopped");
+ }
+}
+
+impl Drop for SuperCamera {
+ fn drop(&mut self) {
+ // Stop the stream
+ if let Ok(mut is_running) = self.is_running.try_write() {
+ *is_running = false;
+ }
+
+ // Wait for USB thread to finish
+ if let Some(handle) = self.usb_thread.lock().unwrap().take() {
+ let _ = handle.join();
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_super_camera_creation() {
+ // This test requires actual USB device, so we'll just test the structure
+ // In a real test environment, we'd use mocks
+ assert!(true);
+ }
+}