Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand Down
29 changes: 26 additions & 3 deletions src/gui/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -22,6 +23,7 @@ enum Message {

struct MainPanel {
camera: Option<Camera>,
awake: SleepMode,
tracking: AIMode,
hdr_on: bool,
debugging_on: bool,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -126,7 +140,7 @@ impl Application for MainPanel {
}
}

fn view(&self) -> Element<Message> {
fn view(&'_ self) -> Element<'_, Message> {
if self.camera.is_some() {
let mut elements = column![
row![
Expand All @@ -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![
Expand Down Expand Up @@ -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 {
Expand Down
108 changes: 93 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub enum Error {
OscError(#[from] rosc::OscError),
#[error("no camera found")]
NoCameraFound,
#[error("Invalid setting")]
InvalidSetting,
}

#[derive(Debug)]
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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"),
Expand All @@ -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"),
}
}
}
Expand All @@ -130,6 +162,13 @@ impl TryFrom<i32> for AIMode {
}
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ExposureMode {
Manual,
Global,
Face,
}

pub enum TrackingMode {
Headroom,
Standard,
Expand All @@ -153,13 +192,51 @@ impl TryFrom<i32> for TrackingMode {
}

pub trait OBSBotWebCam {
fn set_sleep_mode(&self, mode: SleepMode) -> Result<(), Error>;
fn get_sleep_mode(&self) -> Result<SleepMode, Error>;
fn set_ai_mode(&self, mode: AIMode) -> Result<(), Error>;
fn get_ai_mode(&self) -> Result<AIMode, Error>;
fn set_hdr_mode(&self, mode: bool) -> Result<(), Error>;
fn set_exposure_mode(&self, mode: ExposureMode) -> Result<(), Error>;
}

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<SleepMode, Error> {
Ok(self.get_status()?.awake)
}

fn set_ai_mode(&self, mode: AIMode) -> Result<(), Error> {
let cmd = match mode {
AIMode::NoTracking => [0x16, 0x02, 0x00, 0x00],
Expand All @@ -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)
}
Expand Down