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
1820HyperXMicrophoneV2Controller::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
2530HyperXMicrophoneV2Controller::~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}
0 commit comments