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
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ which itself is substantially based on [samliddicott's meet4k package](https://g

- Toggle for **Sleep/Wake**
- Controls for the **tracking speed**
- Controls for **preset positions**
- Updated Dependencies

### Shortly planned features

- Apply position-presets
- UI-Upgrade (WIP)
- Tests & Codecov check

Expand All @@ -38,8 +38,6 @@ which itself is substantially based on [samliddicott's meet4k package](https://g
To run the GUI, you have to build the project and then run the binary:

```shell
cargo build

cargo run --package tiny4linux --bin obsbot-gui
```

Expand Down
29 changes: 28 additions & 1 deletion src/gui/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum Message {
ChangeSleeping(bool),
ChangeTracking(AIMode),
ChangeTrackingSpeed(TrackingSpeed),
ChangePresetPosition(i8),
ChangeHDR(bool),
ChangeExposure(ExposureMode),
ChangeDebugging(bool),
Expand Down Expand Up @@ -109,6 +110,12 @@ impl Application for MainPanel {
camera.set_tracking_speed(new_speed).unwrap();
Command::none()
}
Message::ChangePresetPosition(new_position) => {
self.tracking = AIMode::NoTracking;
camera.set_ai_mode(AIMode::NoTracking).unwrap();
camera.goto_preset_position(new_position).unwrap();
Command::none()
}
Message::ChangeHDR(new_mode) => {
self.hdr_on = new_mode;
camera.set_hdr_mode(new_mode).unwrap();
Expand Down Expand Up @@ -282,6 +289,26 @@ impl Application for MainPanel {
]
.spacing(10)
.padding([10, 0, 10, 0]),
column![
text("Presets: "),
row![
button("1")
.on_press(Message::ChangePresetPosition(0))
.width(Length::Fill)
.style(Button::Secondary),
button("2")
.on_press(Message::ChangePresetPosition(1))
.width(Length::Fill)
.style(Button::Secondary),
button("3")
.on_press(Message::ChangePresetPosition(2))
.width(Length::Fill)
.style(Button::Secondary),
]
.spacing(10)
]
.spacing(10)
.padding([10, 0, 10, 0]),
row![
text("Tracking Speed: "),
button("Standard")
Expand Down Expand Up @@ -370,7 +397,7 @@ impl Application for MainPanel {
fn main() -> iced::Result {
MainPanel::run(Settings {
window: window::Settings {
size: Size::from([300, 720]),
size: Size::from([300, 900]),
resizable: false,
decorations: true,
..Default::default()
Expand Down
236 changes: 190 additions & 46 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,6 @@ use std::{fmt::Display, io};
use thiserror::Error;
use usbio::UvcUsbIo;

const AUTO_EXP_CMD: [u8; 18] = [
0xaa, 0x25, 0x16, 0x00, 0x0c, 0x00, 0x58, 0x91, 0x0a, 0x02, 0x82, 0x29, 0x05, 0x00, 0xb2, 0xaf,
0x02, 0x04,
];
const MANUAL_EXP_CMD: [u8; 18] = [
0xaa, 0x25, 0x15, 0x00, 0x0c, 0x00, 0xa8, 0x9e, 0x0a, 0x02, 0x82, 0x29, 0x05, 0x00, 0xf9, 0x27,
0x01, 0x32,
];

#[derive(Error, Debug)]
pub enum Error {
#[error("value of {1} is not supported for {0}")]
Expand Down Expand Up @@ -195,6 +186,12 @@ pub enum ExposureMode {
Face,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ExposureModeType {
Auto,
Manual,
}

pub enum TrackingMode {
Headroom,
Standard,
Expand Down Expand Up @@ -222,10 +219,12 @@ pub trait OBSBotWebCam {
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 goto_preset_position(&self, preset_nr: i8) -> Result<(), Error>;
fn get_tracking_speed(&self) -> Result<TrackingSpeed, Error>;
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>;
}

impl OBSBotWebCam for Camera {
Expand All @@ -234,27 +233,28 @@ impl OBSBotWebCam for Camera {
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];
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]),
SleepMode::Sleep => ([0x42, 0x00], [0xea, 0x63], [0xbf, 0xfb, 0x01, 0x00, 0x00]),
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: [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();
let cmd = Command02::new()
.function_group(FUNCTION_GROUP_SLEEP)
.sequence_nr(sequence_nr)
.checksum(checksum)
.command(command)
.build();

self.get_status()?.awake = mode;

Expand Down Expand Up @@ -286,33 +286,82 @@ impl OBSBotWebCam for Camera {
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],
),
_ => panic!(),
};

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<TrackingSpeed, Error> {
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];
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]),
TrackingSpeed::Sport => ([0x21, 0x00], [0xfa, 0x0e], [0x67, 0xfe, 0x02]),
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: [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();
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;

Expand All @@ -331,19 +380,47 @@ impl OBSBotWebCam for Camera {
fn set_exposure_mode(&self, mode: ExposureMode) -> Result<(), Error> {
match mode {
ExposureMode::Manual => {
self.send_cmd(0x2, 0x2, &MANUAL_EXP_CMD)?;
self.set_exposure_mode_type(ExposureModeType::Manual)?;
}
ExposureMode::Global => {
self.send_cmd(0x2, 0x2, &AUTO_EXP_CMD)?;
self.set_exposure_mode_type(ExposureModeType::Auto)?;
self.send_cmd(0x2, 0x6, &[0x03, 0x01, 0x00])?;
}
ExposureMode::Face => {
self.send_cmd(0x2, 0x2, &AUTO_EXP_CMD)?;
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(())
}
}

impl Camera {
Expand Down Expand Up @@ -437,3 +514,70 @@ impl Camera {
self.handle.io(unit, selector, query, data)
}
}

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()
}
}