Add some more cloud 3s packets

This commit is contained in:
Lennard Kittner
2026-01-22 13:15:56 +01:00
parent e5e419b8ee
commit 649cfc86f5
2 changed files with 85 additions and 44 deletions

View File

@@ -1,6 +1,6 @@
use crate::{ use crate::{
debug_println, debug_println,
devices::{ChargingStatus, Device, DeviceError, DeviceEvent, DeviceState}, devices::{ChargingStatus, Color, Device, DeviceError, DeviceEvent, DeviceState},
}; };
use std::time::Duration; use std::time::Duration;
@@ -34,27 +34,26 @@ const EQ_CMD: [u8; 5] = [0x02, 0x03, 0x00, 0x00, 0x5f];
const EQ_PACKET_SIZE: usize = 64; const EQ_PACKET_SIZE: usize = 64;
// Battery packet // Battery packet
const BATTERY_PACKET: [u8; 64] = { const BASE_PACKET: [u8; 62] = {
let mut packet = [0u8; 64]; let mut packet = [0u8; 62];
packet[0] = 0x0C; packet[0] = 0x0C;
packet[1] = 0x02; packet[1] = 0x02;
packet[2] = 0x03; packet[2] = 0x03;
packet[3] = 0x01; packet[3] = 0x01;
packet[4] = 0x00; packet[4] = 0x00;
packet[5] = 0x06;
packet[6] = 0x00;
packet packet
}; };
const RESPONSE_ID: u8 = 0x0C;
const NOTIFICATION_ID: u8 = 0x0D;
const CHARGING_RESPONSE: [u8; 64] = { const BATTERY_COMMAND_ID: u8 = 0x06;
let mut packet = [0u8; 64]; const DONGLE_CONNECTED_COMMAND_ID: u8 = 0x02;
packet[0] = 0x0D; const COLOR_COMMAND_ID: u8 = 0x4D;
packet[1] = 0x02; const CHARGE_STATE_COMMAND_ID: u8 = 0x48;
packet[2] = 0x03; const GET_MIC_MUTE_COMMAND_ID: u8 = 0x04;
packet[3] = 0x00; const GET_SIDE_TONE_COMMAND_ID: u8 = 0x16;
packet[4] = 0x0A; const GET_AUTO_POWER_OFF_COMMAND_ID: u8 = 0x4B;
packet const GET_VOICE_PROMPT_COMMAND_ID: u8 = 0x14;
};
// Button report header (incoming from headset) // Button report header (incoming from headset)
const CONSUMER_CONTROL_HEADER: u8 = 0x0f; const CONSUMER_CONTROL_HEADER: u8 = 0x0f;
@@ -94,6 +93,49 @@ fn make_equalizer_band_packet(band_index: u8, db_value: f32) -> Vec<u8> {
packet packet
} }
fn parse_automatic_shutdown_payload(high: u8, low: u8) -> Duration {
let num = (high as u64) * 256 + low as u64;
Duration::from_secs(num)
}
fn parse_response(response: &[u8]) -> Option<Vec<DeviceEvent>> {
if response[6] == 0xFF {
return None;
}
match response[5] {
DONGLE_CONNECTED_COMMAND_ID => Some(vec![DeviceEvent::WirelessConnected(response[6] == 2)]),
GET_MIC_MUTE_COMMAND_ID => Some(vec![DeviceEvent::Muted(response[6] == 1)]),
BATTERY_COMMAND_ID => Some(vec![DeviceEvent::BatterLevel(response[6])]),
GET_VOICE_PROMPT_COMMAND_ID => Some(vec![DeviceEvent::VoicePrompt(response[6] == 1)]),
GET_SIDE_TONE_COMMAND_ID => Some(vec![DeviceEvent::SideToneOn(response[6] == 1)]),
CHARGE_STATE_COMMAND_ID => Some(vec![DeviceEvent::Charging(ChargingStatus::from(
response[6],
))]),
GET_AUTO_POWER_OFF_COMMAND_ID => Some(vec![DeviceEvent::AutomaticShutdownAfter(
parse_automatic_shutdown_payload(response[6], response[7]),
)]),
COLOR_COMMAND_ID => Some(vec![DeviceEvent::ProductColor(Color::from(response[6]))]),
3 | 5 => None,
_ => {
debug_println!("Unknown response {:?}", response);
None
}
}
}
fn parse_notification(response: &[u8]) -> Option<Vec<DeviceEvent>> {
match response[4] {
1 => Some(vec![DeviceEvent::BatterLevel(response[5])]),
3 => Some(vec![DeviceEvent::Muted(response[5] == 1)]),
5 => Some(vec![DeviceEvent::SideToneOn(response[5] == 1)]),
10 => Some(vec![DeviceEvent::Charging(ChargingStatus::from(
response[5],
))]),
12 => Some(vec![DeviceEvent::WirelessConnected(response[5] == 1)]),
_ => None,
}
}
pub struct CloudIIISWireless { pub struct CloudIIISWireless {
state: DeviceState, state: DeviceState,
} }
@@ -110,13 +152,16 @@ impl CloudIIISWireless {
} }
impl Device for CloudIIISWireless { impl Device for CloudIIISWireless {
// Cloud III S: Battery query not discovered yet
fn get_charging_packet(&self) -> Option<Vec<u8>> { fn get_charging_packet(&self) -> Option<Vec<u8>> {
None let mut packet = BASE_PACKET.to_vec();
packet[5] = CHARGE_STATE_COMMAND_ID;
Some(packet)
} }
fn get_battery_packet(&self) -> Option<Vec<u8>> { fn get_battery_packet(&self) -> Option<Vec<u8>> {
Some(BATTERY_PACKET.to_vec()) let mut packet = BASE_PACKET.to_vec();
packet[5] = BATTERY_COMMAND_ID;
Some(packet)
} }
// Cloud III S: Auto shutdown via SET_REPORT (report ID 0x0c) // Cloud III S: Auto shutdown via SET_REPORT (report ID 0x0c)
@@ -126,13 +171,15 @@ impl Device for CloudIIISWireless {
} }
fn get_automatic_shut_down_packet(&self) -> Option<Vec<u8>> { fn get_automatic_shut_down_packet(&self) -> Option<Vec<u8>> {
None let mut packet = BASE_PACKET.to_vec();
packet[5] = GET_AUTO_POWER_OFF_COMMAND_ID;
Some(packet)
} }
// Cloud III S: Cannot query mic state (no response received)
// Physical mic button doesn't emit state packets over HID
fn get_mute_packet(&self) -> Option<Vec<u8>> { fn get_mute_packet(&self) -> Option<Vec<u8>> {
None let mut packet = BASE_PACKET.to_vec();
packet[5] = GET_MIC_MUTE_COMMAND_ID;
Some(packet)
} }
// Cloud III S: Mic control - CONFIRMED WORKING // Cloud III S: Mic control - CONFIRMED WORKING
@@ -157,12 +204,15 @@ impl Device for CloudIIISWireless {
} }
fn get_product_color_packet(&self) -> Option<Vec<u8>> { fn get_product_color_packet(&self) -> Option<Vec<u8>> {
None let mut packet = BASE_PACKET.to_vec();
packet[5] = COLOR_COMMAND_ID;
Some(packet)
} }
// Cloud III S: Sidetone not discovered yet
fn get_side_tone_packet(&self) -> Option<Vec<u8>> { fn get_side_tone_packet(&self) -> Option<Vec<u8>> {
None let mut packet = BASE_PACKET.to_vec();
packet[5] = GET_SIDE_TONE_COMMAND_ID;
Some(packet)
} }
fn set_side_tone_packet(&self, _side_tone_on: bool) -> Option<Vec<u8>> { fn set_side_tone_packet(&self, _side_tone_on: bool) -> Option<Vec<u8>> {
@@ -178,7 +228,9 @@ impl Device for CloudIIISWireless {
} }
fn get_voice_prompt_packet(&self) -> Option<Vec<u8>> { fn get_voice_prompt_packet(&self) -> Option<Vec<u8>> {
None let mut packet = BASE_PACKET.to_vec();
packet[5] = GET_VOICE_PROMPT_COMMAND_ID;
Some(packet)
} }
fn set_voice_prompt_packet(&self, _enable: bool) -> Option<Vec<u8>> { fn set_voice_prompt_packet(&self, _enable: bool) -> Option<Vec<u8>> {
@@ -186,7 +238,9 @@ impl Device for CloudIIISWireless {
} }
fn get_wireless_connected_status_packet(&self) -> Option<Vec<u8>> { fn get_wireless_connected_status_packet(&self) -> Option<Vec<u8>> {
None let mut packet = BASE_PACKET.to_vec();
packet[5] = DONGLE_CONNECTED_COMMAND_ID;
Some(packet)
} }
fn get_sirk_packet(&self) -> Option<Vec<u8>> { fn get_sirk_packet(&self) -> Option<Vec<u8>> {
@@ -231,24 +285,8 @@ impl Device for CloudIIISWireless {
); );
None None
} }
cmd_id if cmd_id == BATTERY_PACKET[0] => { RESPONSE_ID => parse_response(response),
if BATTERY_PACKET[0..=5] == response[0..=5] { NOTIFICATION_ID => parse_notification(response),
Some(vec![DeviceEvent::BatterLevel(response[6])])
} else {
None
}
}
cmd_id if cmd_id == CHARGING_RESPONSE[0] => {
if CHARGING_RESPONSE[0..=4] == response[0..=4] {
if response[5] == 1 {
Some(vec![DeviceEvent::Charging(ChargingStatus::Charging)])
} else {
Some(vec![DeviceEvent::Charging(ChargingStatus::NotCharging)])
}
} else {
None
}
}
_ => { _ => {
debug_println!("Unknown device event: {:?}", response); debug_println!("Unknown device event: {:?}", response);
None None

View File

@@ -355,6 +355,7 @@ pub enum DeviceEvent {
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum Color { pub enum Color {
BlackBlack, BlackBlack,
WhiteWhite,
BlackRed, BlackRed,
UnknownColor(u8), UnknownColor(u8),
} }
@@ -366,6 +367,7 @@ impl Display for Color {
"{}", "{}",
match self { match self {
Color::BlackBlack => "Black".to_string(), Color::BlackBlack => "Black".to_string(),
Color::WhiteWhite => "White".to_string(),
Color::BlackRed => "Red".to_string(), Color::BlackRed => "Red".to_string(),
Color::UnknownColor(n) => format!("Unknown color {}", n), Color::UnknownColor(n) => format!("Unknown color {}", n),
} }
@@ -377,6 +379,7 @@ impl From<u8> for Color {
fn from(color: u8) -> Self { fn from(color: u8) -> Self {
match color { match color {
0 => Color::BlackBlack, 0 => Color::BlackBlack,
1 => Color::WhiteWhite,
2 => Color::BlackRed, 2 => Color::BlackRed,
_ => Color::UnknownColor(color), _ => Color::UnknownColor(color),
} }