Add new device module

This commit is contained in:
Lennard Kittner
2025-03-19 09:29:09 +01:00
parent 135eb3cfdb
commit a2d53f3f04
3 changed files with 382 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
use std::time::Duration;
use crate::devices::{ChargingStatus, Color, Device, DeviceError, DeviceEvent, DeviceState};
// Possible vendor IDs [HP]
const VENDOR_IDS: [u16; 1] = [0x03F0];
// Possible Cloud II Wireless product IDs
const PRODUCT_IDS: [u16; 4] = [0x1718, 0x018B, 0x0D93, 0x0696];
const BASE_PACKET: [u8; 20] = {
let mut packet = [0; 20];
(packet[0], packet[1], packet[2]) = (0x06, 0xff, 0xbb);
packet
};
const BASE_PACKET2: [u8; 20] = {
let mut packet = [0; 20];
(packet[0], packet[1]) = (33, 187);
packet
};
const GET_CHARGING_CMD_ID: u8 = 3;
const GET_MIC_CONNECTED_CMD_ID: u8 = 8;
const GET_BATTERY_CMD_ID: u8 = 2;
const GET_AUTO_SHUTDOWN_CMD_ID: u8 = 7;
const SET_AUTO_SHUTDOWN_CMD_ID: u8 = 34;
const GET_MUTE_CMD_ID: u8 = 5;
const SET_MUTE_CMD_ID: u8 = 32;
const GET_PAIRING_CMD_ID: u8 = 9;
const GET_PRODUCT_COLOR_CMD_ID: u8 = 14;
const GET_SIDE_TONE_ON_CMD_ID: u8 = 6;
const SET_SIDE_TONE_ON_CMD_ID: u8 = 33;
const GET_SIDE_TONE_VOLUME_CMD_ID: u8 = 11;
const SET_SIDE_TONE_VOLUME_CMD_ID: u8 = 35;
const GET_VOICE_PROMPT_CMD_ID: u8 = 9;
const SET_VOICE_PROMPT_CMD_ID: u8 = 19;
const GET_WIRELESS_STATUS_CMD_ID: u8 = 1;
pub struct CloudIIWirelessDTS {
state: DeviceState,
}
impl CloudIIWirelessDTS {
pub fn new_from_state(state: DeviceState) -> Self {
CloudIIWirelessDTS { state }
}
pub fn new() -> Result<Self, DeviceError> {
let state = DeviceState::new(&PRODUCT_IDS, &VENDOR_IDS)?;
Ok(CloudIIWirelessDTS { state })
}
}
impl Device for CloudIIWirelessDTS {
fn get_charging_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = GET_CHARGING_CMD_ID;
tmp
}
fn get_battery_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = GET_BATTERY_CMD_ID;
tmp
}
fn set_automatic_shut_down_packet(&self, shutdown_after: Duration) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = SET_AUTO_SHUTDOWN_CMD_ID;
tmp[4] = (shutdown_after.as_secs() / 60) as u8;
tmp
}
fn get_automatic_shut_down_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = GET_AUTO_SHUTDOWN_CMD_ID;
tmp
}
fn get_mute_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = GET_MUTE_CMD_ID;
tmp
}
fn set_mute_packet(&self, mute: bool) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = SET_MUTE_CMD_ID;
tmp[4] = mute as u8;
tmp
}
fn get_mic_connected_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = GET_MIC_CONNECTED_CMD_ID;
tmp
}
fn get_pairing_info_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = GET_PAIRING_CMD_ID;
tmp
}
fn get_product_color_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET2.to_vec();
tmp[2] = GET_PRODUCT_COLOR_CMD_ID;
tmp
}
fn get_side_tone_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = GET_SIDE_TONE_ON_CMD_ID;
tmp
}
fn set_side_tone_packet(&self, side_tone_on: bool) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = SET_SIDE_TONE_ON_CMD_ID;
tmp[4] = side_tone_on as u8;
tmp
}
fn get_side_tone_volume_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = GET_SIDE_TONE_VOLUME_CMD_ID;
tmp
}
fn set_side_tone_volume_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = SET_SIDE_TONE_VOLUME_CMD_ID;
tmp
}
fn get_voice_prompt_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET2.to_vec();
tmp[2] = GET_VOICE_PROMPT_CMD_ID;
tmp
}
fn set_voice_prompt_packet(&self, enable: bool) -> Vec<u8> {
let mut tmp = BASE_PACKET2.to_vec();
tmp[2] = SET_VOICE_PROMPT_CMD_ID;
tmp
}
fn get_wireless_connected_status_packet(&self) -> Vec<u8> {
let mut tmp = BASE_PACKET.to_vec();
tmp[3] = GET_WIRELESS_STATUS_CMD_ID;
tmp
}
fn get_event_from_device_response(&self, response: &[u8]) -> Option<DeviceEvent> {
match (response[2], response[3], response[4], response[7]) {
(_, GET_CHARGING_CMD_ID, status, _) => Some(DeviceEvent::Charging(ChargingStatus::from(status))),
(_, GET_MIC_CONNECTED_CMD_ID, status, _) => Some(DeviceEvent::MicConnected(status == 1)),
(_, GET_BATTERY_CMD_ID, _, level) => Some(DeviceEvent::BatterLevel(level)),
(_, GET_AUTO_SHUTDOWN_CMD_ID, time, _) => Some(DeviceEvent::AutomaticShutdownAfter(Duration::from_secs(time as u64 * 60))),
(_, GET_MUTE_CMD_ID, status, _) => Some(DeviceEvent::Muted(status == 1)),
(_, GET_PAIRING_CMD_ID, status, _) => Some(DeviceEvent::PairingInfo(status)),
(_, GET_SIDE_TONE_ON_CMD_ID, status, _) => Some(DeviceEvent::SideToneOn(status == 1)),
(_, GET_SIDE_TONE_VOLUME_CMD_ID, status, _) => Some(DeviceEvent::SideToneVolume(status)),
(_, GET_WIRELESS_STATUS_CMD_ID, status, _) => Some(DeviceEvent::WirelessConnected(status == 1 || status == 4)),
(GET_VOICE_PROMPT_CMD_ID, status, _, _) => Some(DeviceEvent::VoicePrompt(status == 1)),
(GET_PRODUCT_COLOR_CMD_ID, status, _, _) => Some(DeviceEvent::ProductColor(Color::from(status))),
_ => None
}
}
fn get_device_state(&mut self) -> &mut DeviceState {
&mut self.state
}
}

207
src/devices/mod.rs Normal file
View File

@@ -0,0 +1,207 @@
mod cloud_ii_wireless_dts;
use hidapi::{HidApi, HidDevice, HidError};
use std::time::Duration;
use thistermination::TerminationFull;
//TODO: connect to rest of code base
//TODO: remove old lib stuff
pub struct DeviceState {
hid_device: HidDevice,
pub battery_level: Option<u8>,
pub charging: Option<ChargingStatus>,
pub muted: Option<bool>,
pub mic_connected: Option<bool>,
pub automatic_shutdown_after: Option<Duration>,
pub pairing_info: Option<u8>,
pub product_color: Option<Color>,
pub side_tone_on: Option<bool>,
pub side_tone_volume: Option<u8>,
pub voice_prompt_on: Option<bool>,
pub connected: Option<bool>,
}
impl DeviceState {
pub fn new(product_ids: &[u16], vendor_ids: &[u16]) -> Result<Self, DeviceError> {
let hid_api = HidApi::new()?;
let hid_device = hid_api
.device_list()
.find_map(|info| {
if product_ids.contains(&info.product_id())
&& vendor_ids.contains(&info.vendor_id())
{
Some(hid_api.open(info.vendor_id(), info.product_id()))
} else {
None
}
})
.ok_or(DeviceError::NoDeviceFound())??;
Ok(DeviceState {
hid_device,
charging: None,
battery_level: None,
muted: None,
mic_connected: None,
automatic_shutdown_after: None,
pairing_info: None,
product_color: None,
side_tone_on: None,
side_tone_volume: None,
voice_prompt_on: None,
connected: None,
})
}
fn update_self_with_event(&mut self, event: &DeviceEvent) {
match event {
DeviceEvent::BatterLevel(level) => self.battery_level = Some(*level),
DeviceEvent::Charging(status) => self.charging = Some(*status),
DeviceEvent::Muted(status) => self.muted = Some(*status),
DeviceEvent::MicConnected(status) => self.mic_connected = Some(*status),
DeviceEvent::AutomaticShutdownAfter(duration) => {
self.automatic_shutdown_after = Some(*duration)
}
DeviceEvent::PairingInfo(info) => self.pairing_info = Some(*info),
DeviceEvent::ProductColor(color) => self.product_color = Some(*color),
DeviceEvent::SideToneOn(side) => self.side_tone_on = Some(*side),
DeviceEvent::SideToneVolume(volume) => self.side_tone_volume = Some(*volume),
DeviceEvent::VoicePrompt(on) => self.voice_prompt_on = Some(*on),
DeviceEvent::WirelessConnected(connected) => self.connected = Some(*connected),
};
}
pub fn clear_state(&mut self) {
self.charging = None;
self.battery_level = None;
self.muted = None;
self.mic_connected = None;
}
}
#[derive(TerminationFull)]
pub enum DeviceError {
#[termination(msg("{0:?}"))]
HidError(#[from] HidError),
#[termination(msg("No device found."))]
NoDeviceFound(),
#[termination(msg("No response. Is the headset turned on?"))]
HeadSetOff(),
#[termination(msg("No response."))]
NoResponse(),
#[termination(msg("Unknown response: {0:?} with length: {1:?}"))]
UnknownResponse([u8; 8], usize),
}
#[derive(Debug, Copy, Clone)]
pub enum DeviceEvent {
BatterLevel(u8),
Muted(bool),
MicConnected(bool),
Charging(ChargingStatus),
AutomaticShutdownAfter(Duration),
PairingInfo(u8),
ProductColor(Color),
SideToneOn(bool),
SideToneVolume(u8),
VoicePrompt(bool),
WirelessConnected(bool),
}
#[derive(Debug, Copy, Clone)]
pub enum Color {
Red,
UnknownColor(u8),
}
impl From<u8> for Color {
fn from(color: u8) -> Self {
match color {
0 => Color::Red,
_ => Color::UnknownColor(color),
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum ChargingStatus {
NotCharging,
Charging,
FullyCharged,
ChargeError,
}
impl From<u8> for ChargingStatus {
fn from(value: u8) -> ChargingStatus {
match value {
0 => ChargingStatus::NotCharging,
1 => ChargingStatus::Charging,
2 => ChargingStatus::FullyCharged,
_ => ChargingStatus::ChargeError,
}
}
}
trait Device {
fn get_charging_packet(&self) -> Vec<u8>;
fn get_battery_packet(&self) -> Vec<u8>;
fn set_automatic_shut_down_packet(&self, shutdown_after: Duration) -> Vec<u8>;
fn get_automatic_shut_down_packet(&self) -> Vec<u8>;
fn get_mute_packet(&self) -> Vec<u8>;
fn set_mute_packet(&self, mute: bool) -> Vec<u8>;
fn get_mic_connected_packet(&self) -> Vec<u8>;
fn get_pairing_info_packet(&self) -> Vec<u8>;
fn get_product_color_packet(&self) -> Vec<u8>;
fn get_side_tone_packet(&self) -> Vec<u8>;
fn set_side_tone_packet(&self, side_tone_on: bool) -> Vec<u8>;
fn get_side_tone_volume_packet(&self) -> Vec<u8>;
fn set_side_tone_volume_packet(&self) -> Vec<u8>;
fn get_voice_prompt_packet(&self) -> Vec<u8>;
fn set_voice_prompt_packet(&self, enable: bool) -> Vec<u8>;
fn get_wireless_connected_status_packet(&self) -> Vec<u8>;
fn get_event_from_device_response(&self, response: &[u8]) -> Option<DeviceEvent>;
fn get_device_state(&mut self) -> &mut DeviceState;
fn wait_for_updates(&mut self, duration: Duration) -> Option<DeviceEvent> {
let mut buf = [0u8; 8];
let res = self
.get_device_state()
.hid_device
.read_timeout(&mut buf[..], duration.as_millis() as i32)
.ok()?;
self.get_event_from_device_response(&buf[0..res])
}
fn refresh_state(&mut self) -> Result<(), DeviceError> {
let packets = vec![
self.get_charging_packet(),
self.get_battery_packet(),
self.get_automatic_shut_down_packet(),
self.get_mute_packet(),
self.get_mic_connected_packet(),
self.get_pairing_info_packet(),
self.get_product_color_packet(),
self.get_side_tone_packet(),
self.get_side_tone_volume_packet(),
self.get_voice_prompt_packet(),
self.get_wireless_connected_status_packet(),
];
let mut responded = false;
for packet in packets {
self.get_device_state().hid_device.write(&packet)?;
if let Some(event) = self.wait_for_updates(Duration::from_secs(1)) {
self.get_device_state().update_self_with_event(&event);
responded = true;
}
}
if responded {
Ok(())
} else {
Err(DeviceError::NoResponse())
}
}
}

View File

@@ -3,6 +3,8 @@ use std::time::Duration;
use hidapi::{HidApi, HidDevice, HidError};
use thistermination::TerminationFull;
mod devices;
// Possible vendor IDs [hyperx , HP]
const VENDOR_IDS: [u16; 2] = [0x0951, 0x03F0];
// Possible Cloud II Wireless product IDs