Skip to content

Commit 1b77c98

Browse files
Add HyperX Cloud Alpha Wireless Support (#284)
* Add HyperX Cloud Alpha Wireless Support Started doing this the hard way using wireshark then found lehnification's project
1 parent c7b61b0 commit 1b77c98

9 files changed

+173
-6
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ talking. This differs from a simple loopback via PulseAudio as you won't have an
99

1010
## Supported Headsets
1111

12+
- HyperX Cloud Alpha Wireless
13+
- Battery, Inactive time, Sidetone, Voice Prompts (only tested on Linux)
1214
- HyperX Cloud Flight Wireless
1315
- Battery only (only tested on Linux)
1416
- Corsair **Void** (Every void-version*, regardless whether Elite, Pro, HS70 Wireless)

src/device_registry.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "device_registry.h"
22

33
#include "devices/corsair_void.h"
4+
#include "devices/hyperx_calphaw.h"
45
#include "devices/hyperx_cflight.h"
56
#include "devices/logitech_g430.h"
67
#include "devices/logitech_g432.h"
@@ -21,7 +22,7 @@
2122

2223
#include <string.h>
2324

24-
#define NUMDEVICES 18
25+
#define NUMDEVICES 19
2526

2627
// array of pointers to device
2728
static struct device*(devicelist[NUMDEVICES]);
@@ -46,6 +47,7 @@ void init_devices()
4647
cflight_init(&devicelist[15]);
4748
g535_init(&devicelist[16]);
4849
arctis_nova_7_init(&devicelist[17]);
50+
calphaw_init(&devicelist[18]);
4951
}
5052

5153
int get_device(struct device* device_found, uint16_t idVendor, uint16_t idProduct)

src/devices/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
set(SOURCE_FILES ${SOURCE_FILES}
22
${CMAKE_CURRENT_SOURCE_DIR}/hyperx_cflight.c
33
${CMAKE_CURRENT_SOURCE_DIR}/hyperx_cflight.h
4+
${CMAKE_CURRENT_SOURCE_DIR}/hyperx_calphaw.c
5+
${CMAKE_CURRENT_SOURCE_DIR}/hyperx_calphaw.h
46
${CMAKE_CURRENT_SOURCE_DIR}/corsair_void.c
57
${CMAKE_CURRENT_SOURCE_DIR}/corsair_void.h
68
${CMAKE_CURRENT_SOURCE_DIR}/logitech_g930.c

src/devices/hyperx_calphaw.c

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Based on hyperx_cflight.c
2+
// Most requests taken off https://github.com/lehnification/hyperx-cloud-alpha-wireless-tray unless stated otherwise
3+
4+
// This file is part of HeadsetControl.
5+
6+
#include "../device.h"
7+
#include "../utility.h"
8+
9+
#include <hidapi.h>
10+
#include <math.h>
11+
#include <stdlib.h>
12+
#include <string.h>
13+
#include <unistd.h>
14+
15+
// #define DEBUG
16+
17+
#ifdef DEBUG
18+
#include <stdio.h>
19+
#endif
20+
21+
static struct device device_calphaw;
22+
23+
// timeout for hidapi requests in milliseconds
24+
#define TIMEOUT 2000
25+
26+
#define VENDOR_HP 0x03f0 // cloud alpha sold with HP, Inc vendor id
27+
#define ID_CALPHAW 0x098d
28+
29+
static const uint16_t PRODUCT_IDS[] = { ID_CALPHAW };
30+
31+
static int calphaw_request_battery(hid_device* device_handle);
32+
static int calphaw_send_inactive_time(hid_device* device_handle, uint8_t num);
33+
static int calphaw_send_sidetone(hid_device* device_handle, uint8_t num);
34+
static int calphaw_switch_voice_prompts(hid_device* device_handle, uint8_t on);
35+
36+
void calphaw_init(struct device** device)
37+
{
38+
device_calphaw.idVendor = VENDOR_HP;
39+
device_calphaw.idProductsSupported = PRODUCT_IDS;
40+
device_calphaw.numIdProducts = sizeof(PRODUCT_IDS) / sizeof(PRODUCT_IDS[0]);
41+
strncpy(device_calphaw.device_name, "HyperX Cloud Alpha Wireless", sizeof(device_calphaw.device_name));
42+
43+
device_calphaw.capabilities = B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME) | B(CAP_SIDETONE) | B(CAP_VOICE_PROMPTS);
44+
device_calphaw.request_battery = &calphaw_request_battery;
45+
device_calphaw.send_inactive_time = &calphaw_send_inactive_time;
46+
device_calphaw.send_sidetone = &calphaw_send_sidetone;
47+
device_calphaw.switch_voice_prompts = &calphaw_switch_voice_prompts;
48+
49+
*device = &device_calphaw;
50+
}
51+
52+
int calphaw_request_charge(hid_device* device_handle)
53+
{
54+
int r = 0;
55+
// request charge info
56+
uint8_t data_request[31] = { 0x21, 0xbb, 0x0c };
57+
58+
r = hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0]));
59+
if (r < 0)
60+
return r;
61+
62+
uint8_t data_read[31];
63+
r = hid_read_timeout(device_handle, data_read, 20, TIMEOUT);
64+
if (r < 0)
65+
return r;
66+
if (r == 0) // timeout
67+
return HSC_ERROR;
68+
69+
if (r == 0xf || r == 0x14) {
70+
if (data_read[3] == 0x01)
71+
return 1;
72+
73+
return 0;
74+
}
75+
76+
return HSC_ERROR;
77+
}
78+
79+
static int calphaw_request_battery(hid_device* device_handle)
80+
{
81+
if (calphaw_request_charge(device_handle) == 1)
82+
return BATTERY_CHARGING;
83+
84+
int r = 0;
85+
// request battery info
86+
uint8_t data_request[31] = { 0x21, 0xbb, 0x0b }; // Data request reverse engineered by using USBPcap and Wireshark by @InfiniteLoop
87+
88+
r = hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0]));
89+
if (r < 0)
90+
return r;
91+
92+
uint8_t data_read[31];
93+
r = hid_read_timeout(device_handle, data_read, 20, TIMEOUT);
94+
if (r < 0)
95+
return r;
96+
if (r == 0) // timeout
97+
return HSC_ERROR;
98+
99+
if (r == 0xf || r == 0x14) {
100+
101+
#ifdef DEBUG
102+
printf("calphaw_request_battery data_read 3: 0x%02x 4: 0x%02x 5: 0x%02x\n", data_read[3], data_read[4], data_read[5]);
103+
#endif
104+
105+
uint8_t batteryPercentage = data_read[3];
106+
107+
#ifdef DEBUG
108+
uint32_t batteryVoltage = (data_read[4] << 8) | data_read[5]; // best assumtion this is voltage in mV
109+
printf("batteryVoltage: %d mV\n", batteryVoltage);
110+
#endif
111+
return (int)(batteryPercentage);
112+
}
113+
114+
return HSC_ERROR;
115+
}
116+
117+
static int calphaw_send_inactive_time(hid_device* device_handle, uint8_t num)
118+
{
119+
// taken from steelseries_arctis_7.c
120+
121+
uint8_t data_request[31] = { 0x21, 0xbb, 0x12, num };
122+
123+
int ret = hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0]));
124+
125+
return ret;
126+
}
127+
128+
int calphaw_toggle_sidetone(hid_device* device_handle, uint8_t num)
129+
{
130+
uint8_t data_request[31] = { 0x21, 0xbb, 0x10, num };
131+
132+
return hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0]));
133+
;
134+
}
135+
136+
static int calphaw_send_sidetone(hid_device* device_handle, uint8_t num)
137+
{
138+
if (num == 0) {
139+
return calphaw_toggle_sidetone(device_handle, 0);
140+
} else {
141+
calphaw_toggle_sidetone(device_handle, 1);
142+
uint8_t data_request[31] = { 0x21, 0xbb, 0x11, num };
143+
144+
int ret = hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0]));
145+
146+
// looks to be a binary setting but I'm not sure
147+
148+
return ret;
149+
}
150+
151+
return HSC_ERROR;
152+
}
153+
154+
static int calphaw_switch_voice_prompts(hid_device* device_handle, uint8_t on)
155+
{
156+
uint8_t data_request[31] = { 0x21, 0xbb, 0x13, on };
157+
158+
return hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0]));
159+
;
160+
}

src/devices/hyperx_calphaw.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
void calphaw_init(struct device** device);

src/devices/hyperx_cflight.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#include <string.h>
1414
#include <unistd.h>
1515

16-
//#define DEBUG
16+
// #define DEBUG
1717

1818
#ifdef DEBUG
1919
#include <stdio.h>

src/devices/logitech_g430.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ static int g430_send_sidetone(hid_device* device_handle, uint8_t num)
4141
0x081E, 0x1E1E, 0x341E, 0x4A1E, 0x601E, 0x761E, 0x8C1E, 0xA21E, 0xB81E, 0xCE1E, 0xE41E, 0xFA1E, 0x101F, 0x261F,
4242
0x3C1F, 0x521F, 0x681F };*/
4343

44-
//unsigned char data[2] = { volumes[num] >> 8, volumes[num] & 0xFF };
44+
// unsigned char data[2] = { volumes[num] >> 8, volumes[num] & 0xFF };
4545

46-
//int size = libusb_control_transfer(device_handle, LIBUSB_DT_HID, LIBUSB_REQUEST_CLEAR_FEATURE, 0x0201, 0x0600, data, 0x2, 1000);
46+
// int size = libusb_control_transfer(device_handle, LIBUSB_DT_HID, LIBUSB_REQUEST_CLEAR_FEATURE, 0x0201, 0x0600, data, 0x2, 1000);
4747

4848
// unused parameter
4949
(void)(device_handle);

src/devices/logitech_g633_g933_935.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ static int g933_935_request_battery(hid_device* device_handle)
7575
if (r == 0)
7676
return HSC_READ_TIMEOUT;
7777

78-
//6th byte is state; 0x1 for idle, 0x3 for charging
78+
// 6th byte is state; 0x1 for idle, 0x3 for charging
7979
uint8_t state = data_read[6];
8080
if (state == 0x03)
8181
return BATTERY_CHARGING;

src/devices/roccat_elo_7_1_air.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,5 @@ static int elo71Air_send_inactive_time(hid_device* hid_device, uint8_t num)
113113
uint8_t response[64];
114114

115115
return send_receive(hid_device, cmd, sizeof(cmd), response);
116-
//meaning of response of headset is not clear yet, fetch & ignore it for now
116+
// meaning of response of headset is not clear yet, fetch & ignore it for now
117117
}

0 commit comments

Comments
 (0)