Skip to content

Commit 9eb0169

Browse files
Eclipse-SolCalcProgrammer1
authored andcommitted
Adds "Save to Device" feature to QuadCast 2S and logging
1 parent d99be3e commit 9eb0169

5 files changed

Lines changed: 224 additions & 24 deletions

File tree

Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.cpp

Lines changed: 196 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
/*---------------------------------------------------------*\
22
| HyperXMicrophoneV2Controller.cpp |
33
| |
4-
| Driver for HyperX QuadCast 2S microphone |
4+
| Driver for HyperX QuadCast 2 S Microphone |
55
| |
66
| Morgan Guimard (morg) |
7+
| Logan Phillips (Eclipse) 23 Oct 2025 |
78
| |
89
| This file is part of the OpenRGB project |
910
| SPDX-License-Identifier: GPL-2.0-or-later |
1011
\*---------------------------------------------------------*/
1112

1213
#include <cstring>
14+
#include <sstream>
15+
#include <iomanip>
1316
#include "HyperXMicrophoneV2Controller.h"
1417
#include "StringUtils.h"
15-
16-
using namespace std::chrono_literals;
18+
#include "LogManager.h"
1719

1820
HyperXMicrophoneV2Controller::HyperXMicrophoneV2Controller(hid_device* dev_handle, std::string path, std::string dev_name)
1921
{
20-
dev = dev_handle;
21-
location = path;
22-
name = dev_name;
22+
dev = dev_handle;
23+
location = path;
24+
name = dev_name;
25+
errors = 0;
26+
last_error_time = std::chrono::steady_clock::now();
27+
pause_until = std::chrono::steady_clock::now();
2328
}
2429

2530
HyperXMicrophoneV2Controller::~HyperXMicrophoneV2Controller()
@@ -53,25 +58,113 @@ std::string HyperXMicrophoneV2Controller::GetSerialString()
5358
return(StringUtils::wstring_to_string(serial_string));
5459
}
5560

56-
void HyperXMicrophoneV2Controller::SendDirect(std::vector<RGBColor> colors)
61+
bool HyperXMicrophoneV2Controller::ShouldPauseUpdates()
5762
{
58-
lock.lock();
63+
return std::chrono::steady_clock::now() < pause_until;
64+
}
5965

60-
uint8_t buf[HYPERX_QUADCAST_2S_PACKET_SIZE];
66+
void HyperXMicrophoneV2Controller::FlushInputBuffer()
67+
{
68+
uint8_t discard[HYPERX_QUADCAST_2S_PACKET_SIZE];
69+
int flushed_count = 0;
70+
71+
/*---------------------------------------------------------*\
72+
| Read and discard all pending responses in the buffer |
73+
\*---------------------------------------------------------*/
74+
while(hid_read_timeout(dev, discard, HYPERX_QUADCAST_2S_PACKET_SIZE, 10) > 0)
75+
{
76+
flushed_count++;
77+
}
78+
79+
if(flushed_count > 0)
80+
{
81+
LOG_DEBUG("[%s] Flushed %d stale response(s) from input buffer", name.c_str(), flushed_count);
82+
}
83+
}
84+
85+
void HyperXMicrophoneV2Controller::TrackCommunicationError()
86+
{
87+
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
88+
long long time_since_last_error = std::chrono::duration_cast<std::chrono::seconds>(now - last_error_time).count();
89+
90+
if(time_since_last_error < 10)
91+
{
92+
errors++;
93+
if(errors >= 5)
94+
{
95+
LOG_WARNING("[%s] Multiple communication errors detected (%d). Flushing input buffer to clear stale responses.", name.c_str(), errors);
96+
FlushInputBuffer();
97+
}
98+
if(errors >= 10)
99+
{
100+
LOG_ERROR("[%s] Multiple consecutive communication errors detected. Another program (such as HyperX NGENUITY) may be controlling this device. Pausing updates for 5 seconds.", name.c_str());
101+
pause_until = std::chrono::steady_clock::now() + std::chrono::seconds(5);
102+
errors = 0;
103+
}
104+
}
105+
else
106+
{
107+
errors = 1;
108+
}
109+
110+
last_error_time = now;
111+
}
112+
113+
bool HyperXMicrophoneV2Controller::WaitForResponse(const uint8_t* sent_packet, int timeout_ms)
114+
{
115+
uint8_t response[HYPERX_QUADCAST_2S_PACKET_SIZE];
116+
memset(response, 0, HYPERX_QUADCAST_2S_PACKET_SIZE);
117+
118+
int bytes_read = hid_read_timeout(dev, response, HYPERX_QUADCAST_2S_PACKET_SIZE, timeout_ms);
119+
120+
if(bytes_read <= 0)
121+
{
122+
LOG_WARNING("[%s] No response received from device (timeout: %d ms)", name.c_str(), timeout_ms);
123+
TrackCommunicationError();
124+
return false;
125+
}
126+
127+
/*---------------------------------------------------------*\
128+
| Verify the response echoes back the command bytes |
129+
| Response bytes 14-15 should match sent bytes 0-1 |
130+
\*---------------------------------------------------------*/
131+
if(response[14] == sent_packet[0] && response[15] == sent_packet[1])
132+
{
133+
return true;
134+
}
61135

136+
/*---------------------------------------------------------*\
137+
| Log validation failure with full response payload |
138+
\*---------------------------------------------------------*/
139+
std::stringstream response_hex;
140+
for(int i = 0; i < bytes_read; i++)
141+
{
142+
response_hex << std::hex << std::setw(2) << std::setfill('0') << (int)response[i];
143+
}
144+
145+
LOG_WARNING("[%s] Invalid response from device. Expected echo of bytes [0x%02X 0x%02X] at positions 14-15, but got [0x%02X 0x%02X]. Full response: %s",
146+
name.c_str(), sent_packet[0], sent_packet[1], response[14], response[15], response_hex.str().c_str());
147+
148+
TrackCommunicationError();
149+
return false;
150+
}
151+
152+
void HyperXMicrophoneV2Controller::SendColorPackets(std::vector<RGBColor> colors, uint8_t command_byte)
153+
{
154+
uint8_t buf[HYPERX_QUADCAST_2S_PACKET_SIZE];
62155
unsigned int total_leds_sent = 0;
63156

64-
for(unsigned int i = 0; i < 7; i++)
157+
for(unsigned int packet = 0; packet < 6; packet++)
65158
{
66159
memset(buf, 0, HYPERX_QUADCAST_2S_PACKET_SIZE);
67160

68161
buf[0] = HYPERX_QUADCAST_2S_REPORT_ID;
69-
buf[1] = i < 6 ? 0x02 : 0x01;
70-
buf[2] = i;
162+
buf[1] = command_byte;
163+
buf[2] = packet;
71164

72165
unsigned int c = 0;
73166

74-
while (c < HYPERX_QUADCAST_2S_LEDS_PER_PACKET && total_leds_sent < HYPERX_QUADCAST_2S_TOTAL_LEDS)
167+
while(c < HYPERX_QUADCAST_2S_LEDS_PER_PACKET && total_leds_sent < HYPERX_QUADCAST_2S_TOTAL_LEDS)
75168
{
76169
buf[4 + (3 * c)] = RGBGetRValue(colors[total_leds_sent]);
77170
buf[5 + (3 * c)] = RGBGetGValue(colors[total_leds_sent]);
@@ -82,7 +175,97 @@ void HyperXMicrophoneV2Controller::SendDirect(std::vector<RGBColor> colors)
82175
}
83176

84177
hid_write(dev, buf, HYPERX_QUADCAST_2S_PACKET_SIZE);
178+
WaitForResponse(buf);
179+
}
180+
}
181+
182+
void HyperXMicrophoneV2Controller::SendDirect(std::vector<RGBColor> colors)
183+
{
184+
lock.lock();
185+
186+
/*---------------------------------------------------------*\
187+
| Skip sending if we're in pause mode |
188+
\*---------------------------------------------------------*/
189+
if(ShouldPauseUpdates())
190+
{
191+
lock.unlock();
192+
return;
85193
}
86194

195+
uint8_t buf[HYPERX_QUADCAST_2S_PACKET_SIZE];
196+
197+
/*---------------------------------------------------------*\
198+
| Send header packet for direct mode |
199+
\*---------------------------------------------------------*/
200+
memset(buf, 0, HYPERX_QUADCAST_2S_PACKET_SIZE);
201+
buf[0] = HYPERX_QUADCAST_2S_REPORT_ID;
202+
buf[1] = 0x01;
203+
buf[2] = 0x06;
204+
hid_write(dev, buf, HYPERX_QUADCAST_2S_PACKET_SIZE);
205+
WaitForResponse(buf);
206+
207+
/*---------------------------------------------------------*\
208+
| Send color data packets |
209+
\*---------------------------------------------------------*/
210+
SendColorPackets(colors, 0x02);
211+
212+
lock.unlock();
213+
}
214+
215+
void HyperXMicrophoneV2Controller::SaveColors(std::vector<RGBColor> colors)
216+
{
217+
lock.lock();
218+
219+
/*---------------------------------------------------------*\
220+
| Skip sending if we're in pause mode |
221+
\*---------------------------------------------------------*/
222+
if(ShouldPauseUpdates())
223+
{
224+
lock.unlock();
225+
return;
226+
}
227+
228+
uint8_t buf[HYPERX_QUADCAST_2S_PACKET_SIZE];
229+
230+
/*---------------------------------------------------------*\
231+
| Initiate save to device |
232+
\*---------------------------------------------------------*/
233+
memset(buf, 0, HYPERX_QUADCAST_2S_PACKET_SIZE);
234+
buf[0] = HYPERX_QUADCAST_2S_REPORT_ID;
235+
buf[1] = 0x03;
236+
buf[2] = 0x01;
237+
buf[3] = 0x06;
238+
hid_write(dev, buf, HYPERX_QUADCAST_2S_PACKET_SIZE);
239+
WaitForResponse(buf);
240+
241+
/*---------------------------------------------------------*\
242+
| Send 6 color data packets |
243+
\*---------------------------------------------------------*/
244+
SendColorPackets(colors, 0x04);
245+
246+
/*---------------------------------------------------------*\
247+
| Send "Framerate" packet |
248+
| If someone ever wanted to try and replicate the effects, |
249+
| apparently this is the packet to try and change. |
250+
| I believe currently this is setting a "static" frame |
251+
\*---------------------------------------------------------*/
252+
memset(buf, 0, HYPERX_QUADCAST_2S_PACKET_SIZE);
253+
buf[0] = 0x42;
254+
buf[1] = 0x02;
255+
buf[5] = 0xE8;
256+
buf[6] = 0x03;
257+
hid_write(dev, buf, HYPERX_QUADCAST_2S_PACKET_SIZE);
258+
WaitForResponse(buf);
259+
260+
/*---------------------------------------------------------*\
261+
| Send final packet |
262+
\*---------------------------------------------------------*/
263+
memset(buf, 0, HYPERX_QUADCAST_2S_PACKET_SIZE);
264+
buf[0] = 0x40;
265+
buf[1] = 0x01;
266+
buf[4] = 0xFF;
267+
hid_write(dev, buf, HYPERX_QUADCAST_2S_PACKET_SIZE);
268+
WaitForResponse(buf);
269+
87270
lock.unlock();
88271
}

Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/*---------------------------------------------------------*\
2-
| HyperXMicrophoneV2Controller.cpp |
2+
| HyperXMicrophoneV2Controller.h |
33
| |
4-
| Driver for HyperX QuadCast 2S microphone |
4+
| Driver for HyperX QuadCast 2 S Microphone |
55
| |
66
| Morgan Guimard (morg) |
7+
| Logan Phillips (Eclipse) 23 Oct 2025 |
78
| |
89
| This file is part of the OpenRGB project |
910
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -33,10 +34,22 @@ class HyperXMicrophoneV2Controller
3334
std::string GetSerialString();
3435

3536
void SendDirect(std::vector<RGBColor> color_data);
37+
void SaveColors(std::vector<RGBColor> color_data);
38+
39+
bool ShouldPauseUpdates();
3640

3741
private:
3842
hid_device* dev;
3943
std::string location;
4044
std::string name;
4145
std::mutex lock;
46+
47+
bool WaitForResponse(const uint8_t* sent_packet, int timeout_ms = 2000);
48+
void SendColorPackets(std::vector<RGBColor> colors, uint8_t command_byte);
49+
void TrackCommunicationError();
50+
void FlushInputBuffer();
51+
52+
unsigned int errors;
53+
std::chrono::steady_clock::time_point last_error_time;
54+
std::chrono::steady_clock::time_point pause_until;
4255
};

Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2ControllerDetect.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/*---------------------------------------------------------*\
22
| HyperXMicrophoneV2ControllerDetect.cpp |
33
| |
4-
| Detector for HyperX QuadCast 2s microphone |
4+
| Detector for HyperX QuadCast 2 S Microphone |
55
| |
66
| Morgan Guimard (morg) |
7+
| Logan Phillips (Eclipse) 23 Oct 2025 |
78
| |
89
| This file is part of the OpenRGB project |
910
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -32,4 +33,4 @@ void DetectHyperXMicrophoneV2Controllers(hid_device_info* info, const std::strin
3233
}
3334
}
3435

35-
REGISTER_HID_DETECTOR_IPU("HyperX QuadCast 2S", DetectHyperXMicrophoneV2Controllers, HYPERX_HP_VID, HYPERX_QUADCAST_2S_PID, 1, 0xFF13, 0xFF00);
36+
REGISTER_HID_DETECTOR_IPU("HyperX QuadCast 2 S", DetectHyperXMicrophoneV2Controllers, HYPERX_HP_VID, HYPERX_QUADCAST_2S_PID, 1, 0xFF13, 0xFF00);

Controllers/HyperXMicrophoneV2Controller/RGBController_HyperXMicrophoneV2.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/*---------------------------------------------------------*\
22
| RGBController_HyperXMicrophoneV2.cpp |
33
| |
4-
| RGBController for HyperX QuadCast 2S microphone |
4+
| RGBController for HyperX QuadCast 2 S Microphone |
55
| |
66
| Morgan Guimard (morg) |
7+
| Logan Phillips (Eclipse) 23 Oct 2025 |
78
| |
89
| This file is part of the OpenRGB project |
910
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -12,7 +13,7 @@
1213
/**------------------------------------------------------------------*\
1314
@name HyperX Quadcast 2S
1415
@type USB
15-
@save :x:
16+
@save :white_check_mark:
1617
@direct :white_check_mark:
1718
@effects :x:
1819
@detectors DetectHyperXMicrophoneV2Controllers
@@ -37,7 +38,7 @@ RGBController_HyperXMicrophoneV2::RGBController_HyperXMicrophoneV2(HyperXMicroph
3738

3839
mode Direct;
3940
Direct.name = "Direct";
40-
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
41+
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE;
4142
Direct.color_mode = MODE_COLORS_PER_LED;
4243
Direct.colors_min = HYPERX_QUADCAST_2S_TOTAL_LEDS;
4344
Direct.colors_max = HYPERX_QUADCAST_2S_TOTAL_LEDS;
@@ -129,17 +130,18 @@ void RGBController_HyperXMicrophoneV2::DeviceUpdateMode()
129130

130131
void RGBController_HyperXMicrophoneV2::DeviceSaveMode()
131132
{
132-
/* Unsuported */
133+
LOG_DEBUG("[%s] Saving current direct colors to device", name.c_str());
134+
controller->SaveColors(colors);
133135
}
134136

135137
void RGBController_HyperXMicrophoneV2::KeepaliveThread()
136138
{
137139
while(keepalive_thread_run.load())
138140
{
139-
if((std::chrono::steady_clock::now() - last_update_time) > std::chrono::milliseconds(50))
141+
if(!controller->ShouldPauseUpdates() && (std::chrono::steady_clock::now() - last_update_time) > std::chrono::milliseconds(1000))
140142
{
141143
UpdateLEDs();
142144
}
143-
std::this_thread::sleep_for(15ms);
145+
std::this_thread::sleep_for(250ms);
144146
}
145147
}

Controllers/HyperXMicrophoneV2Controller/RGBController_HyperXMicrophoneV2.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/*---------------------------------------------------------*\
22
| RGBController_HyperXMicrophoneV2.h |
33
| |
4-
| RGBController for HyperX QuadCast 2S microphone |
4+
| RGBController for HyperX QuadCast 2 S Microphone |
55
| |
66
| Morgan Guimard (morg) |
7+
| Logan Phillips (Eclipse) 23 Oct 2025 |
78
| |
89
| This file is part of the OpenRGB project |
910
| SPDX-License-Identifier: GPL-2.0-or-later |

0 commit comments

Comments
 (0)