Files
OpenRGB/Controllers/LianLiController/LianLiUniHubSLController/LianLiUniHubSLController.cpp
Adam Honse 1403f91b3a RGBController API Overhaul
* Reorganize and clean up RGBController API functions
    * Add functions to get protected RGBController member values
    * Make NetworkClient, ProfileManager, and ResourceManager friend classes so they can access protected members
    * Protected previously-public RGBController members
        * Information strings (name, vendor, description, version, serial location)
        * Device type
        * Active mode
        * Flags
        * LEDs vector
        * LED alternate names vector
        * Modes vector
        * Colors vector
        * Zones vector
    * Add CONTROLLER_FLAG_HIDDEN to allow plugins to hide controllers from control GUI
    * Add update reason codes to RGBController update callback and signal updates on more RGBController events
    * Add loop zone types and segmented zone type
    * Add matrix map field to segments
    * Rework matrix_map_type from using pointers to vector to prevent memory leaks
    * Rework KeyboardLayoutManager to return new matrix_map_type
    * Add access mutex to RGBController API
    * Add per-zone modes ot RGBController API
    * Add JSON description functions to RGBController API
2026-01-20 19:56:37 -06:00

376 lines
11 KiB
C++

/*---------------------------------------------------------*\
| LianLiUniHubSLController.cpp |
| |
| Driver for Lian Li Uni Hub - SL |
| |
| Muhamad Visat 26 Jul 2025 |
| Original work by Luca Lovisa & Oliver P |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-only |
\*---------------------------------------------------------*/
#include "LogManager.h"
#include "StringUtils.h"
#include "LianLiUniHubSLController.h"
using namespace std::chrono_literals;
LianLiUniHubSLController::LianLiUniHubSLController(hid_device *dev, const char *path)
{
if(dev == nullptr)
{
return;
}
device = dev;
location = "HID: " + std::string(path);
is_merged_mode = false;
}
LianLiUniHubSLController::~LianLiUniHubSLController()
{
if(device != nullptr)
{
hid_close(device);
device = nullptr;
}
}
std::string LianLiUniHubSLController::ReadVersion()
{
wchar_t buf[40];
int ret = hid_get_product_string(this->device, buf, 40);
if(ret != 0)
{
return "";
}
/*------------------------------ -*\
| Example: LianLi-UNI FAN-SL-v1.8 |
| We just want the v1.8 part |
| without trailing spaces |
\*--------------------------------*/
std::string version = StringUtils::wstring_to_string(buf);
version = version.substr(version.find_last_of('-') + 1);
return version.substr(0, version.find_last_not_of(' ') + 1);
}
std::string LianLiUniHubSLController::ReadSerial()
{
wchar_t buf[20];
int ret = hid_get_serial_number_string(this->device, buf, 20);
if(ret != 0)
{
return "";
}
std::string serial = StringUtils::wstring_to_string(buf);
return serial;
}
void LianLiUniHubSLController::UpdateMode(const std::vector<zone> &zones, const mode &active)
{
/*---------------------------------*\
| Activate all channels |
\*---------------------------------*/
for(size_t channel = 0; channel < zones.size(); channel++)
{
this->SendActivate(channel, zones[channel].leds_count);
}
/*---------------------------------*\
| Set merge mode if requested |
\*---------------------------------*/
is_merged_mode = active.name.find("Merged") != std::string::npos;
this->SendMerge();
for (size_t channel = 0; channel < zones.size(); channel++)
{
DeviceUpdateZoneLEDs(channel, zones[channel], active);
}
}
void LianLiUniHubSLController::DeviceUpdateZoneLEDs(size_t channel, const zone &z, const mode &active)
{
/*---------------------------------*\
| Handle per-LED color mode |
\*---------------------------------*/
if(active.color_mode == MODE_COLORS_PER_LED)
{
this->SetPerLEDColor(channel, z, active);
return;
}
/*-------------------------------------------------*\
| In merged mode, only first channel takes control |
\*-------------------------------------------------*/
if(is_merged_mode && channel > 0)
{
return;
}
/*---------------------------------*\
| Handle mode-specific color |
\*---------------------------------*/
this->SetModeSpecificColor(channel, active);
}
void LianLiUniHubSLController::SetPerLEDColor(size_t channel, const zone &z, const mode &active)
{
unsigned char color_buf[UNIHUB_SL_MAX_LED_PER_CHANNEL * 3];
float brightness_scale = (float)(active.brightness) / active.brightness_max;
memset(color_buf, 0, sizeof(color_buf));
this->FillStaticColorBuffer(color_buf, z.colors, z.leds_count, brightness_scale);
this->SendColor(channel, color_buf, sizeof(color_buf));
this->SendMode(channel, active);
}
void LianLiUniHubSLController::SetModeSpecificColor(size_t channel, const mode &active)
{
unsigned char color_buf[UNIHUB_SL_MAX_LED_PER_CHANNEL * 3];
memset(color_buf, 0, sizeof(color_buf));
float brightness_scale = (float)(active.brightness) / active.brightness_max;
switch(active.value)
{
case UNIHUB_SL_LED_MODE_RAINBOW:
case UNIHUB_SL_LED_MODE_RAINBOW_MORPH:
case UNIHUB_SL_LED_MODE_STACK_MULTI_COLOR:
case UNIHUB_SL_LED_MODE_NEON:
/*-------------------------*\
| No need to set any value |
\*-------------------------*/
break;
case UNIHUB_SL_LED_MODE_STATIC:
case UNIHUB_SL_LED_MODE_BREATHING:
this->FillStaticColorBuffer(color_buf, active.colors.data(), active.colors.size(), brightness_scale);
break;
case UNIHUB_SL_LED_MODE_COLOR_CYCLE:
case UNIHUB_SL_LED_MODE_RUNWAY:
case UNIHUB_SL_LED_MODE_STAGGERED:
case UNIHUB_SL_LED_MODE_TIDE:
case UNIHUB_SL_LED_MODE_METEOR:
case UNIHUB_SL_LED_MODE_MIXING:
case UNIHUB_SL_LED_MODE_STACK:
this->FillDynamicColorBuffer(color_buf, active.colors.data(), active.colors.size(), brightness_scale);
break;
default:
LOG_WARNING("[Lian Li Uni Hub - SL] Unknown mode value: %d", active.value);
break;
}
this->SendColor(channel, color_buf, sizeof(color_buf));
this->SendMode(channel, active);
}
void LianLiUniHubSLController::FillStaticColorBuffer(unsigned char *color_buf, const RGBColor *colors, size_t num_colors, float brightness_scale)
{
size_t max_fans = (size_t)UNIHUB_SL_MAX_FAN_PER_CHANNEL;
size_t max_idx = num_colors < max_fans ? num_colors : max_fans;
for(size_t fan_idx = 0; fan_idx < max_idx; fan_idx++)
{
RGBColor color = colors[fan_idx];
unsigned char r = (unsigned char)(RGBGetRValue(color) * brightness_scale);
unsigned char g = (unsigned char)(RGBGetGValue(color) * brightness_scale);
unsigned char b = (unsigned char)(RGBGetBValue(color) * brightness_scale);
for(size_t led_idx = 0; led_idx < UNIHUB_SL_LED_PER_FAN; led_idx++)
{
size_t absolute_led_idx = fan_idx * UNIHUB_SL_LED_PER_FAN + led_idx;
size_t idx = absolute_led_idx * 3;
/*------------------------*\
| The protocol uses R B G |
\*------------------------*/
color_buf[idx + 0] = r;
color_buf[idx + 1] = b;
color_buf[idx + 2] = g;
}
}
}
void LianLiUniHubSLController::FillDynamicColorBuffer(unsigned char *color_buf, const RGBColor *colors, size_t num_colors, float brightness_scale)
{
size_t max_fans = (size_t)UNIHUB_SL_MAX_FAN_PER_CHANNEL;
size_t max_idx = num_colors < max_fans ? num_colors : max_fans;
for(size_t color_idx = 0; color_idx < max_idx; color_idx++)
{
RGBColor color = colors[color_idx];
unsigned char r = (unsigned char)(RGBGetRValue(color) * brightness_scale);
unsigned char g = (unsigned char)(RGBGetGValue(color) * brightness_scale);
unsigned char b = (unsigned char)(RGBGetBValue(color) * brightness_scale);
size_t idx = (color_idx * 3);
/*------------------------*\
| The protocol uses R B G |
\*------------------------*/
color_buf[idx + 0] = r;
color_buf[idx + 1] = b;
color_buf[idx + 2] = g;
}
}
unsigned char LianLiUniHubSLController::ConvertBrightness(unsigned int brightness)
{
switch(brightness)
{
case 0:
return UNIHUB_SL_LED_BRIGHTNESS_000;
case 1:
return UNIHUB_SL_LED_BRIGHTNESS_025;
case 2:
return UNIHUB_SL_LED_BRIGHTNESS_050;
case 3:
return UNIHUB_SL_LED_BRIGHTNESS_075;
case 4:
return UNIHUB_SL_LED_BRIGHTNESS_100;
default:
return UNIHUB_SL_LED_BRIGHTNESS_100;
}
}
unsigned char LianLiUniHubSLController::ConvertSpeed(unsigned int speed)
{
switch(speed)
{
case 0:
return UNIHUB_SL_LED_SPEED_000;
case 1:
return UNIHUB_SL_LED_SPEED_025;
case 2:
return UNIHUB_SL_LED_SPEED_050;
case 3:
return UNIHUB_SL_LED_SPEED_075;
case 4:
return UNIHUB_SL_LED_SPEED_100;
default:
return UNIHUB_SL_LED_SPEED_050;
}
}
unsigned char LianLiUniHubSLController::ConvertDirection(unsigned int direction)
{
switch(direction)
{
case MODE_DIRECTION_LEFT:
return UNIHUB_SL_LED_DIRECTION_LTR;
case MODE_DIRECTION_RIGHT:
return UNIHUB_SL_LED_DIRECTION_RTL;
default:
return UNIHUB_SL_LED_DIRECTION_LTR;
}
}
void LianLiUniHubSLController::SendActivate(size_t channel, unsigned char num_fans)
{
unsigned char buf[11];
memset(buf, 0x00, sizeof(buf));
buf[0x00] = UNIHUB_SL_REPORT_ID;
buf[0x01] = 0x10;
buf[0x02] = 0x32;
buf[0x03] = 0x10 * channel + num_fans;
this->LogBuffer("SendActivate", buf, sizeof(buf));
hid_write(this->device, buf, sizeof(buf));
std::this_thread::sleep_for(5ms);
}
void LianLiUniHubSLController::SendMerge()
{
unsigned char buf[11];
memset(buf, 0x00, sizeof(buf));
buf[0x00] = UNIHUB_SL_REPORT_ID;
buf[0x01] = 0x10;
if(is_merged_mode)
{
buf[0x02] = 0x33;
buf[0x03] = 0x00;
buf[0x04] = 0x01;
buf[0x05] = 0x02;
buf[0x06] = 0x03;
buf[0x07] = 0x08;
}
else
{
buf[0x02] = 0x34;
}
this->LogBuffer("SendMerge", buf, sizeof(buf));
hid_write(this->device, buf, sizeof(buf));
std::this_thread::sleep_for(5ms);
}
void LianLiUniHubSLController::SendColor(size_t channel, const unsigned char *colors, size_t num_colors)
{
unsigned char* buf = new unsigned char[2 + num_colors];
memset(buf, 0x00, sizeof(buf));
buf[0x00] = UNIHUB_SL_REPORT_ID;
buf[0x01] = 0x30 + channel; // Channel 1: 0x30, Channel 2: 0x31, etc.
memcpy(&buf[0x02], colors, num_colors);
this->LogBuffer("SendColor", buf, sizeof(buf));
hid_write(this->device, buf, sizeof(buf));
std::this_thread::sleep_for(5ms);
delete[] buf;
}
void LianLiUniHubSLController::SendMode(size_t channel, const mode &active)
{
unsigned char buf[11];
memset(buf, 0x00, sizeof(buf));
buf[0x00] = UNIHUB_SL_REPORT_ID;
buf[0x01] = 0x10 + channel; // Channel 1: 0x10, Channel 2: 0x11, etc.
buf[0x02] = active.value;
buf[0x03] = this->ConvertSpeed(active.speed);
buf[0x04] = this->ConvertDirection(active.direction);
buf[0x05] = this->ConvertBrightness(active.brightness);
this->LogBuffer("SendMode", buf, sizeof(buf));
hid_write(this->device, buf, sizeof(buf));
std::this_thread::sleep_for(5ms);
}
void LianLiUniHubSLController::LogBuffer(const char *operation, const unsigned char *buf, size_t buf_len)
{
std::string hex_string;
for(size_t i = 0; i < buf_len; i++)
{
char hex_byte[3];
snprintf(hex_byte, sizeof(hex_byte), "%02X", buf[i]);
hex_string += hex_byte;
}
LOG_DEBUG("[Lian Li Uni Hub - SL] %s buffer: %s", operation, hex_string.c_str());
}