Publish LumaOps source
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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++)
|
||||
{
|
||||
UpdateZoneLEDs(channel, zones[channel], active);
|
||||
}
|
||||
}
|
||||
|
||||
void LianLiUniHubSLController::UpdateZoneLEDs(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 * (unsigned char)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, (2 + num_colors));
|
||||
|
||||
buf[0x00] = UNIHUB_SL_REPORT_ID;
|
||||
buf[0x01] = 0x30 + (unsigned char)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 + (unsigned char)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());
|
||||
}
|
||||
Reference in New Issue
Block a user