Implement cloud alpha support

This commit is contained in:
Lennard Kittner
2026-02-18 12:57:44 +01:00
parent 2106484d38
commit 3065cfc198
2 changed files with 256 additions and 5 deletions

View File

@@ -0,0 +1,239 @@
use crate::{
debug_println,
devices::{ChargingStatus, Color, Device, DeviceError, DeviceEvent, DeviceState},
};
use std::time::Duration;
const HP: u16 = 0x03F0;
pub const VENDOR_IDS: [u16; 1] = [HP];
// Possible Cloud Alpha Wireless product IDs
pub const PRODUCT_IDS: [u16; 3] = [0x1743, 0x1765, 0x098D];
const BASE_PACKET: [u8; 64] = {
let mut packet = [0; 64];
packet[0] = 33;
packet[1] = 187;
packet
};
const RESPONSE_BUFFER_SIZE: usize = 256;
const GET_CHARGING_CMD_ID: u8 = 12;
const GET_CHARGING_RESPONSE_CODE: u8 = 38;
const GET_MIC_CONNECTED_CMD_ID: u8 = 8;
const GET_BATTERY_CMD_ID: u8 = 11;
const GET_BATTERY_RESPONSE_CODE: u8 = 37;
const GET_AUTO_SHUTDOWN_CMD_ID: u8 = 7;
const SET_AUTO_SHUTDOWN_CMD_ID: u8 = 18;
const GET_MUTE_CMD_ID: u8 = 10;
const GET_MUTE_RESPONSE_CODE: u8 = 35;
const SET_MUTE_CMD_ID: u8 = 21;
const GET_PAIRING_CMD_ID: u8 = 4;
const GET_PRODUCT_COLOR_CMD_ID: u8 = 14;
const GET_SIDE_TONE_ON_CMD_ID: u8 = 5;
const GET_SIDE_TONE_ON_RESPONSE_CODE: u8 = 34;
const SET_SIDE_TONE_ON_CMD_ID: u8 = 16;
const GET_SIDE_TONE_VOLUME_CMD_ID: u8 = 6;
const SET_SIDE_TONE_VOLUME_CMD_ID: u8 = 17;
const GET_VOICE_PROMPT_CMD_ID: u8 = 9;
const SET_VOICE_PROMPT_CMD_ID: u8 = 19;
const GET_WIRELESS_STATUS_CMD_ID: u8 = 3;
const GET_WIRELESS_STATUS_RESPONSE_CODE: u8 = 36;
pub struct CloudAlphaWireless {
state: DeviceState,
}
impl CloudAlphaWireless {
pub fn new_from_state(state: DeviceState) -> Self {
CloudAlphaWireless { state }
}
pub fn new() -> Result<Self, DeviceError> {
let state = DeviceState::new(&PRODUCT_IDS, &VENDOR_IDS)?;
Ok(CloudAlphaWireless { state })
}
}
impl Device for CloudAlphaWireless {
fn get_response_buffer(&self) -> Vec<u8> {
let mut tmp = [0u8; RESPONSE_BUFFER_SIZE].to_vec();
tmp[0] = 33;
tmp
}
fn get_charging_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_CHARGING_CMD_ID;
Some(tmp)
}
fn get_battery_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_BATTERY_CMD_ID;
Some(tmp)
}
fn set_automatic_shut_down_packet(&self, shutdown_after: Duration) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = SET_AUTO_SHUTDOWN_CMD_ID;
tmp[3] = (shutdown_after.as_secs() / 60) as u8;
Some(tmp)
}
fn get_automatic_shut_down_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_AUTO_SHUTDOWN_CMD_ID;
Some(tmp)
}
fn get_mute_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_MUTE_CMD_ID;
Some(tmp)
}
fn set_mute_packet(&self, mute: bool) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = SET_MUTE_CMD_ID;
tmp[3] = mute as u8;
Some(tmp)
}
fn get_surround_sound_packet(&self) -> Option<Vec<u8>> {
None
}
fn set_surround_sound_packet(&self, _surround_sound: bool) -> Option<Vec<u8>> {
None
}
fn get_mic_connected_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_MIC_CONNECTED_CMD_ID;
Some(tmp)
}
fn get_pairing_info_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_PAIRING_CMD_ID; // Or 13
Some(tmp)
}
fn get_product_color_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_PRODUCT_COLOR_CMD_ID;
Some(tmp)
}
fn get_side_tone_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_SIDE_TONE_ON_CMD_ID;
Some(tmp)
}
fn set_side_tone_packet(&self, side_tone_on: bool) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = SET_SIDE_TONE_ON_CMD_ID;
tmp[3] = side_tone_on as u8;
Some(tmp)
}
fn get_side_tone_volume_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_SIDE_TONE_VOLUME_CMD_ID;
Some(tmp)
}
fn set_side_tone_volume_packet(&self, volume: u8) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = SET_SIDE_TONE_VOLUME_CMD_ID;
tmp[3] = volume; // correct?
Some(tmp)
}
fn get_voice_prompt_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_VOICE_PROMPT_CMD_ID;
Some(tmp)
}
fn set_voice_prompt_packet(&self, enable: bool) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = SET_VOICE_PROMPT_CMD_ID;
tmp[3] = enable as u8;
Some(tmp)
}
fn get_wireless_connected_status_packet(&self) -> Option<Vec<u8>> {
let mut tmp = BASE_PACKET.to_vec();
tmp[2] = GET_WIRELESS_STATUS_CMD_ID;
Some(tmp)
}
fn get_sirk_packet(&self) -> Option<Vec<u8>> {
None
}
fn reset_sirk_packet(&self) -> Option<Vec<u8>> {
None
}
fn get_silent_mode_packet(&self) -> Option<Vec<u8>> {
None
}
fn set_silent_mode_packet(&self, _silence: bool) -> Option<Vec<u8>> {
None
}
fn get_event_from_device_response(&self, response: &[u8]) -> Option<Vec<DeviceEvent>> {
debug_println!("Read packet: {:?}", response);
if response[0] != BASE_PACKET[0] || response[1] != BASE_PACKET[1] {
return None;
}
match response[2] {
GET_CHARGING_RESPONSE_CODE | GET_CHARGING_CMD_ID => Some(vec![DeviceEvent::Charging(
ChargingStatus::from(response[3]),
)]),
GET_MIC_CONNECTED_CMD_ID => Some(vec![DeviceEvent::MicConnected(response[3] == 1)]),
GET_BATTERY_RESPONSE_CODE | GET_BATTERY_CMD_ID => {
Some(vec![DeviceEvent::BatterLevel(response[3])])
}
GET_AUTO_SHUTDOWN_CMD_ID => Some(vec![DeviceEvent::AutomaticShutdownAfter(
Duration::from_secs(response[3] as u64 * 60),
)]),
GET_MUTE_RESPONSE_CODE | GET_MUTE_CMD_ID => {
Some(vec![DeviceEvent::Muted(response[3] == 1)])
}
GET_PAIRING_CMD_ID => Some(vec![DeviceEvent::PairingInfo(response[3])]),
GET_SIDE_TONE_ON_RESPONSE_CODE | GET_SIDE_TONE_ON_CMD_ID => {
Some(vec![DeviceEvent::SideToneOn(response[3] == 1)])
}
GET_SIDE_TONE_VOLUME_CMD_ID => Some(vec![DeviceEvent::SideToneVolume(response[3])]), //Correct?
GET_WIRELESS_STATUS_RESPONSE_CODE | GET_WIRELESS_STATUS_CMD_ID => {
Some(vec![DeviceEvent::WirelessConnected(response[3] == 2)])
}
GET_VOICE_PROMPT_CMD_ID => Some(vec![DeviceEvent::VoicePrompt(response[3] == 1)]),
GET_PRODUCT_COLOR_CMD_ID => {
Some(vec![DeviceEvent::ProductColor(Color::from(response[3]))])
}
_ => {
debug_println!("Unknown device event: {:?}", response);
None
}
}
}
fn allow_passive_refresh(&mut self) -> bool {
true
}
fn get_device_state(&self) -> &DeviceState {
&self.state
}
fn get_device_state_mut(&mut self) -> &mut DeviceState {
&mut self.state
}
}

View File

@@ -1,3 +1,4 @@
pub mod cloud_alpha_wireless;
pub mod cloud_ii_wireless; pub mod cloud_ii_wireless;
pub mod cloud_ii_wireless_dts; pub mod cloud_ii_wireless_dts;
pub mod cloud_iii_s_wireless; pub mod cloud_iii_s_wireless;
@@ -6,8 +7,9 @@ pub mod cloud_iii_wireless;
use crate::{ use crate::{
debug_println, debug_println,
devices::{ devices::{
cloud_ii_wireless::CloudIIWireless, cloud_ii_wireless_dts::CloudIIWirelessDTS, cloud_alpha_wireless::CloudAlphaWireless, cloud_ii_wireless::CloudIIWireless,
cloud_iii_s_wireless::CloudIIISWireless, cloud_iii_wireless::CloudIIIWireless, cloud_ii_wireless_dts::CloudIIWirelessDTS, cloud_iii_s_wireless::CloudIIISWireless,
cloud_iii_wireless::CloudIIIWireless,
}, },
}; };
use hidapi::{HidApi, HidDevice, HidError}; use hidapi::{HidApi, HidDevice, HidError};
@@ -17,8 +19,9 @@ use thistermination::TerminationFull;
// Possible vendor IDs [HyperX, HP] // Possible vendor IDs [HyperX, HP]
const VENDOR_IDS: [u16; 2] = [0x0951, 0x03F0]; const VENDOR_IDS: [u16; 2] = [0x0951, 0x03F0];
// All supported product IDs // All supported product IDs
const PRODUCT_IDS: [u16; 10] = [ const PRODUCT_IDS: [u16; 13] = [
0x1718, 0x018B, 0x0D93, 0x0696, 0x0b92, 0x05B7, 0x16EA, 0x16EB, 0x0c9d, 0x06BE, 0x1718, 0x018B, 0x0D93, 0x0696, 0x0b92, 0x05B7, 0x16EA, 0x16EB, 0x0c9d, 0x06BE, 0x1743, 0x1765,
0x098D,
]; ];
const RESPONSE_BUFFER_SIZE: usize = 256; const RESPONSE_BUFFER_SIZE: usize = 256;
@@ -57,6 +60,12 @@ pub fn connect_compatible_device() -> Result<Box<dyn Device>, DeviceError> {
{ {
Box::new(CloudIIIWireless::new_from_state(state)) Box::new(CloudIIIWireless::new_from_state(state))
} }
(v, p)
if cloud_alpha_wireless::VENDOR_IDS.contains(&v)
&& cloud_alpha_wireless::PRODUCT_IDS.contains(&p) =>
{
Box::new(CloudAlphaWireless::new_from_state(state))
}
(_, _) => return Err(DeviceError::NoDeviceFound()), (_, _) => return Err(DeviceError::NoDeviceFound()),
}; };
@@ -421,6 +430,9 @@ impl From<u8> for ChargingStatus {
} }
pub trait Device { pub trait Device {
fn get_response_buffer(&self) -> Vec<u8> {
[0u8; RESPONSE_BUFFER_SIZE].to_vec()
}
fn get_charging_packet(&self) -> Option<Vec<u8>>; fn get_charging_packet(&self) -> Option<Vec<u8>>;
fn get_battery_packet(&self) -> Option<Vec<u8>>; fn get_battery_packet(&self) -> Option<Vec<u8>>;
fn set_automatic_shut_down_packet(&self, shutdown_after: Duration) -> Option<Vec<u8>>; fn set_automatic_shut_down_packet(&self, shutdown_after: Duration) -> Option<Vec<u8>>;
@@ -510,7 +522,7 @@ pub trait Device {
Ok(()) Ok(())
} }
fn wait_for_updates(&mut self, duration: Duration) -> Option<Vec<DeviceEvent>> { fn wait_for_updates(&mut self, duration: Duration) -> Option<Vec<DeviceEvent>> {
let mut buf = [0u8; RESPONSE_BUFFER_SIZE]; let mut buf = self.get_response_buffer();
let res = self let res = self
.get_device_state() .get_device_state()
.hid_device .hid_device