diff --git a/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.cpp b/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.cpp index 7d5f7756..9f93a2bc 100644 --- a/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.cpp +++ b/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.cpp @@ -1,25 +1,30 @@ /*---------------------------------------------------------*\ | HyperXMicrophoneV2Controller.cpp | | | -| Driver for HyperX QuadCast 2S microphone | +| Driver for HyperX QuadCast 2 S Microphone | | | | Morgan Guimard (morg) | +| Logan Phillips (Eclipse) 23 Oct 2025 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | \*---------------------------------------------------------*/ #include +#include +#include #include "HyperXMicrophoneV2Controller.h" #include "StringUtils.h" - -using namespace std::chrono_literals; +#include "LogManager.h" HyperXMicrophoneV2Controller::HyperXMicrophoneV2Controller(hid_device* dev_handle, std::string path, std::string dev_name) { - dev = dev_handle; - location = path; - name = dev_name; + dev = dev_handle; + location = path; + name = dev_name; + errors = 0; + last_error_time = std::chrono::steady_clock::now(); + pause_until = std::chrono::steady_clock::now(); } HyperXMicrophoneV2Controller::~HyperXMicrophoneV2Controller() @@ -53,25 +58,113 @@ std::string HyperXMicrophoneV2Controller::GetSerialString() return(StringUtils::wstring_to_string(serial_string)); } -void HyperXMicrophoneV2Controller::SendDirect(std::vector 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(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 colors, uint8_t command_byte) +{ + uint8_t buf[HYPERX_QUADCAST_2S_PACKET_SIZE]; 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); buf[0] = HYPERX_QUADCAST_2S_REPORT_ID; - buf[1] = i < 6 ? 0x02 : 0x01; - buf[2] = i; + buf[1] = command_byte; + buf[2] = packet; 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[5 + (3 * c)] = RGBGetGValue(colors[total_leds_sent]); @@ -82,7 +175,97 @@ void HyperXMicrophoneV2Controller::SendDirect(std::vector colors) } hid_write(dev, buf, HYPERX_QUADCAST_2S_PACKET_SIZE); + WaitForResponse(buf); + } +} + +void HyperXMicrophoneV2Controller::SendDirect(std::vector 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 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(); } diff --git a/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.h b/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.h index e65ddfb5..99666831 100644 --- a/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.h +++ b/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.h @@ -1,9 +1,10 @@ /*---------------------------------------------------------*\ -| HyperXMicrophoneV2Controller.cpp | +| HyperXMicrophoneV2Controller.h | | | -| Driver for HyperX QuadCast 2S microphone | +| Driver for HyperX QuadCast 2 S Microphone | | | | Morgan Guimard (morg) | +| Logan Phillips (Eclipse) 23 Oct 2025 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | @@ -33,10 +34,22 @@ public: std::string GetSerialString(); void SendDirect(std::vector color_data); + void SaveColors(std::vector color_data); + + bool ShouldPauseUpdates(); private: hid_device* dev; std::string location; std::string name; std::mutex lock; + + bool WaitForResponse(const uint8_t* sent_packet, int timeout_ms = 2000); + void SendColorPackets(std::vector 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; }; diff --git a/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2ControllerDetect.cpp b/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2ControllerDetect.cpp index 440625c8..fc775a93 100644 --- a/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2ControllerDetect.cpp +++ b/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2ControllerDetect.cpp @@ -1,9 +1,10 @@ /*---------------------------------------------------------*\ | HyperXMicrophoneV2ControllerDetect.cpp | | | -| Detector for HyperX QuadCast 2s microphone | +| Detector for HyperX QuadCast 2 S Microphone | | | | Morgan Guimard (morg) | +| Logan Phillips (Eclipse) 23 Oct 2025 | | | | This file is part of the OpenRGB project | | 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); diff --git a/Controllers/HyperXMicrophoneV2Controller/RGBController_HyperXMicrophoneV2.cpp b/Controllers/HyperXMicrophoneV2Controller/RGBController_HyperXMicrophoneV2.cpp index 971d524c..3bfddb52 100644 --- a/Controllers/HyperXMicrophoneV2Controller/RGBController_HyperXMicrophoneV2.cpp +++ b/Controllers/HyperXMicrophoneV2Controller/RGBController_HyperXMicrophoneV2.cpp @@ -1,9 +1,10 @@ /*---------------------------------------------------------*\ | RGBController_HyperXMicrophoneV2.cpp | | | -| RGBController for HyperX QuadCast 2S microphone | +| RGBController for HyperX QuadCast 2 S Microphone | | | | Morgan Guimard (morg) | +| Logan Phillips (Eclipse) 23 Oct 2025 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | @@ -12,7 +13,7 @@ /**------------------------------------------------------------------*\ @name HyperX Quadcast 2S @type USB - @save :x: + @save :white_check_mark: @direct :white_check_mark: @effects :x: @detectors DetectHyperXMicrophoneV2Controllers @@ -37,7 +38,7 @@ RGBController_HyperXMicrophoneV2::RGBController_HyperXMicrophoneV2(HyperXMicroph mode 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.colors_min = 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() { - /* Unsuported */ + LOG_DEBUG("[%s] Saving current direct colors to device", name.c_str()); + controller->SaveColors(colors); } void RGBController_HyperXMicrophoneV2::KeepaliveThread() { 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(); } - std::this_thread::sleep_for(15ms); + std::this_thread::sleep_for(250ms); } } diff --git a/Controllers/HyperXMicrophoneV2Controller/RGBController_HyperXMicrophoneV2.h b/Controllers/HyperXMicrophoneV2Controller/RGBController_HyperXMicrophoneV2.h index 954aad03..e08153a3 100644 --- a/Controllers/HyperXMicrophoneV2Controller/RGBController_HyperXMicrophoneV2.h +++ b/Controllers/HyperXMicrophoneV2Controller/RGBController_HyperXMicrophoneV2.h @@ -1,9 +1,10 @@ /*---------------------------------------------------------*\ | RGBController_HyperXMicrophoneV2.h | | | -| RGBController for HyperX QuadCast 2S microphone | +| RGBController for HyperX QuadCast 2 S Microphone | | | | Morgan Guimard (morg) | +| Logan Phillips (Eclipse) 23 Oct 2025 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later |