Initial commit for HYTE Nexus controller

This commit is contained in:
Adam Honse 2024-11-13 19:52:10 -06:00
parent bd3cc94212
commit 80ac9b3f0d
5 changed files with 729 additions and 0 deletions

View file

@ -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]);
}

View file

@ -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 <atomic>
#include <chrono>
#include <string>
#include <thread>
#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<std::chrono::steady_clock> last_update_time;
std::atomic<bool> keepalive_thread_run;
std::thread keepalive_thread;
void KeepaliveThreadFunction();
void ReadChannelInfo(unsigned char channel);
void ReadDeviceInfo();
void ReadFirmwareVersion();
void SetStartupAnimation(bool enable);
};

View file

@ -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 <vector>
#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<std::string *> 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 ) |
\*---------------------------------------------------------------------------------------------------------*/

View file

@ -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();
}

View file

@ -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;
};