From 7e3f1f65864a5b6170258a1dc6e7d10387d31b3e Mon Sep 17 00:00:00 2001 From: Bono Fox Date: Sun, 12 Oct 2025 01:33:38 +0200 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20#21:=20API=20for=20reading=20th?= =?UTF-8?q?e=20tracking=20speed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tracking speed can be determined --- src/lib.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0e594c4..6a45d2b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,7 @@ pub struct Camera { pub struct CameraStatus { pub awake: SleepMode, pub ai_mode: AIMode, + pub speed: TrackingSpeed, pub hdr_on: bool, } @@ -48,6 +49,7 @@ impl CameraStatus { 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), } } @@ -79,6 +81,14 @@ impl CameraStatus { } } + 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 } @@ -87,6 +97,7 @@ impl CameraStatus { CameraStatus { awake: SleepMode::Unknown, ai_mode: AIMode::Unknown, + speed: TrackingSpeed::Standard, hdr_on: false, } } @@ -162,6 +173,12 @@ impl TryFrom for AIMode { } } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum TrackingSpeed { + Standard, + Sport, +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum ExposureMode { Manual, @@ -196,6 +213,7 @@ pub trait OBSBotWebCam { fn get_sleep_mode(&self) -> Result; fn set_ai_mode(&self, mode: AIMode) -> Result<(), Error>; fn get_ai_mode(&self) -> Result; + fn get_tracking_speed(&self) -> Result; fn set_hdr_mode(&self, mode: bool) -> Result<(), Error>; fn set_exposure_mode(&self, mode: ExposureMode) -> Result<(), Error>; } @@ -258,6 +276,10 @@ impl OBSBotWebCam for Camera { Ok(self.get_status()?.ai_mode) } + fn get_tracking_speed(&self) -> Result { + Ok(self.get_status()?.speed) + } + fn set_hdr_mode(&self, mode: bool) -> Result<(), Error> { let cmd = if mode { [0x01, 0x01, 0x01] From 8427a7ee52d5e84d2a8197587e5ce02d86d489e2 Mon Sep 17 00:00:00 2001 From: Bono Fox Date: Sun, 12 Oct 2025 01:42:03 +0200 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9C=A8=20#21:=20Controls=20for=20trackin?= =?UTF-8?q?g=20speed=20in=20the=20GUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - added API for setting the tracking speed - integrated API in the GUI --- src/gui/main.rs | 31 +++++++++++++++++++++++++++++-- src/lib.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/gui/main.rs b/src/gui/main.rs index 1daeb34..4a00c34 100644 --- a/src/gui/main.rs +++ b/src/gui/main.rs @@ -7,12 +7,13 @@ use iced::widget::{button, column, image, row, text, text_input, toggler}; use iced::{Alignment, Length, Size, Subscription, executor, time, window}; use iced::{Application, Command, Element, Settings, Theme}; use std::time::Duration; -use tiny4linux::{AIMode, Camera, ExposureMode, OBSBotWebCam, SleepMode}; +use tiny4linux::{AIMode, Camera, ExposureMode, OBSBotWebCam, SleepMode, TrackingSpeed}; #[derive(Debug, Clone, PartialEq)] enum Message { ChangeSleeping(bool), ChangeTracking(AIMode), + ChangeTrackingSpeed(TrackingSpeed), ChangeHDR(bool), ChangeExposure(ExposureMode), ChangeDebugging(bool), @@ -29,6 +30,7 @@ struct MainPanel { camera: Option, awake: SleepMode, tracking: AIMode, + tracking_speed: TrackingSpeed, hdr_on: bool, debugging_on: bool, text_input: String, @@ -57,6 +59,7 @@ impl Application for MainPanel { camera, awake: status.awake, tracking: status.ai_mode, + tracking_speed: status.speed, hdr_on: status.hdr_on, debugging_on: false, text_input: String::new(), @@ -101,6 +104,11 @@ impl Application for MainPanel { camera.set_ai_mode(tracking_type).unwrap(); Command::none() } + Message::ChangeTrackingSpeed(new_speed) => { + self.tracking_speed = new_speed; + camera.set_tracking_speed(new_speed).unwrap(); + Command::none() + } Message::ChangeHDR(new_mode) => { self.hdr_on = new_mode; camera.set_hdr_mode(new_mode).unwrap(); @@ -269,6 +277,25 @@ impl Application for MainPanel { .style(Button::Secondary), ] .spacing(10), + row![ + text("Tracking Speed: "), + button("Standard") + .on_press(Message::ChangeTrackingSpeed(TrackingSpeed::Standard)) + .style(if self.tracking_speed == TrackingSpeed::Standard { + Button::Primary + } else { + Button::Secondary + }), + button("Sport") + .on_press(Message::ChangeTrackingSpeed(TrackingSpeed::Sport)) + .style(if self.tracking_speed == TrackingSpeed::Sport { + Button::Primary + } else { + Button::Secondary + }), + ] + .spacing(10) + .padding([10, 0, 10, 0]), toggler(Some("HDR".to_string()), self.hdr_on, Message::ChangeHDR), toggler( Some("Debugging".to_string()), @@ -334,7 +361,7 @@ impl Application for MainPanel { fn main() -> iced::Result { MainPanel::run(Settings { window: window::Settings { - size: Size::from([300, 640]), + size: Size::from([300, 720]), resizable: false, decorations: true, ..Default::default() diff --git a/src/lib.rs b/src/lib.rs index 6a45d2b..17242ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -214,6 +214,7 @@ pub trait OBSBotWebCam { fn set_ai_mode(&self, mode: AIMode) -> Result<(), Error>; fn get_ai_mode(&self) -> Result; 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>; } @@ -280,6 +281,35 @@ impl OBSBotWebCam for Camera { Ok(self.get_status()?.speed) } + fn set_tracking_speed(&self, speed: TrackingSpeed) -> Result<(), Error> { + const FRAME_ID: [u8; 2] = [0xaa, 0x25]; + const SEGMENT_SIZE: [u8; 2] = [0x0c, 0x00]; + const FUNCTION_GROUP: [u8; 6] = [0x0a, 0x04, 0xc4, 0x0c, 0x01, 0x00]; + const APPENDIX: [u8; 7] = [0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]; + + let (sequence_nr, checksum, command) = match speed { + TrackingSpeed::Standard => ([0x20, 0x00], [0xab, 0xcb], [0xe6, 0x3f, 0x00]), + TrackingSpeed::Sport => ([0x21, 0x00], [0xfa, 0x0e], [0x67, 0xfe, 0x02]), + }; + + let cmd: [u8; 24] = [ + FRAME_ID.as_slice(), + sequence_nr.as_slice(), + SEGMENT_SIZE.as_slice(), + checksum.as_slice(), + FUNCTION_GROUP.as_slice(), + command.as_slice(), + APPENDIX.as_slice(), + ] + .concat() + .try_into() + .unwrap(); + + 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] From 0e5174a24aeec4e6974d51bde40417cf05d314ee Mon Sep 17 00:00:00 2001 From: Bono Fox Date: Sun, 12 Oct 2025 01:46:35 +0200 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=92=84=20Section=20for=20exposure=20m?= =?UTF-8?q?ode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - added headline and padding --- src/gui/main.rs | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/gui/main.rs b/src/gui/main.rs index 4a00c34..c33ee38 100644 --- a/src/gui/main.rs +++ b/src/gui/main.rs @@ -262,21 +262,26 @@ impl Application for MainPanel { }), ] .spacing(10), - row![ - button("Manual") - .on_press(Message::ChangeExposure(ExposureMode::Manual)) - .width(Length::Fill) - .style(Button::Secondary), - button("Face") - .on_press(Message::ChangeExposure(ExposureMode::Face)) - .width(Length::Fill) - .style(Button::Secondary), - button("Global") - .on_press(Message::ChangeExposure(ExposureMode::Global)) - .width(Length::Fill) - .style(Button::Secondary), + column![ + text("Exposure: "), + row![ + button("Manual") + .on_press(Message::ChangeExposure(ExposureMode::Manual)) + .width(Length::Fill) + .style(Button::Secondary), + button("Face") + .on_press(Message::ChangeExposure(ExposureMode::Face)) + .width(Length::Fill) + .style(Button::Secondary), + button("Global") + .on_press(Message::ChangeExposure(ExposureMode::Global)) + .width(Length::Fill) + .style(Button::Secondary), + ] + .spacing(10) ] - .spacing(10), + .spacing(10) + .padding([10, 0, 10, 0]), row![ text("Tracking Speed: "), button("Standard") From 566e8c6d7f6550d424cd286cee1a75343b1b39f5 Mon Sep 17 00:00:00 2001 From: Bono Fox Date: Sun, 12 Oct 2025 01:50:31 +0200 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=9A=B8=20Show=20tracking=20speed=20in?= =?UTF-8?q?=20text=20literal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gui/main.rs | 6 +++++- src/lib.rs | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/gui/main.rs b/src/gui/main.rs index c33ee38..64d0bc0 100644 --- a/src/gui/main.rs +++ b/src/gui/main.rs @@ -308,7 +308,11 @@ impl Application for MainPanel { Message::ChangeDebugging ), text(if self.awake == SleepMode::Awake { - self.tracking.to_string() + format!( + "{tracking_mode} ({tracking_speed})", + tracking_mode = self.tracking.to_string(), + tracking_speed = self.tracking_speed.to_string() + ) } else { self.awake.to_string() }) diff --git a/src/lib.rs b/src/lib.rs index 17242ca..887e73e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,6 +179,15 @@ pub enum TrackingSpeed { 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, From 801d7c0d5bff8e87d2ec6da241ee76b6414bfe18 Mon Sep 17 00:00:00 2001 From: Bono Fox Date: Sun, 12 Oct 2025 01:56:45 +0200 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=93=9D=20Added=20the=20tracking=20spe?= =?UTF-8?q?ed=20feature=20in=20the=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fe98a3e..85377c1 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ which itself is substantially based on [samliddicott's meet4k package](https://g ## New features and improvements - Toggle for Sleep/Wake +- Controls for the tracking speed - Updated Dependencies ### Shortly planned features From 94dd9a889494060b1a8350b943aca32dec7c1852 Mon Sep 17 00:00:00 2001 From: Bono Fox Date: Sun, 12 Oct 2025 01:58:10 +0200 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=93=9D=20Small=20improvements=20to=20?= =?UTF-8?q?the=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 85377c1..9e7b3aa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Tiny4Linux: A GUI-Controller for the OBSBOT Tiny2 With the Tiny series, OBSBOT offers an affordable PTZ camera option for everyone. -The OBSBOT Center provides a way to fully control these devices: https://www.obsbot.com/de/download/obsbot-tiny-2 \ +The OBSBOT Center provides a way to fully control these devices: https://www.obsbot.com/de/download/obsbot-tiny-2 + As of [September 2025](https://web.archive.org/web/20251008100439/https://www.obsbot.com/download/obsbot-tiny-2), however, this software is only available for users on Windows and macOS. According to [the latest Steam Hardware Survey](https://web.archive.org/web/20251008101418/https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam), Linux users account for 2.68%, which is actually more than macOS users. Especially within the tech community, many users (rightly so :P) rely on Linux as their operating system. @@ -22,14 +23,15 @@ which itself is substantially based on [samliddicott's meet4k package](https://g ## New features and improvements -- Toggle for Sleep/Wake -- Controls for the tracking speed +- Toggle for **Sleep/Wake** +- Controls for the **tracking speed** - Updated Dependencies ### Shortly planned features - Apply position-presets -- UI-Upgrade +- UI-Upgrade (WIP) +- Tests & Codecov check ## Run the GUI