diff --git a/src/cli/main.rs b/src/cli/main.rs index d12195e..354b34e 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -1,7 +1,9 @@ +// SPDX-License-Identifier: EUPL-1.2 + use clap::{Parser, Subcommand}; use clap_complete::generate; use dialoguer::{FuzzySelect, Select}; -use tiny4linux::{AIMode, Camera, OBSBotWebCam}; +use tiny4linux::{AIMode, Camera, SleepMode, Tiny2Camera}; /// Simple program to greet a person #[derive(Parser)] @@ -160,11 +162,11 @@ fn evaluate_sleep_arg(state: Option, camera: Camera) { match state { Some(OnOffArg::Off) => { println!("Setting the camera to sleep"); - camera.set_sleep_mode(tiny4linux::SleepMode::Sleep).unwrap(); + camera.set_sleep_mode(SleepMode::Sleep).unwrap(); } Some(OnOffArg::On) => { println!("Waking up the camera"); - camera.set_sleep_mode(tiny4linux::SleepMode::Awake).unwrap(); + camera.set_sleep_mode(SleepMode::Awake).unwrap(); } None => { let options = [ diff --git a/src/gui/main.rs b/src/gui/main.rs index 9050a31..223df37 100644 --- a/src/gui/main.rs +++ b/src/gui/main.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + mod styles; mod ui_modules; @@ -9,7 +11,7 @@ use iced::window::Position; use iced::{Element, Point}; use iced::{Length, Size, Subscription, Task, time, window}; use std::time::Duration; -use tiny4linux::{AIMode, Camera, ExposureMode, OBSBotWebCam, SleepMode, TrackingSpeed}; +use tiny4linux::{AIMode, Camera, ExposureMode, SleepMode, Tiny2Camera, TrackingSpeed}; #[derive(Debug, Clone, PartialEq)] enum Message { diff --git a/src/gui/styles/button_non_styled.rs b/src/gui/styles/button_non_styled.rs index d538a1c..4e09c2e 100644 --- a/src/gui/styles/button_non_styled.rs +++ b/src/gui/styles/button_non_styled.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use iced::Theme; use iced::widget::button::{Status, Style}; diff --git a/src/gui/styles/colors.rs b/src/gui/styles/colors.rs index a3eb860..fa21893 100644 --- a/src/gui/styles/colors.rs +++ b/src/gui/styles/colors.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use iced::{Color, color}; pub const COLOR_PRIMARY_OBSBOT: Color = color!(0xe6, 0x00, 0x33); diff --git a/src/gui/styles/general_area_style.rs b/src/gui/styles/general_area_style.rs index c69b6b6..ea89ec8 100644 --- a/src/gui/styles/general_area_style.rs +++ b/src/gui/styles/general_area_style.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::styles::colors::COLOR_BACKGROUND_SECONDARY_DARK; use iced::widget::container; diff --git a/src/gui/styles/mod.rs b/src/gui/styles/mod.rs index c2c88df..d776bc0 100644 --- a/src/gui/styles/mod.rs +++ b/src/gui/styles/mod.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + pub mod button_non_styled; pub mod colors; pub mod general_area_style; diff --git a/src/gui/styles/theme.rs b/src/gui/styles/theme.rs index de9e753..28452ca 100644 --- a/src/gui/styles/theme.rs +++ b/src/gui/styles/theme.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::styles::colors::{COLOR_BACKGROUND_DARK, COLOR_PRIMARY_OBSBOT}; use iced::Theme; use iced::theme::Palette; diff --git a/src/gui/styles/tooltip_style.rs b/src/gui/styles/tooltip_style.rs index 6c3c011..7314641 100644 --- a/src/gui/styles/tooltip_style.rs +++ b/src/gui/styles/tooltip_style.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::Message; use crate::styles::colors::{COLOR_BACKGROUND_DARK, COLOR_BACKGROUND_SECONDARY_DARK}; use iced::Border; diff --git a/src/gui/ui_modules/button_exposure_mode.rs b/src/gui/ui_modules/button_exposure_mode.rs index 0335b80..9fd61c0 100644 --- a/src/gui/ui_modules/button_exposure_mode.rs +++ b/src/gui/ui_modules/button_exposure_mode.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::Message; use crate::styles::tooltip_style::tooltip_content; use iced::widget::button::secondary; diff --git a/src/gui/ui_modules/button_hdr.rs b/src/gui/ui_modules/button_hdr.rs index c37d82c..4fc629b 100644 --- a/src/gui/ui_modules/button_hdr.rs +++ b/src/gui/ui_modules/button_hdr.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::Message; use crate::styles::tooltip_style::tooltip_content; use iced::alignment::Vertical; diff --git a/src/gui/ui_modules/button_sleep_wake.rs b/src/gui/ui_modules/button_sleep_wake.rs index df2b454..a0188df 100644 --- a/src/gui/ui_modules/button_sleep_wake.rs +++ b/src/gui/ui_modules/button_sleep_wake.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::styles::tooltip_style::tooltip_content; use crate::{Message, WindowMode}; use iced::widget::tooltip::Position; diff --git a/src/gui/ui_modules/button_tracking_mode.rs b/src/gui/ui_modules/button_tracking_mode.rs index 82b7399..650960e 100644 --- a/src/gui/ui_modules/button_tracking_mode.rs +++ b/src/gui/ui_modules/button_tracking_mode.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::Message; use crate::styles::tooltip_style::tooltip_content; use iced::widget::button::{primary, secondary}; diff --git a/src/gui/ui_modules/button_window_mode_change.rs b/src/gui/ui_modules/button_window_mode_change.rs index c2119cd..78a2914 100644 --- a/src/gui/ui_modules/button_window_mode_change.rs +++ b/src/gui/ui_modules/button_window_mode_change.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::styles::tooltip_style::tooltip_content; use crate::{Message, WindowMode}; use iced::widget::tooltip::Position; diff --git a/src/gui/ui_modules/current_stats.rs b/src/gui/ui_modules/current_stats.rs index d8a2697..652f2ae 100644 --- a/src/gui/ui_modules/current_stats.rs +++ b/src/gui/ui_modules/current_stats.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::{MainPanel, Message}; use iced::alignment::Horizontal; use iced::widget::{Container, column, container, horizontal_rule, row, text}; diff --git a/src/gui/ui_modules/debug_area.rs b/src/gui/ui_modules/debug_area.rs index 4740681..69be39c 100644 --- a/src/gui/ui_modules/debug_area.rs +++ b/src/gui/ui_modules/debug_area.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::Message::{HexDump, HexDump02}; use crate::{MainPanel, Message}; use iced::Length; diff --git a/src/gui/ui_modules/mod.rs b/src/gui/ui_modules/mod.rs index 16779ec..dd79c9d 100644 --- a/src/gui/ui_modules/mod.rs +++ b/src/gui/ui_modules/mod.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + mod button_exposure_mode; mod button_hdr; mod button_sleep_wake; diff --git a/src/gui/ui_modules/settings_area.rs b/src/gui/ui_modules/settings_area.rs index addb6fb..5349f3d 100644 --- a/src/gui/ui_modules/settings_area.rs +++ b/src/gui/ui_modules/settings_area.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::styles::tooltip_style::tooltip_content; use crate::ui_modules::button_exposure_mode::button_exposure_mode; use crate::ui_modules::button_hdr::button_hdr; diff --git a/src/gui/ui_modules/window_layout.rs b/src/gui/ui_modules/window_layout.rs index 9ef5238..5c8f00e 100644 --- a/src/gui/ui_modules/window_layout.rs +++ b/src/gui/ui_modules/window_layout.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + use crate::styles::button_non_styled::button_non_styled; use crate::styles::general_area_style::general_area_style; use crate::ui_modules::button_sleep_wake::button_sleep_wake; diff --git a/src/lib.rs b/src/lib.rs index 863faed..3433536 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,612 +1,5 @@ // SPDX-License-Identifier: EUPL-1.2 -mod usbio; +mod libs; -use errno::Errno; -use std::{fmt::Display, io}; -use thiserror::Error; -use usbio::UvcUsbIo; - -#[derive(Error, Debug)] -pub enum Error { - #[error("value of {1} is not supported for {0}")] - UnsupportedIntValue(String, i32), - #[error("USB IO error: {0}")] - USBIOError(i32), - #[error("IO error: {0}")] - IOError(#[from] io::Error), - #[error("no camera found")] - NoCameraFound, - #[error("Invalid setting")] - InvalidSetting, -} - -#[derive(Debug)] -pub struct Camera { - handle: usbio::CameraHandle, - debugging: bool, -} - -pub struct CameraStatus { - pub awake: SleepMode, - pub ai_mode: AIMode, - pub speed: TrackingSpeed, - pub hdr_on: bool, -} - -impl CameraStatus { - pub fn decode(bytes: &[u8]) -> Self { - CameraStatus { - awake: Self::decode_sleep_mode(bytes), - ai_mode: Self::decode_ai_mode(bytes), - speed: Self::decode_tracking_speed(bytes), - hdr_on: Self::decode_hdr_on(bytes), - } - } - - fn decode_sleep_mode(bytes: &[u8]) -> SleepMode { - match bytes[0x02] { - 0 => SleepMode::Awake, - 1 => SleepMode::Sleep, - _ => SleepMode::Unknown, - } - } - - fn decode_ai_mode(bytes: &[u8]) -> AIMode { - let m = bytes[0x18]; - let n = bytes[0x1c]; - - match (m, n) { - (0, 0) => AIMode::NoTracking, - (2, 0) => AIMode::NormalTracking, - (2, 1) => AIMode::UpperBody, - (2, 2) => AIMode::CloseUp, - (2, 3) => AIMode::Headless, - (2, 4) => AIMode::LowerBody, - (5, 0) => AIMode::DeskMode, - (4, 0) => AIMode::Whiteboard, - (6, 0) => AIMode::Hand, - (1, 0) => AIMode::Group, - (_, _) => AIMode::Unknown, - } - } - - fn decode_tracking_speed(bytes: &[u8]) -> TrackingSpeed { - match bytes[0x21] { - 0 => TrackingSpeed::Standard, - 2 => TrackingSpeed::Sport, - _ => TrackingSpeed::Standard, - } - } - - fn decode_hdr_on(bytes: &[u8]) -> bool { - bytes[0x6] != 0 - } - - pub fn default() -> Self { - CameraStatus { - awake: SleepMode::Unknown, - ai_mode: AIMode::Unknown, - speed: TrackingSpeed::Standard, - hdr_on: false, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum SleepMode { - Awake, - Sleep, - Unknown, -} - -impl Display for SleepMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SleepMode::Awake => write!(f, "Awake"), - SleepMode::Sleep => write!(f, "Sleeping"), - SleepMode::Unknown => write!(f, "Unknown"), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum AIMode { - NoTracking, - NormalTracking, - UpperBody, - CloseUp, - Headless, - LowerBody, - DeskMode, - Whiteboard, - Hand, - Group, - Unknown, -} - -impl Display for AIMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AIMode::NoTracking => write!(f, "Static"), - AIMode::NormalTracking => write!(f, "Normal Tracking"), - AIMode::UpperBody => write!(f, "Upper Body"), - AIMode::CloseUp => write!(f, "Close-up"), - AIMode::Headless => write!(f, "Headless"), - AIMode::LowerBody => write!(f, "Lower Body"), - AIMode::DeskMode => write!(f, "Desk Mode"), - AIMode::Whiteboard => write!(f, "Whiteboard"), - AIMode::Hand => write!(f, "Hand"), - AIMode::Group => write!(f, "Group"), - AIMode::Unknown => write!(f, "Unknown"), - } - } -} - -impl TryFrom for AIMode { - type Error = Error; - - fn try_from(value: i32) -> Result { - match value { - 0 => Ok(AIMode::NoTracking), - 1 => Ok(AIMode::NormalTracking), - 2 => Ok(AIMode::UpperBody), - 3 => Ok(AIMode::CloseUp), - 4 => Ok(AIMode::Headless), - 5 => Ok(AIMode::LowerBody), - 6 => Ok(AIMode::DeskMode), - 7 => Ok(AIMode::Whiteboard), - 8 => Ok(AIMode::Hand), - 9 => Ok(AIMode::Group), - _ => Err(Error::UnsupportedIntValue("AIMode".to_string(), value)), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum TrackingSpeed { - Standard, - Sport, -} - -impl Display for TrackingSpeed { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TrackingSpeed::Standard => write!(f, "Standard"), - TrackingSpeed::Sport => write!(f, "Sport"), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ExposureMode { - Manual, - Global, - Face, -} - -impl Display for ExposureMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ExposureMode::Manual => write!(f, "Manual"), - ExposureMode::Global => write!(f, "Global"), - ExposureMode::Face => write!(f, "Face"), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ExposureModeType { - Auto, - Manual, -} - -pub enum TrackingMode { - Headroom, - Standard, - Motion, -} - -impl TryFrom for TrackingMode { - type Error = Error; - - fn try_from(value: i32) -> Result { - match value { - 0 => Ok(TrackingMode::Headroom), - 1 => Ok(TrackingMode::Standard), - 2 => Ok(TrackingMode::Motion), - _ => Err(Error::UnsupportedIntValue( - "TrackingMode".to_string(), - value, - )), - } - } -} - -pub trait OBSBotWebCam { - fn set_sleep_mode(&self, mode: SleepMode) -> Result<(), Error>; - fn get_sleep_mode(&self) -> Result; - fn set_ai_mode(&self, mode: AIMode) -> Result<(), Error>; - fn get_ai_mode(&self) -> Result; - fn goto_preset_position(&self, preset_nr: i8) -> Result<(), Error>; - fn get_tracking_speed(&self) -> Result; - fn set_tracking_speed(&self, speed: TrackingSpeed) -> Result<(), Error>; - fn set_hdr_mode(&self, mode: bool) -> Result<(), Error>; - fn set_exposure_mode(&self, mode: ExposureMode) -> Result<(), Error>; - fn set_exposure_mode_type(&self, mode: ExposureModeType) -> Result<(), Error>; - fn set_debugging(&mut self, debugging: bool); -} - -impl OBSBotWebCam for Camera { - fn set_sleep_mode(&self, mode: SleepMode) -> Result<(), Error> { - if mode == SleepMode::Unknown { - return Err(Error::InvalidSetting); - } - - const FUNCTION_GROUP_SLEEP: [u8; 6] = [0x0a, 0x02, 0xc2, 0xa0, 0x04, 0x00]; - - let (sequence_nr, checksum, command) = match mode { - SleepMode::Awake => ( - [0xa5, 0x00], - [0x5f, 0xef], - [0xbe, 0x07, 0x00, 0x00, 0x00, 0x00], - ), - SleepMode::Sleep => ( - [0x42, 0x00], - [0xea, 0x63], - [0xbf, 0xfb, 0x01, 0x00, 0x00, 0x00], - ), - SleepMode::Unknown => panic!(), - }; - - let cmd = Command02::new() - .function_group(FUNCTION_GROUP_SLEEP) - .sequence_nr(sequence_nr) - .checksum(checksum) - .command(command) - .build(); - - self.get_status()?.awake = mode; - - self.send_cmd(0x2, 0x2, &cmd) - } - - fn get_sleep_mode(&self) -> Result { - Ok(self.get_status()?.awake) - } - - fn set_ai_mode(&self, mode: AIMode) -> Result<(), Error> { - let cmd = match mode { - AIMode::NoTracking => [0x16, 0x02, 0x00, 0x00], - AIMode::NormalTracking => [0x16, 0x02, 0x02, 0x00], - AIMode::UpperBody => [0x16, 0x02, 0x02, 0x01], - AIMode::DeskMode => [0x16, 0x02, 0x05, 0x00], - AIMode::Whiteboard => [0x16, 0x02, 0x04, 0x00], - AIMode::Group => [0x16, 0x02, 0x01, 0x00], - AIMode::Hand => [0x16, 0x02, 0x03, 0x00], - AIMode::CloseUp => [0x16, 0x02, 0x02, 0x02], - AIMode::Headless => [0x16, 0x02, 0x02, 0x03], - AIMode::LowerBody => [0x16, 0x02, 0x02, 0x04], - AIMode::Unknown => [0x16, 0x02, 0x00, 0x00], - }; - self.send_cmd(0x2, 0x6, &cmd) - } - - fn get_ai_mode(&self) -> Result { - Ok(self.get_status()?.ai_mode) - } - - fn goto_preset_position(&self, preset_nr: i8) -> Result<(), Error> { - if preset_nr < 0 || preset_nr > 3 { - return Err(Error::InvalidSetting); - } - - const FUNCTION_GROUP_PRESETS: [u8; 6] = [0x0a, 0x04, 0xc4, 0x39, 0x14, 0x00]; - - let (sequence_nr, checksum, command) = match preset_nr { - 0 => ( - [0x20, 0x00], - [0x6b, 0xdc], - [0xd6, 0xfb, 0x00, 0x00, 0x00, 0x00], - ), - 1 => ( - [0x1a, 0x00], - [0x4b, 0x03], - [0xeb, 0x2a, 0x01, 0x00, 0x00, 0x00], - ), - 2 => ( - [0x26, 0x00], - [0x8b, 0xc3], - [0xaf, 0x19, 0x02, 0x00, 0x00, 0x00], - ), - _ => { - println!("Invalid preset nr {}", preset_nr + 1); - return Err(Error::InvalidSetting); - } - }; - - let cmd = Command02::new() - .function_group(FUNCTION_GROUP_PRESETS) - .sequence_nr(sequence_nr) - .checksum(checksum) - .command(command) - .appendix({ - let mut arr = [0u8; 16]; - for i in 0..4 { - arr[i * 4..(i + 1) * 4].copy_from_slice(&[0x00, 0x00, 0x80, 0x3f]); - } - arr - }) - .build(); - - self.send_cmd(0x2, 0x2, &cmd) - } - - fn get_tracking_speed(&self) -> Result { - Ok(self.get_status()?.speed) - } - - fn set_tracking_speed(&self, speed: TrackingSpeed) -> Result<(), Error> { - const FUNCTION_GROUP_TRACKING_SPEED: [u8; 6] = [0x0a, 0x04, 0xc4, 0x0c, 0x01, 0x00]; - - let appendix: [u8; 16] = { - let mut a = [0x00; 16]; - a[..4].fill(0x00); - a - }; - - let (sequence_nr, checksum, command) = match speed { - TrackingSpeed::Standard => ( - [0x20, 0x00], - [0xab, 0xcb], - [0xe6, 0x3f, 0x00, 0x00, 0x00, 0x00], - ), - TrackingSpeed::Sport => ( - [0x21, 0x00], - [0xfa, 0x0e], - [0x67, 0xfe, 0x02, 0x00, 0x00, 0x00], - ), - }; - - let cmd = Command02::new() - .function_group(FUNCTION_GROUP_TRACKING_SPEED) - .sequence_nr(sequence_nr) - .checksum(checksum) - .command(command) - .appendix(appendix) - .build(); - - self.get_status()?.speed = speed; - - self.send_cmd(0x2, 0x2, &cmd) - } - - fn set_hdr_mode(&self, mode: bool) -> Result<(), Error> { - let cmd = if mode { - [0x01, 0x01, 0x01] - } else { - [0x01, 0x01, 0x00] - }; - self.send_cmd(0x2, 0x6, &cmd) - } - - fn set_exposure_mode(&self, mode: ExposureMode) -> Result<(), Error> { - match mode { - ExposureMode::Manual => { - self.set_exposure_mode_type(ExposureModeType::Manual)?; - } - ExposureMode::Global => { - self.set_exposure_mode_type(ExposureModeType::Auto)?; - self.send_cmd(0x2, 0x6, &[0x03, 0x01, 0x00])?; - } - ExposureMode::Face => { - self.set_exposure_mode_type(ExposureModeType::Auto)?; - self.send_cmd(0x2, 0x6, &[0x03, 0x01, 0x01])?; - } - }; - Ok(()) - } - - fn set_exposure_mode_type(&self, mode: ExposureModeType) -> Result<(), Error> { - const FUNCTION_GROUP_EXPOSURE_MODE_TYPE: [u8; 6] = [0x0a, 0x02, 0x82, 0x29, 0x05, 0x00]; - - let (sequence_nr, checksum, command) = match mode { - ExposureModeType::Auto => ( - [0x16, 0x00], - [0x58, 0x91], - [0xb2, 0xaf, 0x02, 0x04, 0x00, 0x00], - ), - ExposureModeType::Manual => ( - [0x15, 0x00], - [0xa8, 0x9e], - [0xf9, 0x27, 0x01, 0x32, 0x00, 0x00], - ), - }; - - let command = Command02::new() - .function_group(FUNCTION_GROUP_EXPOSURE_MODE_TYPE) - .sequence_nr(sequence_nr) - .checksum(checksum) - .command(command) - .build(); - - self.send_cmd(0x2, 0x2, &command)?; - - Ok(()) - } - - fn set_debugging(&mut self, debugging: bool) { - self.set_debugging(debugging); - } -} - -impl Camera { - pub fn new(hint: &str) -> Result { - Ok(Self { - handle: usbio::open_camera(hint)?, - debugging: false, - }) - } - - pub fn info(&self) -> Result<(), Errno> { - self.handle.info() - } - - pub fn get_status(&self) -> Result { - let mut data: [u8; 60] = [0u8; 60]; - self.get_cur(0x2, 0x6, &mut data) - .map_err(|x| Error::USBIOError(x.0))?; - - if self.debugging { - println!("Current state: {:?} {:}", data, hex::encode(&data)); - } - - Ok(CameraStatus::decode(&data)) - } - - pub fn dump(&self) -> Result<(), Errno> { - let mut data: [u8; 60] = [0u8; 60]; - self.get_cur(0x2, 0x6, &mut data)?; - hexdump::hexdump(&data); - Ok(()) - } - - pub fn dump_02(&self) -> Result<(), Errno> { - let mut data: [u8; 60] = [0u8; 60]; - self.get_cur(0x2, 0x2, &mut data)?; - hexdump::hexdump(&data); - Ok(()) - } - - pub fn send_cmd(&self, unit: u8, selector: u8, cmd: &[u8]) -> Result<(), Error> { - let mut data = [0u8; 60]; - data[..cmd.len()].copy_from_slice(cmd); - - self.set_cur(unit, selector, &mut data) - .map_err(|e| Error::USBIOError(e.0)) - } - - fn get_cur(&self, unit: u8, selector: u8, data: &mut [u8]) -> Result<(), Errno> { - // always call get_len first - match self.get_len(unit, selector) { - Ok(size) => { - if data.len() < size { - println!("Got size {}", size); - return Err(Errno(1)); - } - } - Err(err) => return Err(err), - }; - - // Why not &mut data here? - match self.io(unit, selector, usbio::UVC_GET_CUR, data) { - Ok(_) => Ok(()), - Err(err) => Err(err), - } - } - - fn set_cur(&self, unit: u8, selector: u8, data: &mut [u8]) -> Result<(), Errno> { - match self.get_len(unit, selector) { - Ok(size) => { - if data.len() > size { - println!("Got size {}", size); - return Err(Errno(1)); - } - } - Err(err) => return Err(err), - }; - - if self.debugging { - println!("{:} {:} {:}", unit, selector, hex::encode(&data)); - } - - match self.io(unit, selector, usbio::UVC_SET_CUR, data) { - Ok(_) => Ok(()), - Err(err) => Err(err), - } - } - - fn get_len(&self, unit: u8, selector: u8) -> Result { - let mut data = [0u8; 2]; - - match self.io(unit, selector, usbio::UVC_GET_LEN, &mut data) { - Ok(_) => Ok(u16::from_le_bytes(data).into()), - Err(err) => Err(err), - } - } - - fn io(&self, unit: u8, selector: u8, query: u8, data: &mut [u8]) -> Result<(), Errno> { - self.handle.io(unit, selector, query, data) - } - - fn set_debugging(&mut self, debugging: bool) { - self.debugging = debugging - } -} - -pub struct Command02 { - pub function_group: Option<[u8; 6]>, - pub sequence_nr: Option<[u8; 2]>, - pub checksum: Option<[u8; 2]>, - pub command: Option<[u8; 6]>, - pub appendix: Option<[u8; 16]>, -} - -impl Command02 { - pub fn new() -> Self { - Self { - function_group: None, - sequence_nr: None, - checksum: None, - command: None, - appendix: None, - } - } - - pub fn function_group(mut self, function_group: [u8; 6]) -> Self { - self.function_group = Some(function_group); - self - } - - pub fn sequence_nr(mut self, sequence_number: [u8; 2]) -> Self { - self.sequence_nr = Some(sequence_number); - self - } - - pub fn checksum(mut self, checksum: [u8; 2]) -> Self { - self.checksum = Some(checksum); - self - } - - pub fn command(mut self, cmd: [u8; 6]) -> Self { - self.command = Some(cmd); - self - } - - pub fn appendix(mut self, app: [u8; 16]) -> Self { - self.appendix = Some(app); - self - } - - pub fn build(self) -> [u8; 36] { - const FRAME_ID: [u8; 2] = [0xaa, 0x25]; - const SEGMENT_SIZE: [u8; 2] = [0x0c, 0x00]; - - [ - FRAME_ID.as_slice(), - self.sequence_nr - .expect("sequence_nr is required") - .as_slice(), - SEGMENT_SIZE.as_slice(), - self.checksum.expect("checksum is required").as_slice(), - self.function_group - .expect("function_group is required") - .as_slice(), - self.command.expect("command is required").as_slice(), - self.appendix.unwrap_or([0x00; 16]).as_slice(), - ] - .concat() - .try_into() - .unwrap() - } -} +pub use libs::*; diff --git a/src/libs/camera/camera.rs b/src/libs/camera/camera.rs new file mode 100644 index 0000000..3e774b5 --- /dev/null +++ b/src/libs/camera/camera.rs @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use crate::libs::camera::enums::{AIMode, ExposureMode, SleepMode, TrackingSpeed}; +use crate::libs::camera::status::CameraStatus; +use crate::libs::camera::transport::CameraTransport; +use crate::libs::errors::T4lError; +use crate::{ + AIModeCommand, ExposureModeCommand, ExposureModeTypeCommand, GotoPresetPositionCommand, + HdrModeCommand, SleepCommand, TrackingSpeedCommand, +}; +use errno::Errno; + +pub struct Camera { + transport: CameraTransport, + debugging: bool, +} + +impl Camera { + pub fn new(hint: &str) -> Result { + Ok(Self { + transport: CameraTransport::new(hint)?, + debugging: false, + }) + } + + pub fn info(&self) -> Result<(), Errno> { + self.transport.info() + } + + pub fn send_cmd(&self, unit: u8, selector: u8, cmd: &[u8]) -> Result<(), T4lError> { + self.transport.send_cmd(unit, selector, cmd, self.debugging) + } + + pub fn get_status(&self) -> Result { + self.transport.get_status(self.debugging) + } + + pub fn dump(&self) -> Result<(), Errno> { + self.transport.dump() + } + + pub fn dump_02(&self) -> Result<(), Errno> { + self.transport.dump_02() + } + + pub fn set_debugging(&mut self, debugging: bool) { + self.debugging = debugging + } +} + +pub trait Tiny2Camera { + fn set_sleep_mode(&self, mode: SleepMode) -> Result<(), T4lError>; + fn get_sleep_mode(&self) -> Result; + fn set_ai_mode(&self, mode: AIMode) -> Result<(), T4lError>; + fn get_ai_mode(&self) -> Result; + fn goto_preset_position(&self, preset_nr: i8) -> Result<(), T4lError>; + fn get_tracking_speed(&self) -> Result; + fn set_tracking_speed(&self, speed: TrackingSpeed) -> Result<(), T4lError>; + fn set_hdr_mode(&self, mode: bool) -> Result<(), T4lError>; + fn set_exposure_mode(&self, mode: ExposureMode) -> Result<(), T4lError>; + fn set_debugging(&mut self, debugging: bool); +} + +impl Tiny2Camera for Camera { + fn set_sleep_mode(&self, mode: SleepMode) -> Result<(), T4lError> { + let cmd = SleepCommand::build(mode)?; + + self.send_cmd(0x2, 0x2, &cmd) + } + + fn get_sleep_mode(&self) -> Result { + Ok(self.get_status()?.awake) + } + + fn set_ai_mode(&self, mode: AIMode) -> Result<(), T4lError> { + let cmd = AIModeCommand::build(mode)?; + + self.send_cmd(0x2, 0x6, &cmd) + } + + fn get_ai_mode(&self) -> Result { + Ok(self.get_status()?.ai_mode) + } + + fn goto_preset_position(&self, preset_nr: i8) -> Result<(), T4lError> { + let cmd = GotoPresetPositionCommand::build(preset_nr)?; + + self.send_cmd(0x2, 0x2, &cmd) + } + + fn get_tracking_speed(&self) -> Result { + Ok(self.get_status()?.speed) + } + + fn set_tracking_speed(&self, speed: TrackingSpeed) -> Result<(), T4lError> { + let cmd = TrackingSpeedCommand::build(speed)?; + + self.get_status()?.speed = speed; + + self.send_cmd(0x2, 0x2, &cmd) + } + + fn set_hdr_mode(&self, mode: bool) -> Result<(), T4lError> { + let cmd = HdrModeCommand::build(mode); + + self.send_cmd(0x2, 0x6, &cmd) + } + + fn set_exposure_mode(&self, mode: ExposureMode) -> Result<(), T4lError> { + let exposure_mode_type_command = ExposureModeTypeCommand::build(mode); + + self.send_cmd(0x2, 0x2, &exposure_mode_type_command)?; + + let exposure_mode_command = ExposureModeCommand::build(mode); + + exposure_mode_command + .map(|exposure_mode_command| self.send_cmd(0x2, 0x6, &exposure_mode_command)); + + Ok(()) + } + + fn set_debugging(&mut self, debugging: bool) { + self.set_debugging(debugging); + } +} diff --git a/src/libs/camera/command02.rs b/src/libs/camera/command02.rs new file mode 100644 index 0000000..4c90431 --- /dev/null +++ b/src/libs/camera/command02.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: EUPL-1.2 + +pub struct Command02 { + pub function_group: Option<[u8; 6]>, + pub sequence_nr: Option<[u8; 2]>, + pub checksum: Option<[u8; 2]>, + pub command: Option<[u8; 6]>, + pub appendix: Option<[u8; 16]>, +} + +impl Command02 { + pub fn new() -> Self { + Self { + function_group: None, + sequence_nr: None, + checksum: None, + command: None, + appendix: None, + } + } + + pub fn function_group(mut self, function_group: [u8; 6]) -> Self { + self.function_group = Some(function_group); + self + } + + pub fn sequence_nr(mut self, sequence_number: [u8; 2]) -> Self { + self.sequence_nr = Some(sequence_number); + self + } + + pub fn checksum(mut self, checksum: [u8; 2]) -> Self { + self.checksum = Some(checksum); + self + } + + pub fn command(mut self, cmd: [u8; 6]) -> Self { + self.command = Some(cmd); + self + } + + pub fn appendix(mut self, app: [u8; 16]) -> Self { + self.appendix = Some(app); + self + } + + pub fn build(self) -> [u8; 36] { + const FRAME_ID: [u8; 2] = [0xaa, 0x25]; + const SEGMENT_SIZE: [u8; 2] = [0x0c, 0x00]; + + [ + FRAME_ID.as_slice(), + self.sequence_nr + .expect("sequence_nr is required") + .as_slice(), + SEGMENT_SIZE.as_slice(), + self.checksum.expect("checksum is required").as_slice(), + self.function_group + .expect("function_group is required") + .as_slice(), + self.command.expect("command is required").as_slice(), + self.appendix.unwrap_or([0x00; 16]).as_slice(), + ] + .concat() + .try_into() + .unwrap() + } +} diff --git a/src/libs/camera/commands/ai_mode.rs b/src/libs/camera/commands/ai_mode.rs new file mode 100644 index 0000000..7b0a2d8 --- /dev/null +++ b/src/libs/camera/commands/ai_mode.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use crate::AIMode; +use crate::libs::errors::T4lError; + +pub struct AIModeCommand; + +impl AIModeCommand { + pub fn build(mode: AIMode) -> Result<[u8; 4], T4lError> { + Ok(match mode { + AIMode::NoTracking => [0x16, 0x02, 0x00, 0x00], + AIMode::NormalTracking => [0x16, 0x02, 0x02, 0x00], + AIMode::UpperBody => [0x16, 0x02, 0x02, 0x01], + AIMode::DeskMode => [0x16, 0x02, 0x05, 0x00], + AIMode::Whiteboard => [0x16, 0x02, 0x04, 0x00], + AIMode::Group => [0x16, 0x02, 0x01, 0x00], + AIMode::Hand => [0x16, 0x02, 0x03, 0x00], + AIMode::CloseUp => [0x16, 0x02, 0x02, 0x02], + AIMode::Headless => [0x16, 0x02, 0x02, 0x03], + AIMode::LowerBody => [0x16, 0x02, 0x02, 0x04], + AIMode::Unknown => [0x16, 0x02, 0x00, 0x00], + }) + } +} diff --git a/src/libs/camera/commands/exposure_mode.rs b/src/libs/camera/commands/exposure_mode.rs new file mode 100644 index 0000000..556cc1c --- /dev/null +++ b/src/libs/camera/commands/exposure_mode.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use crate::ExposureMode; + +pub struct ExposureModeCommand; + +impl ExposureModeCommand { + pub fn build(mode: ExposureMode) -> Option<[u8; 3]> { + match mode { + ExposureMode::Manual => None, + ExposureMode::Global => Some([0x03, 0x01, 0x00]), + ExposureMode::Face => Some([0x03, 0x01, 0x01]), + } + } +} diff --git a/src/libs/camera/commands/exposure_mode_type.rs b/src/libs/camera/commands/exposure_mode_type.rs new file mode 100644 index 0000000..5f2d8ed --- /dev/null +++ b/src/libs/camera/commands/exposure_mode_type.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use crate::Command02; +use crate::ExposureMode; + +pub struct ExposureModeTypeCommand; + +impl ExposureModeTypeCommand { + pub fn build(mode: ExposureMode) -> [u8; 36] { + const FUNCTION_GROUP_EXPOSURE_MODE_TYPE: [u8; 6] = [0x0a, 0x02, 0x82, 0x29, 0x05, 0x00]; + + let (sequence_nr, checksum, command) = if mode == ExposureMode::Manual { + ( + [0x16, 0x00], + [0x58, 0x91], + [0xb2, 0xaf, 0x02, 0x04, 0x00, 0x00], + ) + } else { + ( + [0x15, 0x00], + [0xa8, 0x9e], + [0xf9, 0x27, 0x01, 0x32, 0x00, 0x00], + ) + }; + + Command02::new() + .function_group(FUNCTION_GROUP_EXPOSURE_MODE_TYPE) + .sequence_nr(sequence_nr) + .checksum(checksum) + .command(command) + .build() + } +} diff --git a/src/libs/camera/commands/goto_preset_position.rs b/src/libs/camera/commands/goto_preset_position.rs new file mode 100644 index 0000000..0fc89f7 --- /dev/null +++ b/src/libs/camera/commands/goto_preset_position.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use crate::Command02; +use crate::libs::errors::T4lError; + +pub struct GotoPresetPositionCommand; + +impl GotoPresetPositionCommand { + pub fn build(preset_nr: i8) -> Result<[u8; 36], T4lError> { + if preset_nr < 0 || preset_nr > 3 { + return Err(T4lError::InvalidSetting); + } + + const FUNCTION_GROUP_PRESETS: [u8; 6] = [0x0a, 0x04, 0xc4, 0x39, 0x14, 0x00]; + + let (sequence_nr, checksum, command) = match preset_nr { + 0 => ( + [0x20, 0x00], + [0x6b, 0xdc], + [0xd6, 0xfb, 0x00, 0x00, 0x00, 0x00], + ), + 1 => ( + [0x1a, 0x00], + [0x4b, 0x03], + [0xeb, 0x2a, 0x01, 0x00, 0x00, 0x00], + ), + 2 => ( + [0x26, 0x00], + [0x8b, 0xc3], + [0xaf, 0x19, 0x02, 0x00, 0x00, 0x00], + ), + _ => { + println!("Invalid preset nr {}", preset_nr + 1); + return Err(T4lError::InvalidSetting); + } + }; + + Ok(Command02::new() + .function_group(FUNCTION_GROUP_PRESETS) + .sequence_nr(sequence_nr) + .checksum(checksum) + .command(command) + .appendix({ + let mut arr = [0u8; 16]; + for i in 0..4 { + arr[i * 4..(i + 1) * 4].copy_from_slice(&[0x00, 0x00, 0x80, 0x3f]); + } + arr + }) + .build()) + } +} diff --git a/src/libs/camera/commands/hdr_mode.rs b/src/libs/camera/commands/hdr_mode.rs new file mode 100644 index 0000000..497e2a5 --- /dev/null +++ b/src/libs/camera/commands/hdr_mode.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: EUPL-1.2 + +pub struct HdrModeCommand; + +impl HdrModeCommand { + pub fn build(mode: bool) -> [u8; 3] { + if mode { + [0x01, 0x01, 0x01] + } else { + [0x01, 0x01, 0x00] + } + } +} diff --git a/src/libs/camera/commands/mod.rs b/src/libs/camera/commands/mod.rs new file mode 100644 index 0000000..358824b --- /dev/null +++ b/src/libs/camera/commands/mod.rs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: EUPL-1.2 + +mod ai_mode; +mod exposure_mode; +mod exposure_mode_type; +mod goto_preset_position; +mod hdr_mode; +mod sleep; +mod tracking_speed; + +pub use ai_mode::*; +pub use exposure_mode::*; +pub use exposure_mode_type::*; +pub use goto_preset_position::*; +pub use hdr_mode::*; +pub use sleep::*; +pub use tracking_speed::*; diff --git a/src/libs/camera/commands/sleep.rs b/src/libs/camera/commands/sleep.rs new file mode 100644 index 0000000..a9c0fc9 --- /dev/null +++ b/src/libs/camera/commands/sleep.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use crate::Command02; +use crate::SleepMode; +use crate::libs::errors::T4lError; + +pub struct SleepCommand; + +impl SleepCommand { + pub fn build(mode: SleepMode) -> Result<[u8; 36], T4lError> { + if mode == SleepMode::Unknown { + return Err(T4lError::InvalidSetting); + } + + const FUNCTION_GROUP_SLEEP: [u8; 6] = [0x0a, 0x02, 0xc2, 0xa0, 0x04, 0x00]; + + let (sequence_nr, checksum, command) = match mode { + SleepMode::Awake => ( + [0xa5, 0x00], + [0x5f, 0xef], + [0xbe, 0x07, 0x00, 0x00, 0x00, 0x00], + ), + SleepMode::Sleep => ( + [0x42, 0x00], + [0xea, 0x63], + [0xbf, 0xfb, 0x01, 0x00, 0x00, 0x00], + ), + SleepMode::Unknown => panic!(), + }; + + Ok(Command02::new() + .function_group(FUNCTION_GROUP_SLEEP) + .sequence_nr(sequence_nr) + .checksum(checksum) + .command(command) + .build()) + } +} diff --git a/src/libs/camera/commands/tracking_speed.rs b/src/libs/camera/commands/tracking_speed.rs new file mode 100644 index 0000000..43b33c2 --- /dev/null +++ b/src/libs/camera/commands/tracking_speed.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use crate::Command02; +use crate::TrackingSpeed; +use crate::libs::errors::T4lError; + +pub struct TrackingSpeedCommand; + +impl TrackingSpeedCommand { + pub fn build(speed: TrackingSpeed) -> Result<[u8; 36], T4lError> { + const FUNCTION_GROUP_TRACKING_SPEED: [u8; 6] = [0x0a, 0x04, 0xc4, 0x0c, 0x01, 0x00]; + + let appendix: [u8; 16] = { + let mut a = [0x00; 16]; + a[..4].fill(0x00); + a + }; + + let (sequence_nr, checksum, command) = match speed { + TrackingSpeed::Standard => ( + [0x20, 0x00], + [0xab, 0xcb], + [0xe6, 0x3f, 0x00, 0x00, 0x00, 0x00], + ), + TrackingSpeed::Sport => ( + [0x21, 0x00], + [0xfa, 0x0e], + [0x67, 0xfe, 0x02, 0x00, 0x00, 0x00], + ), + }; + + Ok(Command02::new() + .function_group(FUNCTION_GROUP_TRACKING_SPEED) + .sequence_nr(sequence_nr) + .checksum(checksum) + .command(command) + .appendix(appendix) + .build()) + } +} diff --git a/src/libs/camera/enums.rs b/src/libs/camera/enums.rs new file mode 100644 index 0000000..ffee2e2 --- /dev/null +++ b/src/libs/camera/enums.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use std::fmt::Display; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum SleepMode { + Awake, + Sleep, + Unknown, +} + +impl Display for SleepMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SleepMode::Awake => write!(f, "Awake"), + SleepMode::Sleep => write!(f, "Sleeping"), + SleepMode::Unknown => write!(f, "Unknown"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum AIMode { + NoTracking, + NormalTracking, + UpperBody, + CloseUp, + Headless, + LowerBody, + DeskMode, + Whiteboard, + Hand, + Group, + Unknown, +} + +impl Display for AIMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AIMode::NoTracking => write!(f, "Static"), + AIMode::NormalTracking => write!(f, "Normal Tracking"), + AIMode::UpperBody => write!(f, "Upper Body"), + AIMode::CloseUp => write!(f, "Close-up"), + AIMode::Headless => write!(f, "Headless"), + AIMode::LowerBody => write!(f, "Lower Body"), + AIMode::DeskMode => write!(f, "Desk Mode"), + AIMode::Whiteboard => write!(f, "Whiteboard"), + AIMode::Hand => write!(f, "Hand"), + AIMode::Group => write!(f, "Group"), + AIMode::Unknown => write!(f, "Unknown"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum TrackingSpeed { + Standard, + Sport, +} + +impl Display for TrackingSpeed { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TrackingSpeed::Standard => write!(f, "Standard"), + TrackingSpeed::Sport => write!(f, "Sport"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ExposureMode { + Manual, + Global, + Face, +} + +impl Display for ExposureMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExposureMode::Manual => write!(f, "Manual"), + ExposureMode::Global => write!(f, "Global"), + ExposureMode::Face => write!(f, "Face"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ExposureModeType { + Auto, + Manual, +} diff --git a/src/libs/camera/mod.rs b/src/libs/camera/mod.rs new file mode 100644 index 0000000..0df8c2c --- /dev/null +++ b/src/libs/camera/mod.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: EUPL-1.2 + +mod camera; +mod command02; +mod commands; +mod enums; +mod status; +mod transport; + +pub use camera::Camera; +pub use camera::Tiny2Camera; +pub use command02::Command02; +pub use commands::*; +pub use enums::*; +pub use status::CameraStatus; diff --git a/src/libs/camera/status.rs b/src/libs/camera/status.rs new file mode 100644 index 0000000..4dd07d1 --- /dev/null +++ b/src/libs/camera/status.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use crate::libs::camera::enums::{AIMode, SleepMode, TrackingSpeed}; + +pub struct CameraStatus { + pub awake: SleepMode, + pub ai_mode: AIMode, + pub speed: TrackingSpeed, + pub hdr_on: bool, +} + +impl CameraStatus { + pub fn decode(bytes: &[u8]) -> Self { + CameraStatus { + awake: Self::decode_sleep_mode(bytes), + ai_mode: Self::decode_ai_mode(bytes), + speed: Self::decode_tracking_speed(bytes), + hdr_on: Self::decode_hdr_on(bytes), + } + } + + fn decode_sleep_mode(bytes: &[u8]) -> SleepMode { + match bytes[0x02] { + 0 => SleepMode::Awake, + 1 => SleepMode::Sleep, + _ => SleepMode::Unknown, + } + } + + fn decode_ai_mode(bytes: &[u8]) -> AIMode { + let m = bytes[0x18]; + let n = bytes[0x1c]; + + match (m, n) { + (0, 0) => AIMode::NoTracking, + (2, 0) => AIMode::NormalTracking, + (2, 1) => AIMode::UpperBody, + (2, 2) => AIMode::CloseUp, + (2, 3) => AIMode::Headless, + (2, 4) => AIMode::LowerBody, + (5, 0) => AIMode::DeskMode, + (4, 0) => AIMode::Whiteboard, + (6, 0) => AIMode::Hand, + (1, 0) => AIMode::Group, + (_, _) => AIMode::Unknown, + } + } + + fn decode_tracking_speed(bytes: &[u8]) -> TrackingSpeed { + match bytes[0x21] { + 0 => TrackingSpeed::Standard, + 2 => TrackingSpeed::Sport, + _ => TrackingSpeed::Standard, + } + } + + fn decode_hdr_on(bytes: &[u8]) -> bool { + bytes[0x6] != 0 + } + + pub fn default() -> Self { + CameraStatus { + awake: SleepMode::Unknown, + ai_mode: AIMode::Unknown, + speed: TrackingSpeed::Standard, + hdr_on: false, + } + } +} diff --git a/src/libs/camera/transport.rs b/src/libs/camera/transport.rs new file mode 100644 index 0000000..eaf347c --- /dev/null +++ b/src/libs/camera/transport.rs @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use crate::CameraStatus; +use crate::libs::errors::T4lError; +use crate::libs::usbio::{ + CameraHandle, UVC_GET_CUR, UVC_GET_LEN, UVC_SET_CUR, UvcUsbIo, open_camera, +}; +use errno::Errno; + +/// This is a wrapper around the USB camera transport. +/// It is used to send commands to a camera using a camera handle. +pub struct CameraTransport { + handle: CameraHandle, +} + +impl CameraTransport { + /// Creates a new instance of the CameraTransport struct. + /// + /// This function initializes a new instance by attempting to open a camera + /// using the provided `hint`. If the camera cannot be opened, it returns an + /// error of type `T4lError`. + /// + /// # Parameters + /// - `hint`: A string slice that serves as a hint to identify the camera to open. + /// + /// # Returns + /// - `Ok(Self)`: A new instance of the struct if the camera is successfully opened. + /// - `Err(T4lError)`: An error returned if the camera could not be opened. + /// + /// # Examples + /// ```rust,ignore + /// let instance = CameraTransport::new("OBSBOT Tiny 2"); + /// match instance { + /// Ok(camera) => println!("Camera opened successfully."), + /// Err(e) => println!("Failed to open camera: {:?}", e), + /// } + /// ``` + pub fn new(hint: &str) -> Result { + Ok(Self { + handle: open_camera(hint)?, + }) + } + + /// Retrieves and returns information about the current object or instance. + /// + /// This method invokes the `info` method on the associated handle to gather the required details. + /// The specific information returned depends on the implementation of the underlying handle's `info` method. + /// + /// # Returns + /// * `Ok(())` - If the operation completes successfully. + /// * `Err(Errno)` - If an error occurs during the process. The `Errno` indicates the specific error encountered. + /// + /// # Errors + /// This function may return an error if the underlying `info` call fails. + /// The error codes and their meanings are dependent on the implementation of the handle being used. + /// + /// # Example + /// ```rust,ignore + /// let result = object.info(); + /// if let Err(e) = result { + /// eprintln!("Failed to retrieve information: {:?}", e); + /// } else { + /// println!("Information retrieved successfully."); + /// } + /// ``` + pub fn info(&self) -> Result<(), Errno> { + self.handle.info() + } + + /// Sends a hexadecimal command to the specified unit and selector using the provided command data. + /// + /// # Parameters + /// - `unit`: The unit identifier to which the command is being sent. + /// - `selector`: The selector value specifying the target operation. + /// - `cmd`: A slice of bytes containing the command data to be sent. + /// - `debugging`: A boolean flag indicating whether debugging information should be printed to the console. + /// + /// # Returns + /// - `Ok(())`: If the command is successfully sent. + /// - `Err(T4lError)`: If an error occurs while sending the command, wrapped in a `T4lError::USBIOError`. + /// + /// # Errors + /// This method returns `T4lError::USBIOError` if the `set_cur` operation fails. + /// + /// # Example + /// ```rust,ignore + /// let cmd_data = [0x01, 0x02, 0x03]; + /// match device.send_cmd(0x2, 0x6, &cmd_data) { + /// Ok(_) => println!("Command sent successfully"), + /// Err(e) => eprintln!("Failed to send command: {:?}", e), + /// } + /// ``` + pub fn send_cmd( + &self, + unit: u8, + selector: u8, + cmd: &[u8], + debugging: bool, + ) -> Result<(), T4lError> { + let mut data = [0u8; 60]; + data[..cmd.len()].copy_from_slice(cmd); + + self.set_cur(unit, selector, &mut data, debugging) + .map_err(|e| T4lError::USBIOError(e.0)) + } + + /// Retrieves the current status of the camera. + /// + /// This method fetches the camera's current status by communicating with + /// the device, decoding the received data, and returning a `CameraStatus` + /// object if successful. + /// + /// # Parameters + /// - `debugging`: A boolean flag indicating whether debugging information should be printed to the console. + /// + /// # Returns + /// * `Ok(CameraStatus)` - A successfully decoded `CameraStatus` object representing + /// the current state of the camera. + /// * `Err(T4lError)` - An error encountered during the process, wrapped in `T4lError`. + /// + /// # Errors + /// This function can return the following errors: + /// * `T4lError::USBIOError` - If there is an issue during the USB communication. + /// + /// # Debugging + /// If the debugging mode is enabled via the `debugging` field, this function + /// will print the raw data and its hexadecimal representation to the console + /// for debugging purposes. + /// + /// # Example + /// ```rust,ignore + /// match camera.get_status() { + /// Ok(status) => println!("Camera status: {:?}", status), + /// Err(e) => eprintln!("Failed to get camera status: {:?}", e), + /// } + /// ``` + pub fn get_status(&self, debugging: bool) -> Result { + let mut data: [u8; 60] = [0u8; 60]; + self.get_cur(0x2, 0x6, &mut data) + .map_err(|x| T4lError::USBIOError(x.0))?; + + if debugging { + println!("Current state: {:?} {:}", data, hex::encode(&data)); + } + + Ok(CameraStatus::decode(&data)) + } + + /// Dumps the current state of the 0x2, 0x6 data to the console in hexadecimal format. + /// + /// # Returns + /// + /// - `Ok(())` if the data is successfully retrieved and dumped to the console. + /// - `Err(Errno)` if an error occurs during the data retrieval process. + /// + /// # Errors + /// + /// This method can return an error of type `Errno` if the `get_cur` method fails to retrieve + /// the data. + pub fn dump(&self) -> Result<(), Errno> { + let mut data: [u8; 60] = [0u8; 60]; + self.get_cur(0x2, 0x6, &mut data)?; + hexdump::hexdump(&data); + Ok(()) + } + + /// Dumps the current state of the 0x2, 0x2 data to the console in hexadecimal format. + /// + /// # Returns + /// + /// - `Ok(())` if the data is successfully retrieved and dumped to the console. + /// - `Err(Errno)` if an error occurs during the data retrieval process. + /// + /// # Errors + /// + /// This method can return an error of type `Errno` if the `get_cur` method fails to retrieve + /// the data. + pub fn dump_02(&self) -> Result<(), Errno> { + let mut data: [u8; 60] = [0u8; 60]; + self.get_cur(0x2, 0x2, &mut data)?; + hexdump::hexdump(&data); + Ok(()) + } + + fn get_cur(&self, unit: u8, selector: u8, data: &mut [u8]) -> Result<(), Errno> { + // always call get_len first + match self.get_len(unit, selector) { + Ok(size) => { + if data.len() < size { + println!("Got size {}", size); + return Err(Errno(1)); + } + } + Err(err) => return Err(err), + }; + + match self.io(unit, selector, UVC_GET_CUR, data) { + Ok(_) => Ok(()), + Err(err) => Err(err), + } + } + + fn set_cur( + &self, + unit: u8, + selector: u8, + data: &mut [u8], + debugging: bool, + ) -> Result<(), Errno> { + match self.get_len(unit, selector) { + Ok(size) => { + if data.len() > size { + println!("Got size {}", size); + return Err(Errno(1)); + } + } + Err(err) => return Err(err), + }; + + if debugging { + println!("{:} {:} {:}", unit, selector, hex::encode(&data)); + } + + match self.io(unit, selector, UVC_SET_CUR, data) { + Ok(_) => Ok(()), + Err(err) => Err(err), + } + } + + fn get_len(&self, unit: u8, selector: u8) -> Result { + let mut data = [0u8; 2]; + + match self.io(unit, selector, UVC_GET_LEN, &mut data) { + Ok(_) => Ok(u16::from_le_bytes(data).into()), + Err(err) => Err(err), + } + } + + fn io(&self, unit: u8, selector: u8, query: u8, data: &mut [u8]) -> Result<(), Errno> { + self.handle.io(unit, selector, query, data) + } +} diff --git a/src/libs/errors/enums.rs b/src/libs/errors/enums.rs new file mode 100644 index 0000000..95422e5 --- /dev/null +++ b/src/libs/errors/enums.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: EUPL-1.2 + +use std::io; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum T4lError { + #[error("value of {1} is not supported for {0}")] + UnsupportedIntValue(String, i32), + #[error("USB IO error: {0}")] + USBIOError(i32), + #[error("IO error: {0}")] + IOError(#[from] io::Error), + #[error("no camera found")] + NoCameraFound, + #[error("Invalid setting")] + InvalidSetting, +} diff --git a/src/libs/errors/mod.rs b/src/libs/errors/mod.rs new file mode 100644 index 0000000..796b399 --- /dev/null +++ b/src/libs/errors/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: EUPL-1.2 + +mod enums; + +pub use enums::T4lError; diff --git a/src/libs/mod.rs b/src/libs/mod.rs new file mode 100644 index 0000000..27efa74 --- /dev/null +++ b/src/libs/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: EUPL-1.2 + +mod camera; +mod errors; +mod usbio; + +pub use camera::*; diff --git a/src/usbio.rs b/src/libs/usbio.rs similarity index 97% rename from src/usbio.rs rename to src/libs/usbio.rs index 232c159..f818190 100644 --- a/src/usbio.rs +++ b/src/libs/usbio.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: EUPL-1.2 +use crate::libs::errors::T4lError; use enum_dispatch::enum_dispatch; use errno::Errno; use glob::MatchOptions; @@ -62,7 +63,7 @@ impl UvcUsbIo for CameraHandle { } } -pub(crate) fn open_camera(hint: &str) -> Result { +pub(crate) fn open_camera(hint: &str) -> Result { if let Ok(file) = File::open(hint) { return Ok(file.into()); } @@ -89,7 +90,7 @@ pub(crate) fn open_camera(hint: &str) -> Result { } } } - Err(crate::Error::NoCameraFound) + Err(T4lError::NoCameraFound) } #[repr(C)]