From 80ac9b3f0d7de3b1b279414e167d158aa263ba48 Mon Sep 17 00:00:00 2001 From: Adam Honse Date: Wed, 13 Nov 2024 19:52:10 -0600 Subject: [PATCH] Initial commit for HYTE Nexus controller --- .../HYTENexusController.cpp | 370 ++++++++++++++++++ .../HYTENexusController/HYTENexusController.h | 83 ++++ .../HYTENexusControllerDetect.cpp | 69 ++++ .../RGBController_HYTENexus.cpp | 173 ++++++++ .../RGBController_HYTENexus.h | 34 ++ 5 files changed, 729 insertions(+) create mode 100644 Controllers/HYTENexusController/HYTENexusController.cpp create mode 100644 Controllers/HYTENexusController/HYTENexusController.h create mode 100644 Controllers/HYTENexusController/HYTENexusControllerDetect.cpp create mode 100644 Controllers/HYTENexusController/RGBController_HYTENexus.cpp create mode 100644 Controllers/HYTENexusController/RGBController_HYTENexus.h diff --git a/Controllers/HYTENexusController/HYTENexusController.cpp b/Controllers/HYTENexusController/HYTENexusController.cpp new file mode 100644 index 00000000..003cafa8 --- /dev/null +++ b/Controllers/HYTENexusController/HYTENexusController.cpp @@ -0,0 +1,370 @@ +/*---------------------------------------------------------*\ +| 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]); + } + + serialport->serial_write((char *)command_buf, ((led_count * 3) + 7)); + 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]); +} diff --git a/Controllers/HYTENexusController/HYTENexusController.h b/Controllers/HYTENexusController/HYTENexusController.h new file mode 100644 index 00000000..6c6c5fd6 --- /dev/null +++ b/Controllers/HYTENexusController/HYTENexusController.h @@ -0,0 +1,83 @@ +/*---------------------------------------------------------*\ +| HYTENexusController.h | +| | +| 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 | +\*---------------------------------------------------------*/ + +#pragma once + +#include +#include +#include +#include +#include "RGBController.h" +#include "serial_port.h" + +#define HYTE_THICC_Q60_PID 0x0400 +#define HYTE_NEXUS_PORTAL_NP50_PID 0x0901 + +typedef struct +{ + unsigned int device_type; + unsigned int hardware_version; + unsigned char led_count; +} hyte_nexus_device; + +typedef struct +{ + bool is_nexus_channel; + bool has_4_led_logo; + bool has_6_led_logo; + bool has_lcd_leds; + unsigned int num_devices; + hyte_nexus_device devices[19]; +} hyte_nexus_channel; + +enum +{ + HYTE_NEXUS_DEVICE_TYPE_LS10 = 0x01, + HYTE_NEXUS_DEVICE_TYPE_LS30 = 0x02, + HYTE_NEXUS_DEVICE_TYPE_FP12 = 0x03, + HYTE_NEXUS_DEVICE_TYPE_FP12_DUO = 0x04, + HYTE_NEXUS_DEVICE_TYPE_FP12_TRIO = 0x05, + HYTE_NEXUS_DEVICE_TYPE_LN4060 = 0x06, + HYTE_NEXUS_DEVICE_TYPE_LN70 = 0x07, +}; + +class HYTENexusController +{ +public: + HYTENexusController(char* port, unsigned short pid); + ~HYTENexusController(); + + std::string GetFirmwareVersion(); + std::string GetLocation(); + std::string GetDeviceName(unsigned int device_type); + + void LEDStreaming(unsigned char channel, unsigned short num_leds, RGBColor* colors); + + hyte_nexus_channel channels[4]; + unsigned int num_channels; + unsigned short device_pid; + +private: + std::string firmware_version; + std::string port_name; + serial_port * serialport = nullptr; + std::chrono::time_point last_update_time; + std::atomic keepalive_thread_run; + std::thread keepalive_thread; + + void KeepaliveThreadFunction(); + + void ReadChannelInfo(unsigned char channel); + void ReadDeviceInfo(); + void ReadFirmwareVersion(); + + void SetStartupAnimation(bool enable); +}; diff --git a/Controllers/HYTENexusController/HYTENexusControllerDetect.cpp b/Controllers/HYTENexusController/HYTENexusControllerDetect.cpp new file mode 100644 index 00000000..acd8af8b --- /dev/null +++ b/Controllers/HYTENexusController/HYTENexusControllerDetect.cpp @@ -0,0 +1,69 @@ +/*---------------------------------------------------------*\ +| HYTENexusControllerDetect.cpp | +| | +| Detector for HYTE Nexus | +| | +| Adam Honse (calcprogrammer1@gmail.com) 19 Nov 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include +#include "Detector.h" +#include "HYTENexusController.h" +#include "RGBController_HYTENexus.h" +#include "find_usb_serial_port.h" + +#define HYTE_VID 0x3402 + +struct hyte_nexus_type +{ + unsigned short vid; + unsigned short pid; + const char * name; +}; + +#define HYTE_NEXUS_NUM_DEVICES 2 + +static const hyte_nexus_type hyte_nexus_devices[] = +{ + { HYTE_VID, HYTE_THICC_Q60_PID, "HYTE THICC Q60", }, + { HYTE_VID, HYTE_NEXUS_PORTAL_NP50_PID, "HYTE Nexus Portal NP50" }, +}; + +/******************************************************************************************\ +* * +* DetectHYTENexusControllers * +* * +* Detect devices supported by the HYTENexus driver * +* * +\******************************************************************************************/ + +void DetectHYTENexusControllers() +{ + for(unsigned int device_id = 0; device_id < HYTE_NEXUS_NUM_DEVICES; device_id++) + { + std::vector ports = find_usb_serial_port(hyte_nexus_devices[device_id].vid, hyte_nexus_devices[device_id].pid); + + for(unsigned int i = 0; i < ports.size(); i++) + { + if(*ports[i] != "") + { + HYTENexusController * controller = new HYTENexusController((char *)ports[i]->c_str(), hyte_nexus_devices[device_id].pid); + RGBController_HYTENexus * rgb_controller = new RGBController_HYTENexus(controller); + rgb_controller->name = hyte_nexus_devices[device_id].name; + + ResourceManager::get()->RegisterRGBController(rgb_controller); + } + } + } +} /* DetectHYTENexusControllers() */ + +REGISTER_DETECTOR("HYTE Nexus", DetectHYTENexusControllers); +/*---------------------------------------------------------------------------------------------------------*\ +| Entries for dynamic UDEV rules | +| | +| DUMMY_DEVICE_DETECTOR("HYTE THICC Q60", DetectHYTENexusControllers, 0x3402, 0x0400 ) | +| DUMMY_DEVICE_DETECTOR("HYTE Nexus Portal NP50", DetectHYTENexusControllers, 0x3402, 0x0901 ) | +\*---------------------------------------------------------------------------------------------------------*/ diff --git a/Controllers/HYTENexusController/RGBController_HYTENexus.cpp b/Controllers/HYTENexusController/RGBController_HYTENexus.cpp new file mode 100644 index 00000000..3c7678c6 --- /dev/null +++ b/Controllers/HYTENexusController/RGBController_HYTENexus.cpp @@ -0,0 +1,173 @@ +/*---------------------------------------------------------*\ +| RGBController_HYTENexus.cpp | +| | +| RGBController 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 "RGBController_HYTENexus.h" + +using namespace std::chrono_literals; + +RGBController_HYTENexus::RGBController_HYTENexus(HYTENexusController* controller_ptr) +{ + controller = controller_ptr; + + name = "HYTE Nexus Device"; + vendor = "HYTE"; + description = "HYTE Nexus Device"; + type = DEVICE_TYPE_LEDSTRIP; + location = controller->GetLocation(); + version = controller->GetFirmwareVersion(); + + mode Direct; + Direct.name = "Direct"; + Direct.value = 0; + Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR; + Direct.color_mode = MODE_COLORS_PER_LED; + modes.push_back(Direct); + + SetupZones(); +} + +RGBController_HYTENexus::~RGBController_HYTENexus() +{ + +} + +void RGBController_HYTENexus::SetupZones() +{ + for(unsigned int channel = 0; channel < controller->num_channels; channel++) + { + unsigned int channel_leds = 0; + unsigned int logo_leds = 0; + + for(unsigned int device = 0; device < controller->channels[channel].num_devices; device++) + { + channel_leds += controller->channels[channel].devices[device].led_count; + } + + if(controller->channels[channel].has_4_led_logo == true || controller->channels[channel].has_6_led_logo == true) + { + if(controller->channels[channel].has_4_led_logo == true) + { + logo_leds = 4; + } + else if(controller->channels[channel].has_6_led_logo == true) + { + logo_leds = 6; + } + + channel_leds += logo_leds; + } + + if(controller->channels[channel].has_lcd_leds == true) + { + channel_leds += 42; + } + + zone channel_zone; + + channel_zone.name = "Channel " + std::to_string(channel); + channel_zone.type = ZONE_TYPE_LINEAR; + channel_zone.leds_min = channel_leds; + channel_zone.leds_max = channel_leds; + channel_zone.leds_count = channel_leds; + channel_zone.matrix_map = NULL; + + zones.push_back(channel_zone); + + for(unsigned int led_idx = 0; led_idx < channel_leds; led_idx++) + { + led channel_led; + + channel_led.name = "Channel " + std::to_string(channel) + " LED " + std::to_string(led_idx); + channel_led.value = channel; + + leds.push_back(channel_led); + } + + unsigned int start_idx = 0; + + if(controller->channels[channel].has_4_led_logo == true || controller->channels[channel].has_6_led_logo == true) + { + segment logo_segment; + + logo_segment.name = "Logo"; + logo_segment.leds_count = logo_leds; + logo_segment.start_idx = start_idx; + logo_segment.type = ZONE_TYPE_SINGLE; + + zones[channel].segments.push_back(logo_segment); + + start_idx += logo_segment.leds_count; + } + + if(controller->channels[channel].has_lcd_leds == true) + { + segment lcd_leds_segment; + + lcd_leds_segment.name = "LCD LED Matrix"; + lcd_leds_segment.leds_count = 42; + lcd_leds_segment.start_idx = start_idx; + lcd_leds_segment.type = ZONE_TYPE_SINGLE; + + zones[channel].segments.push_back(lcd_leds_segment); + + start_idx += lcd_leds_segment.leds_count; + } + + for(unsigned int device = 0; device < controller->channels[channel].num_devices; device++) + { + if(controller->channels[channel].devices[device].led_count > 0) + { + segment device_segment; + + device_segment.name = controller->GetDeviceName(controller->channels[channel].devices[device].device_type); + device_segment.leds_count = controller->channels[channel].devices[device].led_count; + device_segment.start_idx = start_idx; + device_segment.type = ZONE_TYPE_LINEAR; + + zones[channel].segments.push_back(device_segment); + + start_idx += device_segment.leds_count; + } + } + } + + SetupColors(); +} + +void RGBController_HYTENexus::ResizeZone(int /*zone*/, int /*new_size*/) +{ + /*---------------------------------------------------------*\ + | This device does not support resizing zones | + \*---------------------------------------------------------*/ +} + +void RGBController_HYTENexus::DeviceUpdateLEDs() +{ + for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++) + { + UpdateZoneLEDs(zone_idx); + } +} + +void RGBController_HYTENexus::UpdateZoneLEDs(int zone) +{ + controller->LEDStreaming(zone, zones[zone].leds_count, zones[zone].colors); +} + +void RGBController_HYTENexus::UpdateSingleLED(int led) +{ + UpdateZoneLEDs(leds[led].value); +} + +void RGBController_HYTENexus::DeviceUpdateMode() +{ + DeviceUpdateLEDs(); +} diff --git a/Controllers/HYTENexusController/RGBController_HYTENexus.h b/Controllers/HYTENexusController/RGBController_HYTENexus.h new file mode 100644 index 00000000..aa1670af --- /dev/null +++ b/Controllers/HYTENexusController/RGBController_HYTENexus.h @@ -0,0 +1,34 @@ +/*---------------------------------------------------------*\ +| RGBController_HYTENexus.h | +| | +| RGBController 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 | +\*---------------------------------------------------------*/ + +#pragma once + +#include "HYTENexusController.h" +#include "RGBController.h" + +class RGBController_HYTENexus : public RGBController +{ +public: + RGBController_HYTENexus(HYTENexusController* controller_ptr); + ~RGBController_HYTENexus(); + + void SetupZones(); + void ResizeZone(int zone, int new_size); + + void DeviceUpdateLEDs(); + void UpdateZoneLEDs(int zone); + void UpdateSingleLED(int led); + + void DeviceUpdateMode(); + +private: + HYTENexusController* controller; +};