@ -1,25 +1,30 @@
/*---------------------------------------------------------*\
/*---------------------------------------------------------*\
| HyperXMicrophoneV2Controller . cpp |
| HyperXMicrophoneV2Controller . cpp |
| |
| |
| Driver for HyperX QuadCast 2 S microphone |
| Driver for HyperX QuadCast 2 S Microphone |
| |
| |
| Morgan Guimard ( morg ) |
| Morgan Guimard ( morg ) |
| Logan Phillips ( Eclipse ) 23 Oct 2025 |
| |
| |
| This file is part of the OpenRGB project |
| This file is part of the OpenRGB project |
| SPDX - License - Identifier : GPL - 2.0 - or - later |
| SPDX - License - Identifier : GPL - 2.0 - or - later |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
# include <cstring>
# include <cstring>
# include <sstream>
# include <iomanip>
# include "HyperXMicrophoneV2Controller.h"
# include "HyperXMicrophoneV2Controller.h"
# include "StringUtils.h"
# include "StringUtils.h"
# include "LogManager.h"
using namespace std : : chrono_literals ;
HyperXMicrophoneV2Controller : : HyperXMicrophoneV2Controller ( hid_device * dev_handle , std : : string path , std : : string dev_name )
HyperXMicrophoneV2Controller : : HyperXMicrophoneV2Controller ( hid_device * dev_handle , std : : string path , std : : string dev_name )
{
{
dev = dev_handle ;
dev = dev_handle ;
location = path ;
location = path ;
name = dev_name ;
name = dev_name ;
errors = 0 ;
last_error_time = std : : chrono : : steady_clock : : now ( ) ;
pause_until = std : : chrono : : steady_clock : : now ( ) ;
}
}
HyperXMicrophoneV2Controller : : ~ HyperXMicrophoneV2Controller ( )
HyperXMicrophoneV2Controller : : ~ HyperXMicrophoneV2Controller ( )
@ -53,25 +58,113 @@ std::string HyperXMicrophoneV2Controller::GetSerialString()
return ( StringUtils : : wstring_to_string ( serial_string ) ) ;
return ( StringUtils : : wstring_to_string ( serial_string ) ) ;
}
}
void HyperXMicrophoneV2Controller : : SendDirect ( std : : vector < RGBColor > colors )
bool HyperXMicrophoneV2Controller : : ShouldPauseUpdates ( )
{
{
lock . lock ( ) ;
return std : : chrono : : steady_clock : : now ( ) < pause_until ;
}
uint8_t buf [ HYPERX_QUADCAST_2S_PACKET_SIZE ] ;
void HyperXMicrophoneV2Controller : : FlushInputBuffer ( )
{
uint8_t discard [ HYPERX_QUADCAST_2S_PACKET_SIZE ] ;
int flushed_count = 0 ;
/*---------------------------------------------------------*\
| Read and discard all pending responses in the buffer |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
while ( hid_read_timeout ( dev , discard , HYPERX_QUADCAST_2S_PACKET_SIZE , 10 ) > 0 )
{
flushed_count + + ;
}
if ( flushed_count > 0 )
{
LOG_DEBUG ( " [%s] Flushed %d stale response(s) from input buffer " , name . c_str ( ) , flushed_count ) ;
}
}
void HyperXMicrophoneV2Controller : : TrackCommunicationError ( )
{
std : : chrono : : steady_clock : : time_point now = std : : chrono : : steady_clock : : now ( ) ;
long long time_since_last_error = std : : chrono : : duration_cast < std : : chrono : : seconds > ( now - last_error_time ) . count ( ) ;
if ( time_since_last_error < 10 )
{
errors + + ;
if ( errors > = 5 )
{
LOG_WARNING ( " [%s] Multiple communication errors detected (%d). Flushing input buffer to clear stale responses. " , name . c_str ( ) , errors ) ;
FlushInputBuffer ( ) ;
}
if ( errors > = 10 )
{
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 ( ) ) ;
pause_until = std : : chrono : : steady_clock : : now ( ) + std : : chrono : : seconds ( 5 ) ;
errors = 0 ;
}
}
else
{
errors = 1 ;
}
last_error_time = now ;
}
bool HyperXMicrophoneV2Controller : : WaitForResponse ( const uint8_t * sent_packet , int timeout_ms )
{
uint8_t response [ HYPERX_QUADCAST_2S_PACKET_SIZE ] ;
memset ( response , 0 , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
int bytes_read = hid_read_timeout ( dev , response , HYPERX_QUADCAST_2S_PACKET_SIZE , timeout_ms ) ;
if ( bytes_read < = 0 )
{
LOG_WARNING ( " [%s] No response received from device (timeout: %d ms) " , name . c_str ( ) , timeout_ms ) ;
TrackCommunicationError ( ) ;
return false ;
}
/*---------------------------------------------------------*\
| Verify the response echoes back the command bytes |
| Response bytes 14 - 15 should match sent bytes 0 - 1 |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if ( response [ 14 ] = = sent_packet [ 0 ] & & response [ 15 ] = = sent_packet [ 1 ] )
{
return true ;
}
/*---------------------------------------------------------*\
| Log validation failure with full response payload |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
std : : stringstream response_hex ;
for ( int i = 0 ; i < bytes_read ; i + + )
{
response_hex < < std : : hex < < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' ) < < ( int ) response [ i ] ;
}
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 " ,
name . c_str ( ) , sent_packet [ 0 ] , sent_packet [ 1 ] , response [ 14 ] , response [ 15 ] , response_hex . str ( ) . c_str ( ) ) ;
TrackCommunicationError ( ) ;
return false ;
}
void HyperXMicrophoneV2Controller : : SendColorPackets ( std : : vector < RGBColor > colors , uint8_t command_byte )
{
uint8_t buf [ HYPERX_QUADCAST_2S_PACKET_SIZE ] ;
unsigned int total_leds_sent = 0 ;
unsigned int total_leds_sent = 0 ;
for ( unsigned int i = 0 ; i < 7 ; i + + )
for ( unsigned int packet = 0 ; packet < 6 ; packet + + )
{
{
memset ( buf , 0 , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
memset ( buf , 0 , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
buf [ 0 ] = HYPERX_QUADCAST_2S_REPORT_ID ;
buf [ 0 ] = HYPERX_QUADCAST_2S_REPORT_ID ;
buf [ 1 ] = i < 6 ? 0x02 : 0x01 ;
buf [ 1 ] = command_byte ;
buf [ 2 ] = i ;
buf [ 2 ] = packet ;
unsigned int c = 0 ;
unsigned int c = 0 ;
while ( c < HYPERX_QUADCAST_2S_LEDS_PER_PACKET & & total_leds_sent < HYPERX_QUADCAST_2S_TOTAL_LEDS )
while ( c < HYPERX_QUADCAST_2S_LEDS_PER_PACKET & & total_leds_sent < HYPERX_QUADCAST_2S_TOTAL_LEDS )
{
{
buf [ 4 + ( 3 * c ) ] = RGBGetRValue ( colors [ total_leds_sent ] ) ;
buf [ 4 + ( 3 * c ) ] = RGBGetRValue ( colors [ total_leds_sent ] ) ;
buf [ 5 + ( 3 * c ) ] = RGBGetGValue ( colors [ total_leds_sent ] ) ;
buf [ 5 + ( 3 * c ) ] = RGBGetGValue ( colors [ total_leds_sent ] ) ;
@ -82,7 +175,97 @@ void HyperXMicrophoneV2Controller::SendDirect(std::vector<RGBColor> colors)
}
}
hid_write ( dev , buf , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
hid_write ( dev , buf , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
WaitForResponse ( buf ) ;
}
}
void HyperXMicrophoneV2Controller : : SendDirect ( std : : vector < RGBColor > colors )
{
lock . lock ( ) ;
/*---------------------------------------------------------*\
| Skip sending if we ' re in pause mode |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if ( ShouldPauseUpdates ( ) )
{
lock . unlock ( ) ;
return ;
}
}
uint8_t buf [ HYPERX_QUADCAST_2S_PACKET_SIZE ] ;
/*---------------------------------------------------------*\
| Send header packet for direct mode |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
memset ( buf , 0 , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
buf [ 0 ] = HYPERX_QUADCAST_2S_REPORT_ID ;
buf [ 1 ] = 0x01 ;
buf [ 2 ] = 0x06 ;
hid_write ( dev , buf , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
WaitForResponse ( buf ) ;
/*---------------------------------------------------------*\
| Send color data packets |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
SendColorPackets ( colors , 0x02 ) ;
lock . unlock ( ) ;
}
void HyperXMicrophoneV2Controller : : SaveColors ( std : : vector < RGBColor > colors )
{
lock . lock ( ) ;
/*---------------------------------------------------------*\
| Skip sending if we ' re in pause mode |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if ( ShouldPauseUpdates ( ) )
{
lock . unlock ( ) ;
return ;
}
uint8_t buf [ HYPERX_QUADCAST_2S_PACKET_SIZE ] ;
/*---------------------------------------------------------*\
| Initiate save to device |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
memset ( buf , 0 , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
buf [ 0 ] = HYPERX_QUADCAST_2S_REPORT_ID ;
buf [ 1 ] = 0x03 ;
buf [ 2 ] = 0x01 ;
buf [ 3 ] = 0x06 ;
hid_write ( dev , buf , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
WaitForResponse ( buf ) ;
/*---------------------------------------------------------*\
| Send 6 color data packets |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
SendColorPackets ( colors , 0x04 ) ;
/*---------------------------------------------------------*\
| Send " Framerate " packet |
| If someone ever wanted to try and replicate the effects , |
| apparently this is the packet to try and change . |
| I believe currently this is setting a " static " frame |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
memset ( buf , 0 , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
buf [ 0 ] = 0x42 ;
buf [ 1 ] = 0x02 ;
buf [ 5 ] = 0xE8 ;
buf [ 6 ] = 0x03 ;
hid_write ( dev , buf , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
WaitForResponse ( buf ) ;
/*---------------------------------------------------------*\
| Send final packet |
\ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
memset ( buf , 0 , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
buf [ 0 ] = 0x40 ;
buf [ 1 ] = 0x01 ;
buf [ 4 ] = 0xFF ;
hid_write ( dev , buf , HYPERX_QUADCAST_2S_PACKET_SIZE ) ;
WaitForResponse ( buf ) ;
lock . unlock ( ) ;
lock . unlock ( ) ;
}
}