/*---------------------------------------------------------*\ | HYTENexusController.cpp | | | | Driver for HYTE Nexus | | | | Adam Honse (calcprogrammer1@gmail.com) 12 Nov 2024 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-only | \*---------------------------------------------------------*/ #include "HYTENexusController.h" #include "LogManager.h" using namespace std::chrono_literals; /*---------------------------------------------------------*\ | The protocol for the HYTE NP50 and Q60 are documented | | on hackmd.io: | | NP50: https://hackmd.io/3X_ojT77Sr-sLt5Fo2CYMQ | | Q60: https://hackmd.io/7qUhUQfIQReQYNhdGLqO6g | | | | More information on the HYTE Nexus Playground site: | | https://hyte.com/nexus/nexus-playground | \*---------------------------------------------------------*/ HYTENexusController::HYTENexusController(char* port, unsigned short pid) { port_name = port; device_pid = pid; /*-----------------------------------------------------*\ | Initialize channels based on PID | \*-----------------------------------------------------*/ memset(channels, 0, sizeof(channels)); switch(pid) { case HYTE_NEXUS_PORTAL_NP50_PID: num_channels = 3; channels[0].is_nexus_channel = true; channels[1].is_nexus_channel = true; channels[2].is_nexus_channel = true; channels[0].has_6_led_logo = true; break; case HYTE_THICC_Q60_PID: num_channels = 4; channels[0].is_nexus_channel = true; channels[1].is_nexus_channel = true; channels[2].is_nexus_channel = false; channels[3].is_nexus_channel = false; channels[2].has_lcd_leds = true; channels[3].has_4_led_logo = true; break; default: num_channels = 0; break; } /*-----------------------------------------------------*\ | Open the port | | Baud rate doesn't matter for ACM device | \*-----------------------------------------------------*/ serialport = new serial_port(port_name.c_str(), 2000000); /*-----------------------------------------------------*\ | Get controller information and firmware version | \*-----------------------------------------------------*/ ReadDeviceInfo(); ReadFirmwareVersion(); /*-----------------------------------------------------*\ | Get attached device information for all channels | \*-----------------------------------------------------*/ for(unsigned int channel = 0; channel < num_channels; channel++) { if(channels[channel].is_nexus_channel) { ReadChannelInfo(channel); } } keepalive_thread_run = true; keepalive_thread = std::thread(&HYTENexusController::KeepaliveThreadFunction, this); } HYTENexusController::~HYTENexusController() { keepalive_thread_run = false; keepalive_thread.join(); serialport->serial_close(); } std::string HYTENexusController::GetFirmwareVersion() { return(firmware_version); } std::string HYTENexusController::GetLocation() { return(port_name); } void HYTENexusController::KeepaliveThreadFunction() { while(keepalive_thread_run.load()) { if((std::chrono::steady_clock::now() - last_update_time) > 2500ms) { ReadDeviceInfo(); } std::this_thread::sleep_for(1s); } } std::string HYTENexusController::GetDeviceName(unsigned int device_type) { std::string device_name = ""; switch(device_type) { case HYTE_NEXUS_DEVICE_TYPE_LS10: device_name = "LS10"; break; case HYTE_NEXUS_DEVICE_TYPE_LS30: device_name = "LS30"; break; case HYTE_NEXUS_DEVICE_TYPE_FP12: device_name = "FP12"; break; case HYTE_NEXUS_DEVICE_TYPE_FP12_DUO: device_name = "FP12 Duo"; break; case HYTE_NEXUS_DEVICE_TYPE_FP12_TRIO: device_name = "FP12 Trio"; break; case HYTE_NEXUS_DEVICE_TYPE_LN4060: device_name = "LN4060"; break; case HYTE_NEXUS_DEVICE_TYPE_LN70: device_name = "LN70"; break; } return(device_name); } void HYTENexusController::LEDStreaming(unsigned char channel, unsigned short led_count, RGBColor* colors) { /*-----------------------------------------------------*\ | Send LED Streaming command | | Byte 0: FF | | Byte 1: EE | | Byte 2: 01 | | Byte 3: Channel (Port1, Port2, or Port3) | | Byte 4: LEDCount_H | | Byte 5: LEDCount_L | | Byte 6: Reserved | | Byte 7: G | | Byte 8: R | | Byte 9: B | | Repeat GRB pattern for remaining bytes | \*-----------------------------------------------------*/ unsigned char command_buf[750]; memset(command_buf, 0, sizeof(command_buf)); command_buf[0] = 0xFF; command_buf[1] = 0xEE; command_buf[2] = 0x01; command_buf[3] = (channel + 1); command_buf[4] = (led_count >> 8); command_buf[5] = (led_count & 0xFF); for(unsigned int led_idx = 0; led_idx < led_count; led_idx++) { unsigned int offset = (led_idx * 3); command_buf[7 + offset] = RGBGetGValue(colors[led_idx]); command_buf[8 + offset] = RGBGetRValue(colors[led_idx]); command_buf[9 + offset] = RGBGetBValue(colors[led_idx]); } /*-----------------------------------------------------*\ | The HYTE THICC Q60 requires 90 bytes to be sent for | | the 4th channel (logo) even though it only has 4 LEDs | \*-----------------------------------------------------*/ unsigned char bytes_to_send = ((led_count * 3) + 7); if((device_pid == HYTE_THICC_Q60_PID) && (channel == 3) && (bytes_to_send < 90)) { bytes_to_send = 90; } serialport->serial_write((char *)command_buf, bytes_to_send); serialport->serial_flush_tx(); serialport->serial_flush_rx(); /*-----------------------------------------------------*\ | Wait 10ms for device process command | \*-----------------------------------------------------*/ std::this_thread::sleep_for(5ms); } void HYTENexusController::ReadChannelInfo(unsigned char channel) { /*-----------------------------------------------------*\ | Send Get Channel Info command | | Byte 0: FF | | Byte 1: CC | | Byte 2: 01 (Get Status) | | Byte 3: Channel (Port1, Port2, or Port3) | \*-----------------------------------------------------*/ unsigned char command_buf[4]; command_buf[0] = 0xFF; command_buf[1] = 0xCC; command_buf[2] = 0x01; command_buf[3] = (channel + 1); serialport->serial_write((char *)command_buf, sizeof(command_buf)); serialport->serial_flush_tx(); /*-----------------------------------------------------*\ | Wait 50ms for device to send response | \*-----------------------------------------------------*/ std::this_thread::sleep_for(50ms); /*-----------------------------------------------------*\ | Receive Channel Info | | First Device Additional Devices | | Byte 0: FF 00 | | Byte 1: CC 00 | | Byte 2: Device Count | | Byte 3: Device Type | | Byte 4: Hardware Version | | Byte 5: LED Count | | Byte 6: Fan Temp_H | | Byte 7: Fan Temp_L | | Byte 8: Fan RPM_H | | Byte 9: Fan RPM_L | | Byte 10: Fan Orientation | | Byte 11: Touch | | | | Format repeats with offset of 12 * n for nth device | | in the list, except for the first two bytes being | | zero. | \*-----------------------------------------------------*/ unsigned char receive_buf[240]; serialport->serial_read((char *)receive_buf, sizeof(receive_buf)); serialport->serial_flush_rx(); channels[channel].num_devices = 0; memset(&channels[channel].devices, 0, sizeof(channels[channel].devices)); for(unsigned int device = 0; device < 19; device++) { unsigned int offset = 12 * device; if(receive_buf[offset + 2] > 0) { channels[channel].num_devices++; channels[channel].devices[device].device_type = receive_buf[offset + 3]; channels[channel].devices[device].hardware_version = receive_buf[offset + 4]; channels[channel].devices[device].led_count = receive_buf[offset + 5]; } } } void HYTENexusController::ReadDeviceInfo() { /*-----------------------------------------------------*\ | Send Get Device Info command | | Byte 0: FF | | Byte 1: CC | | Byte 2: 01 (Get Status) | | Byte 3: 00 (NP50/Pump) | \*-----------------------------------------------------*/ unsigned char command_buf[4]; command_buf[0] = 0xFF; command_buf[1] = 0xCC; command_buf[2] = 0x01; command_buf[3] = 0x00; serialport->serial_write((char *)command_buf, sizeof(command_buf)); serialport->serial_flush_tx(); /*-----------------------------------------------------*\ | Wait 50ms for device to send response | \*-----------------------------------------------------*/ std::this_thread::sleep_for(50ms); /*-----------------------------------------------------*\ | Receive Device Info | | NP50 Q60 | | Byte 0: FF FF | | Byte 1: CC CC | | Byte 2: 00 00 | | Byte 3: Noise_H Reserve | | Byte 4: Noise_L Reserve | | Byte 5: Reserve In Liquid Temp_H | | Byte 6: Reserve In Liquid Temp_L | | Byte 7: Temp_H Out Liquid Temp_H | | Byte 8: Temp_L Out Liquid Temp_L | | Byte 9: RPM_H Pump RPM_H | | Byte 10: RPM_L Pump RPM_L | | Byte 11: Reserve Fan Exhaust/Intake | | Byte 12: Current Cooling Mode Current Cooling Mode| | Byte 13: Warning Warnings | \*-----------------------------------------------------*/ unsigned char receive_buf[14]; serialport->serial_read((char *)receive_buf, sizeof(receive_buf)); serialport->serial_flush_rx(); /*-----------------------------------------------------*\ | Update last update time | \*-----------------------------------------------------*/ last_update_time = std::chrono::steady_clock::now(); } void HYTENexusController::ReadFirmwareVersion() { /*-----------------------------------------------------*\ | Send Get Firmware Version command | | Byte 0: FF | | Byte 1: DD | | Byte 2: 02 | | Byte 3: 00 (Reserve) | \*-----------------------------------------------------*/ unsigned char command_buf[4]; command_buf[0] = 0xFF; command_buf[1] = 0xDD; command_buf[2] = 0x02; command_buf[3] = 0x00; serialport->serial_write((char *)command_buf, sizeof(command_buf)); serialport->serial_flush_tx(); /*-----------------------------------------------------*\ | Wait 50ms for device to send response | \*-----------------------------------------------------*/ std::this_thread::sleep_for(50ms); /*-----------------------------------------------------*\ | Receive Firmware Version | | Byte 0: FF | | Byte 1: DD | | Byte 2: 02 | | Byte 3: Large Version | | Byte 4: Mid Version | | Byte 5: Small Version | | Byte 6: Hardware Version | \*-----------------------------------------------------*/ unsigned char receive_buf[7]; serialport->serial_read((char *)receive_buf, sizeof(receive_buf)); serialport->serial_flush_rx(); /*-----------------------------------------------------*\ | Format Firmware Version string | \*-----------------------------------------------------*/ firmware_version = "FW: " + std::to_string(receive_buf[3]) + "." + std::to_string(receive_buf[4]) + "." + std::to_string(receive_buf[5]) + ", HW: " + std::to_string(receive_buf[6]); }