diff --git a/README.md b/README.md index 1717a51..fe98a3e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Especially within the tech community, many users (rightly so :P) rely on Linux a Unfortunately, they currently miss out on the configuration options offered by the OBSBOT Center. To partially address this issue, [OBSBOT now provides an SDK](https://www.obsbot.com/de/sdk), which allows developers to replicate certain functionalities. +However, the availability of the SDK is still limited. +Fortunately, the community has since managed to implement several solutions on its own through [reverse engineering](https://github.com/taxfromdk/obsbot_tiny_reversing) and a lot of dedication. This project provides a graphical interface for some basic features. However, to make full use of all functions, a configuration must first be performed in the OBSBOT Center using one of the supported operating systems. @@ -18,6 +20,16 @@ A guide on how to do this quickly and easily on Linux can be found below. This repository is a fork of [Constantine Evans's "Tiny2"](https://github.com/cgevans/tiny2), which itself is substantially based on [samliddicott's meet4k package](https://github.com/samliddicott/meet4k). +## New features and improvements + +- Toggle for Sleep/Wake +- Updated Dependencies + +### Shortly planned features + +- Apply position-presets +- UI-Upgrade + ## Run the GUI To run the GUI, you have to build the project and then run the binary: diff --git a/src/gui/main.rs b/src/gui/main.rs index fd0291e..2402b38 100644 --- a/src/gui/main.rs +++ b/src/gui/main.rs @@ -3,10 +3,11 @@ use iced::widget::{button, column, image, row, text, text_input, toggler}; use iced::{Alignment, Length, Subscription, executor, time, window}; use iced::{Application, Command, Element, Settings, Theme}; use std::time::Duration; -use tiny4linux::{AIMode, Camera, ExposureMode, OBSBotWebCam}; +use tiny4linux::{AIMode, Camera, ExposureMode, OBSBotWebCam, SleepMode}; #[derive(Debug, Clone, PartialEq)] enum Message { + ChangeSleeping(bool), ChangeTracking(AIMode), ChangeHDR(bool), ChangeExposure(ExposureMode), @@ -22,6 +23,7 @@ enum Message { struct MainPanel { camera: Option, + awake: SleepMode, tracking: AIMode, hdr_on: bool, debugging_on: bool, @@ -49,6 +51,7 @@ impl Application for MainPanel { ( MainPanel { camera, + awake: status.awake, tracking: status.ai_mode, hdr_on: status.hdr_on, debugging_on: false, @@ -78,6 +81,17 @@ impl Application for MainPanel { let camera = self.camera.as_ref().unwrap(); match message { + Message::ChangeSleeping(should_sleep) => { + if should_sleep { + self.awake = SleepMode::Sleep; + camera.set_sleep_mode(SleepMode::Sleep).unwrap(); + } else { + self.awake = SleepMode::Awake; + camera.set_sleep_mode(SleepMode::Awake).unwrap(); + } + + Command::none() + } Message::ChangeTracking(tracking_type) => { self.tracking = tracking_type; camera.set_ai_mode(tracking_type).unwrap(); @@ -126,7 +140,7 @@ impl Application for MainPanel { } } - fn view(&self) -> Element { + fn view(&'_ self) -> Element<'_, Message> { if self.camera.is_some() { let mut elements = column![ row![ @@ -136,6 +150,11 @@ impl Application for MainPanel { .vertical_alignment(Vertical::Center), image("src/assets/obsbot-tiny-2.png").height(100) ], + toggler( + Some("Sleeping".to_string()), + self.awake != SleepMode::Awake, + Message::ChangeSleeping + ), button("Static").on_press(Message::ChangeTracking(AIMode::NoTracking)), button("Normal Tracking").on_press(Message::ChangeTracking(AIMode::NormalTracking)), row![ @@ -192,7 +211,11 @@ impl Application for MainPanel { self.debugging_on, Message::ChangeDebugging ), - text(self.tracking) + text(if self.awake == SleepMode::Awake { + self.tracking.to_string() + } else { + self.awake.to_string() + }) ]; if self.debugging_on { diff --git a/src/lib.rs b/src/lib.rs index b96a9c8..0e594c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,8 @@ pub enum Error { OscError(#[from] rosc::OscError), #[error("no camera found")] NoCameraFound, + #[error("Invalid setting")] + InvalidSetting, } #[derive(Debug)] @@ -36,16 +38,33 @@ pub struct Camera { } pub struct CameraStatus { + pub awake: SleepMode, pub ai_mode: AIMode, 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), + 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]; - let ai_mode = match (m, n) { + match (m, n) { (0, 0) => AIMode::NoTracking, (2, 0) => AIMode::NormalTracking, (2, 1) => AIMode::UpperBody, @@ -56,22 +75,40 @@ impl CameraStatus { (4, 0) => AIMode::Whiteboard, (6, 0) => AIMode::Hand, (1, 0) => AIMode::Group, - (_, _) => panic!(), - }; - - let hdr_on = bytes[0x6] != 0; + (_, _) => AIMode::Unknown, + } + } - CameraStatus { ai_mode, hdr_on } + fn decode_hdr_on(bytes: &[u8]) -> bool { + bytes[0x6] != 0 } pub fn default() -> Self { CameraStatus { - ai_mode: AIMode::NoTracking, + awake: SleepMode::Unknown, + ai_mode: AIMode::Unknown, 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, @@ -84,19 +121,13 @@ pub enum AIMode { Whiteboard, Hand, Group, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ExposureMode { - Manual, - Global, - Face, + Unknown, } impl Display for AIMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - AIMode::NoTracking => write!(f, "No Tracking"), + AIMode::NoTracking => write!(f, "Static"), AIMode::NormalTracking => write!(f, "Normal Tracking"), AIMode::UpperBody => write!(f, "Upper Body"), AIMode::CloseUp => write!(f, "Close-up"), @@ -106,6 +137,7 @@ impl Display for AIMode { AIMode::Whiteboard => write!(f, "Whiteboard"), AIMode::Hand => write!(f, "Hand"), AIMode::Group => write!(f, "Group"), + AIMode::Unknown => write!(f, "Unknown"), } } } @@ -130,6 +162,13 @@ impl TryFrom for AIMode { } } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ExposureMode { + Manual, + Global, + Face, +} + pub enum TrackingMode { Headroom, Standard, @@ -153,6 +192,8 @@ impl TryFrom for TrackingMode { } 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 set_hdr_mode(&self, mode: bool) -> Result<(), Error>; @@ -160,6 +201,42 @@ pub trait OBSBotWebCam { } impl OBSBotWebCam for Camera { + fn set_sleep_mode(&self, mode: SleepMode) -> Result<(), Error> { + if mode == SleepMode::Unknown { + return Err(Error::InvalidSetting); + } + + const FRAME_ID: [u8; 2] = [0xaa, 0x25]; + const SEGMENT_SIZE: [u8; 2] = [0x0c, 0x00]; + const FUNCTION_GROUP: [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]), + SleepMode::Sleep => ([0x42, 0x00], [0xea, 0x63], [0xbf, 0xfb, 0x01, 0x00, 0x00]), + SleepMode::Unknown => panic!(), + }; + + let cmd: [u8; 19] = [ + FRAME_ID.as_slice(), + sequence_nr.as_slice(), + SEGMENT_SIZE.as_slice(), + checksum.as_slice(), + FUNCTION_GROUP.as_slice(), + command.as_slice(), + ] + .concat() + .try_into() + .unwrap(); + + 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], @@ -172,6 +249,7 @@ impl OBSBotWebCam for Camera { 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) }