Adds "Save to Device" feature to QuadCast 2S and logging

master
Logan Phillips 7 months ago committed by Adam Honse
parent d99be3e0e6
commit 9eb0169fcf

@ -1,25 +1,30 @@
/*---------------------------------------------------------*\ /*---------------------------------------------------------*\
| HyperXMicrophoneV2Controller.cpp | | HyperXMicrophoneV2Controller.cpp |
| | | |
| Driver for HyperX QuadCast 2S 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();
} }

@ -1,9 +1,10 @@
/*---------------------------------------------------------*\ /*---------------------------------------------------------*\
| HyperXMicrophoneV2Controller.cpp | | HyperXMicrophoneV2Controller.h |
| | | |
| Driver for HyperX QuadCast 2S 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 |
@ -33,10 +34,22 @@ public:
std::string GetSerialString(); std::string GetSerialString();
void SendDirect(std::vector<RGBColor> color_data); void SendDirect(std::vector<RGBColor> color_data);
void SaveColors(std::vector<RGBColor> color_data);
bool ShouldPauseUpdates();
private: private:
hid_device* dev; hid_device* dev;
std::string location; std::string location;
std::string name; std::string name;
std::mutex lock; std::mutex lock;
bool WaitForResponse(const uint8_t* sent_packet, int timeout_ms = 2000);
void SendColorPackets(std::vector<RGBColor> colors, uint8_t command_byte);
void TrackCommunicationError();
void FlushInputBuffer();
unsigned int errors;
std::chrono::steady_clock::time_point last_error_time;
std::chrono::steady_clock::time_point pause_until;
}; };

@ -1,9 +1,10 @@
/*---------------------------------------------------------*\ /*---------------------------------------------------------*\
| HyperXMicrophoneV2ControllerDetect.cpp | | HyperXMicrophoneV2ControllerDetect.cpp |
| | | |
| Detector for HyperX QuadCast 2s microphone | | Detector 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 |
@ -32,4 +33,4 @@ void DetectHyperXMicrophoneV2Controllers(hid_device_info* info, const std::strin
} }
} }
REGISTER_HID_DETECTOR_IPU("HyperX QuadCast 2S", DetectHyperXMicrophoneV2Controllers, HYPERX_HP_VID, HYPERX_QUADCAST_2S_PID, 1, 0xFF13, 0xFF00); REGISTER_HID_DETECTOR_IPU("HyperX QuadCast 2 S", DetectHyperXMicrophoneV2Controllers, HYPERX_HP_VID, HYPERX_QUADCAST_2S_PID, 1, 0xFF13, 0xFF00);

@ -1,9 +1,10 @@
/*---------------------------------------------------------*\ /*---------------------------------------------------------*\
| RGBController_HyperXMicrophoneV2.cpp | | RGBController_HyperXMicrophoneV2.cpp |
| | | |
| RGBController for HyperX QuadCast 2S microphone | | RGBController 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 |
@ -12,7 +13,7 @@
/**------------------------------------------------------------------*\ /**------------------------------------------------------------------*\
@name HyperX Quadcast 2S @name HyperX Quadcast 2S
@type USB @type USB
@save :x: @save :white_check_mark:
@direct :white_check_mark: @direct :white_check_mark:
@effects :x: @effects :x:
@detectors DetectHyperXMicrophoneV2Controllers @detectors DetectHyperXMicrophoneV2Controllers
@ -37,7 +38,7 @@ RGBController_HyperXMicrophoneV2::RGBController_HyperXMicrophoneV2(HyperXMicroph
mode Direct; mode Direct;
Direct.name = "Direct"; Direct.name = "Direct";
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR; Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE;
Direct.color_mode = MODE_COLORS_PER_LED; Direct.color_mode = MODE_COLORS_PER_LED;
Direct.colors_min = HYPERX_QUADCAST_2S_TOTAL_LEDS; Direct.colors_min = HYPERX_QUADCAST_2S_TOTAL_LEDS;
Direct.colors_max = HYPERX_QUADCAST_2S_TOTAL_LEDS; Direct.colors_max = HYPERX_QUADCAST_2S_TOTAL_LEDS;
@ -129,17 +130,18 @@ void RGBController_HyperXMicrophoneV2::DeviceUpdateMode()
void RGBController_HyperXMicrophoneV2::DeviceSaveMode() void RGBController_HyperXMicrophoneV2::DeviceSaveMode()
{ {
/* Unsuported */ LOG_DEBUG("[%s] Saving current direct colors to device", name.c_str());
controller->SaveColors(colors);
} }
void RGBController_HyperXMicrophoneV2::KeepaliveThread() void RGBController_HyperXMicrophoneV2::KeepaliveThread()
{ {
while(keepalive_thread_run.load()) while(keepalive_thread_run.load())
{ {
if((std::chrono::steady_clock::now() - last_update_time) > std::chrono::milliseconds(50)) if(!controller->ShouldPauseUpdates() && (std::chrono::steady_clock::now() - last_update_time) > std::chrono::milliseconds(1000))
{ {
UpdateLEDs(); UpdateLEDs();
} }
std::this_thread::sleep_for(15ms); std::this_thread::sleep_for(250ms);
} }
} }

@ -1,9 +1,10 @@
/*---------------------------------------------------------*\ /*---------------------------------------------------------*\
| RGBController_HyperXMicrophoneV2.h | | RGBController_HyperXMicrophoneV2.h |
| | | |
| RGBController for HyperX QuadCast 2S microphone | | RGBController 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 |

Loading…
Cancel
Save