Added all Skydimo Serial and HID devices

This commit is contained in:
Barry H
2026-07-13 07:33:43 -05:00
committed by Adam Honse
parent d11be2aa0b
commit 5da92fca4e
10 changed files with 1324 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
/*---------------------------------------------------------*\
| RGBController_SkydimoHID.cpp |
| |
| RGBController for Skydimo HID devices |
| |
| Bartholomew Ho <qiangqiang101@hotmail.com> 09 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_SkydimoHID.h"
/**------------------------------------------------------------------*\
@name Skydimo SK0902
@category HeadsetStand
@type USB
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectSkydimoHIDControllers
@comment
\*-------------------------------------------------------------------*/
static const unsigned int skydimo_hid_led_positions[SKYDIMO_HID_LED_COUNT][2] =
{
{0, 6}, {0, 5}, {0, 4}, {0, 3}, {0, 2}, {0, 1}, {0, 0},
{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4}, {1, 5}, {1, 6},
{2, 6}, {2, 5}, {2, 4}, {2, 3}, {2, 2}, {2, 1}, {2, 0},
{3, 0}, {3, 1}, {3, 2}, {3, 3}, {3, 4}, {3, 5}, {3, 6},
{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0},
{5, 0}, {5, 1}, {5, 2}, {5, 3}, {5, 4}, {5, 5}, {5, 6},
{6, 6}, {6, 5}, {6, 4}, {6, 3}, {6, 2}, {6, 1}, {6, 0}
};
RGBController_SkydimoHID::RGBController_SkydimoHID(SkydimoHIDController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetDeviceName();
vendor = "Skydimo";
type = DEVICE_TYPE_HEADSET_STAND;
description = "Skydimo HID Device";
serial = controller->GetSerial();
location = controller->GetLocation();
mode direct_mode;
direct_mode.name = "Direct";
direct_mode.value = 0;
direct_mode.flags = MODE_FLAG_HAS_PER_LED_COLOR;
direct_mode.color_mode = MODE_COLORS_PER_LED;
modes.push_back(direct_mode);
SetupZones();
}
RGBController_SkydimoHID::~RGBController_SkydimoHID()
{
Shutdown();
delete controller;
}
void RGBController_SkydimoHID::SetupZones()
{
zone led_zone;
led_zone.name = "Matrix";
led_zone.type = ZONE_TYPE_MATRIX;
led_zone.leds_min = SKYDIMO_HID_LED_COUNT;
led_zone.leds_max = SKYDIMO_HID_LED_COUNT;
led_zone.leds_count = SKYDIMO_HID_LED_COUNT;
led_zone.matrix_map.height = SKYDIMO_HID_MATRIX_SIZE;
led_zone.matrix_map.width = SKYDIMO_HID_MATRIX_SIZE;
led_zone.matrix_map.map.assign(SKYDIMO_HID_MATRIX_SIZE * SKYDIMO_HID_MATRIX_SIZE, SKYDIMO_HID_MATRIX_NA);
/*-----------------------------------------------------*\
| Construct the matrix map |
\*-----------------------------------------------------*/
for(unsigned int led_idx = 0; led_idx < SKYDIMO_HID_LED_COUNT; led_idx++)
{
unsigned int column = skydimo_hid_led_positions[led_idx][0];
unsigned int row = skydimo_hid_led_positions[led_idx][1];
led_zone.matrix_map.map[(row * SKYDIMO_HID_MATRIX_SIZE) + column] = led_idx;
}
zones.push_back(led_zone);
for(unsigned int led_idx = 0; led_idx < SKYDIMO_HID_LED_COUNT; led_idx++)
{
led new_led;
new_led.name = "LED " + std::to_string(led_idx + 1);
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_SkydimoHID::DeviceUpdateLEDs()
{
controller->SetLEDs(colors);
}
void RGBController_SkydimoHID::DeviceUpdateZoneLEDs(int)
{
DeviceUpdateLEDs();
}
void RGBController_SkydimoHID::DeviceUpdateSingleLED(int)
{
DeviceUpdateLEDs();
}
void RGBController_SkydimoHID::DeviceUpdateMode()
{
}

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------*\
| RGBController_SkydimoHID.h |
| |
| RGBController for Skydimo HID devices |
| |
| Bartholomew Ho <qiangqiang101@hotmail.com> 09 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "SkydimoHIDController.h"
class RGBController_SkydimoHID : public RGBController
{
public:
RGBController_SkydimoHID(SkydimoHIDController* controller_ptr);
~RGBController_SkydimoHID();
void SetupZones();
void DeviceUpdateLEDs();
void DeviceUpdateZoneLEDs(int zone);
void DeviceUpdateSingleLED(int led);
void DeviceUpdateMode();
private:
SkydimoHIDController* controller;
};

View File

@@ -0,0 +1,195 @@
/*---------------------------------------------------------*\
| SkydimoHIDController.cpp |
| |
| Driver for Skydimo HID devices |
| |
| Bartholomew Ho <qiangqiang101@hotmail.com> 09 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogManager.h"
#include "SkydimoHIDController.h"
#include "StringUtils.h"
static unsigned char CalculateCRC8(const unsigned char* data, unsigned int length)
{
unsigned char crc = 0x00;
for(unsigned int byte_idx = 0; byte_idx < length; byte_idx++)
{
crc ^= data[byte_idx];
for(unsigned int bit_idx = 0; bit_idx < SKYDIMO_HID_CRC_BIT_COUNT; bit_idx++)
{
if(crc & SKYDIMO_HID_CRC_HIGH_BIT)
{
crc = (unsigned char)((crc << 1) ^ SKYDIMO_HID_CRC_POLYNOMIAL);
}
else
{
crc = (unsigned char)(crc << 1);
}
}
}
return(crc);
}
SkydimoHIDController::SkydimoHIDController(hid_device* dev_handle, const char* path)
{
dev = dev_handle;
location = path;
wchar_t name_string[SKYDIMO_HID_NAME_BUFFER_SIZE];
int result;
result = hid_get_manufacturer_string(dev, name_string, SKYDIMO_HID_NAME_BUFFER_SIZE);
if(result == 0)
{
device_name = StringUtils::wstring_to_string(name_string);
}
else
{
device_name = "Skydimo";
LOG_WARNING("[SkydimoHIDController] Failed to read manufacturer string for %s", path);
}
result = hid_get_product_string(dev, name_string, SKYDIMO_HID_NAME_BUFFER_SIZE);
if(result == 0)
{
device_name.append(" ").append(StringUtils::wstring_to_string(name_string));
}
else
{
device_name.append(" HID Device");
LOG_WARNING("[SkydimoHIDController] Failed to read product string for %s", path);
}
wchar_t serial_string[SKYDIMO_HID_SERIAL_BUFFER_SIZE];
result = hid_get_serial_number_string(dev, serial_string, SKYDIMO_HID_SERIAL_BUFFER_SIZE);
if(result == 0)
{
serial = StringUtils::wstring_to_string(serial_string);
}
else
{
serial = "";
LOG_DEBUG("[SkydimoHIDController] No serial number available for %s", path);
}
}
SkydimoHIDController::~SkydimoHIDController()
{
if(dev)
{
hid_close(dev);
}
}
std::string SkydimoHIDController::GetDeviceName() const
{
return(device_name);
}
std::string SkydimoHIDController::GetSerial() const
{
return(serial);
}
std::string SkydimoHIDController::GetLocation() const
{
return("HID: " + location);
}
bool SkydimoHIDController::SendPacket(unsigned char start_led, const unsigned char* chunk, unsigned int chunk_length)
{
unsigned char packet[SKYDIMO_HID_PACKET_SIZE];
memset(packet, 0x00, sizeof(packet));
packet[0] = SKYDIMO_HID_REPORT_ID;
packet[SKYDIMO_HID_PACKET_START_LED_INDEX] = start_led;
packet[SKYDIMO_HID_PACKET_RESERVED_INDEX] = 0x00;
if(chunk_length > SKYDIMO_HID_PACKET_DATA_SIZE)
{
chunk_length = SKYDIMO_HID_PACKET_DATA_SIZE;
}
memcpy(&packet[SKYDIMO_HID_PACKET_DATA_OFFSET], chunk, chunk_length);
/*-----------------------------------------------------*\
| Calculate CRC8 over all bytes before the checksum |
\*-----------------------------------------------------*/
packet[SKYDIMO_HID_PACKET_SIZE - 1] = CalculateCRC8(packet, SKYDIMO_HID_PACKET_SIZE - 1);
int bytes_written = hid_write(dev, packet, SKYDIMO_HID_PACKET_SIZE);
if(bytes_written != SKYDIMO_HID_PACKET_SIZE)
{
LOG_ERROR("[SkydimoHIDController] Failed to write RGB packet at LED %u (%d/%u bytes): %ls",
(unsigned int)start_led, bytes_written, (unsigned int)SKYDIMO_HID_PACKET_SIZE, hid_error(dev));
return(false);
}
return(true);
}
void SkydimoHIDController::SetLEDs(const std::vector<RGBColor>& colors)
{
std::lock_guard<std::mutex> guard(device_mutex);
unsigned char frame_data[SKYDIMO_HID_FRAME_SIZE];
memset(frame_data, 0, sizeof(frame_data));
/*-----------------------------------------------------*\
| Convert colors to GRB format |
\*-----------------------------------------------------*/
for(unsigned int led_idx = 0; (led_idx < colors.size()) && (led_idx < SKYDIMO_HID_LED_COUNT); led_idx++)
{
unsigned int color_offset = led_idx * SKYDIMO_HID_BYTES_PER_LED;
frame_data[color_offset + 0] = RGBGetGValue(colors[led_idx]);
frame_data[color_offset + 1] = RGBGetRValue(colors[led_idx]);
frame_data[color_offset + 2] = RGBGetBValue(colors[led_idx]);
}
/*-----------------------------------------------------*\
| Send the frame in packet-sized chunks |
\*-----------------------------------------------------*/
for(unsigned int frame_offset = 0; frame_offset < SKYDIMO_HID_FRAME_SIZE; frame_offset += SKYDIMO_HID_PACKET_DATA_SIZE)
{
unsigned int bytes_remaining = SKYDIMO_HID_FRAME_SIZE - frame_offset;
unsigned int chunk_length = (bytes_remaining < SKYDIMO_HID_PACKET_DATA_SIZE)
? bytes_remaining
: SKYDIMO_HID_PACKET_DATA_SIZE;
unsigned char start_led = (unsigned char)(frame_offset / SKYDIMO_HID_BYTES_PER_LED);
if(!SendPacket(start_led, &frame_data[frame_offset], chunk_length))
{
return;
}
}
/*-----------------------------------------------------*\
| Send the frame-end packet |
\*-----------------------------------------------------*/
unsigned char end_packet[SKYDIMO_HID_PACKET_SIZE];
memset(end_packet, 0x00, sizeof(end_packet));
end_packet[0] = SKYDIMO_HID_REPORT_ID;
end_packet[SKYDIMO_HID_END_COMMAND_INDEX] = SKYDIMO_HID_END_COMMAND_VALUE;
end_packet[SKYDIMO_HID_END_SUBCOMMAND_INDEX] = SKYDIMO_HID_END_SUBCOMMAND_VALUE;
end_packet[SKYDIMO_HID_END_LED_COUNT_INDEX] = SKYDIMO_HID_LED_COUNT;
end_packet[SKYDIMO_HID_END_MARKER_INDEX] = SKYDIMO_HID_END_MARKER_VALUE;
int bytes_written = hid_write(dev, end_packet, SKYDIMO_HID_PACKET_SIZE);
if(bytes_written != SKYDIMO_HID_PACKET_SIZE)
{
LOG_ERROR("[SkydimoHIDController] Failed to write frame-end packet (%d/%u bytes): %ls",
bytes_written, (unsigned int)SKYDIMO_HID_PACKET_SIZE, hid_error(dev));
}
}

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------*\
| SkydimoHIDController.h |
| |
| Driver for Skydimo HID devices |
| |
| Bartholomew Ho <qiangqiang101@hotmail.com> 09 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <mutex>
#include <string>
#include <vector>
#include <hidapi.h>
#include "RGBControllerInterface.h"
#define SKYDIMO_HID_LED_COUNT 49
#define SKYDIMO_HID_MATRIX_SIZE 7
#define SKYDIMO_HID_BYTES_PER_LED 3
#define SKYDIMO_HID_FRAME_SIZE (SKYDIMO_HID_LED_COUNT * SKYDIMO_HID_BYTES_PER_LED)
#define SKYDIMO_HID_PACKET_SIZE 64
#define SKYDIMO_HID_PACKET_DATA_SIZE 60
#define SKYDIMO_HID_PACKET_DATA_OFFSET 3
#define SKYDIMO_HID_REPORT_ID 0x01
#define SKYDIMO_HID_PACKET_START_LED_INDEX 1
#define SKYDIMO_HID_PACKET_RESERVED_INDEX 2
#define SKYDIMO_HID_MATRIX_NA 0xFFFFFFFF
#define SKYDIMO_HID_CRC_POLYNOMIAL 0x07
#define SKYDIMO_HID_CRC_HIGH_BIT 0x80
#define SKYDIMO_HID_CRC_BIT_COUNT 8
#define SKYDIMO_HID_NAME_BUFFER_SIZE 255
#define SKYDIMO_HID_SERIAL_BUFFER_SIZE 128
#define SKYDIMO_HID_END_MARKER_INDEX 60
#define SKYDIMO_HID_END_MARKER_VALUE 0x1E
#define SKYDIMO_HID_END_COMMAND_INDEX 1
#define SKYDIMO_HID_END_COMMAND_VALUE 0xFF
#define SKYDIMO_HID_END_SUBCOMMAND_INDEX 2
#define SKYDIMO_HID_END_SUBCOMMAND_VALUE 0xFF
#define SKYDIMO_HID_END_LED_COUNT_INDEX 3
class SkydimoHIDController
{
public:
SkydimoHIDController(hid_device* dev_handle, const char* path);
~SkydimoHIDController();
std::string GetDeviceName() const;
std::string GetSerial() const;
std::string GetLocation() const;
void SetLEDs(const std::vector<RGBColor>& colors);
private:
hid_device* dev;
std::string device_name;
std::string serial;
std::string location;
std::mutex device_mutex;
bool SendPacket(unsigned char start_led, const unsigned char* chunk, unsigned int chunk_length);
};

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------*\
| SkydimoHIDControllerDetect.cpp |
| |
| Detector for Skydimo HID devices |
| |
| Bartholomew Ho <qiangqiang101@hotmail.com> 09 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "DetectionManager.h"
#include "LogManager.h"
#include "RGBController_SkydimoHID.h"
#include "SkydimoHIDController.h"
/*---------------------------------------------------------*\
| Skydimo HID vendor and product IDs |
\*---------------------------------------------------------*/
#define SKYDIMO_VID 0x1A86
#define SKYDIMO_SK0902_PID 0xE316
#define SKYDIMO_SK0902_INTERFACE 0
#define SKYDIMO_SK0902_USAGE_PAGE 0xFF00
#define SKYDIMO_SK0902_USAGE 0x01
DetectedControllers DetectSkydimoHIDControllers(hid_device_info* info, const std::string&)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
SkydimoHIDController* controller = new SkydimoHIDController(dev, info->path);
RGBController_SkydimoHID* rgb_controller = new RGBController_SkydimoHID(controller);
detected_controllers.push_back(rgb_controller);
LOG_INFO("[SkydimoHIDControllerDetect] Detected Skydimo SK0902 at %s", info->path);
}
else
{
LOG_WARNING("[SkydimoHIDControllerDetect] Failed to open Skydimo SK0902 at %s", info->path);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_IPU("Skydimo SK0902", DetectSkydimoHIDControllers, SKYDIMO_VID, SKYDIMO_SK0902_PID, SKYDIMO_SK0902_INTERFACE, SKYDIMO_SK0902_USAGE_PAGE, SKYDIMO_SK0902_USAGE);

View File

@@ -0,0 +1,147 @@
/*---------------------------------------------------------*\
| RGBController_SkydimoSerial.cpp |
| |
| RGBController for Skydimo serial devices |
| |
| Bartholomew Ho <qiangqiang101@hotmail.com> 30 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_SkydimoSerial.h"
/**------------------------------------------------------------------*\
@name Skydimo Serial
@category LEDStrip
@type Serial
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectSkydimoSerialControllers
@comment
\*-------------------------------------------------------------------*/
RGBController_SkydimoSerial::RGBController_SkydimoSerial(SkydimoSerialController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetName();
vendor = "Skydimo";
type = DEVICE_TYPE_LEDSTRIP;
description = "Skydimo Serial Device";
serial = controller->GetSerial();
location = controller->GetLocation();
mode direct_mode;
direct_mode.name = "Direct";
direct_mode.value = 0;
direct_mode.flags = MODE_FLAG_HAS_PER_LED_COLOR;
direct_mode.color_mode = MODE_COLORS_PER_LED;
modes.push_back(direct_mode);
SetupZones();
}
RGBController_SkydimoSerial::~RGBController_SkydimoSerial()
{
Shutdown();
controller->SetBlack();
delete controller;
}
void RGBController_SkydimoSerial::SetupZones()
{
bool first_run = zones.empty();
leds.clear();
colors.clear();
zones.resize(1);
zones[0].leds_min = controller->GetMinLEDCount();
zones[0].leds_max = controller->GetMaxLEDCount();
if(first_run)
{
if(controller->IsResizable())
{
zones[0].flags = ZONE_FLAG_MANUALLY_CONFIGURABLE_SIZE
| ZONE_FLAG_MANUALLY_CONFIGURABLE_NAME
| ZONE_FLAG_MANUALLY_CONFIGURABLE_TYPE
| ZONE_FLAG_MANUALLY_CONFIGURABLE_MATRIX_MAP
| ZONE_FLAG_MANUALLY_CONFIGURABLE_SEGMENTS;
}
}
if(!(zones[0].flags & ZONE_FLAG_MANUALLY_CONFIGURED_NAME))
{
zones[0].name = "Output 1";
}
if(!(zones[0].flags & ZONE_FLAG_MANUALLY_CONFIGURED_SIZE))
{
zones[0].leds_count = controller->GetLEDCount();
}
if(!(zones[0].flags & ZONE_FLAG_MANUALLY_CONFIGURED_TYPE))
{
zones[0].type = controller->GetMatrixMap().empty() ? ZONE_TYPE_LINEAR : ZONE_TYPE_MATRIX;
}
if(!(zones[0].flags & ZONE_FLAG_MANUALLY_CONFIGURED_MATRIX_MAP))
{
zones[0].matrix_map.height = controller->GetMatrixHeight();
zones[0].matrix_map.width = controller->GetMatrixWidth();
zones[0].matrix_map.map = controller->GetMatrixMap();
}
for(unsigned int led_idx = 0; led_idx < zones[0].leds_count; led_idx++)
{
led new_led;
new_led.name = zones[0].name + ", LED " + std::to_string(led_idx + 1);
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_SkydimoSerial::DeviceConfigureZone(int zone_idx)
{
unsigned int zone_index = (unsigned int)zone_idx;
if(zone_index < zones.size())
{
if(zones[zone_index].flags & ZONE_FLAG_MANUALLY_CONFIGURED_SIZE)
{
controller->SetLEDCount(zones[zone_index].leds_count);
}
else
{
controller->SetLEDCount(controller->GetMinLEDCount());
}
zones[zone_index].leds_count = controller->GetLEDCount();
SetupZones();
}
}
void RGBController_SkydimoSerial::DeviceUpdateLEDs()
{
controller->SetLEDs(colors);
}
void RGBController_SkydimoSerial::DeviceUpdateZoneLEDs(int)
{
DeviceUpdateLEDs();
}
void RGBController_SkydimoSerial::DeviceUpdateSingleLED(int)
{
DeviceUpdateLEDs();
}
void RGBController_SkydimoSerial::DeviceUpdateMode()
{
}

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------*\
| RGBController_SkydimoSerial.h |
| |
| RGBController for Skydimo serial devices |
| |
| Bartholomew Ho <qiangqiang101@hotmail.com> 30 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "SkydimoSerialController.h"
class RGBController_SkydimoSerial : public RGBController
{
public:
RGBController_SkydimoSerial(SkydimoSerialController* controller_ptr);
~RGBController_SkydimoSerial();
void SetupZones();
void DeviceConfigureZone(int zone_idx);
void DeviceUpdateLEDs();
void DeviceUpdateZoneLEDs(int zone);
void DeviceUpdateSingleLED(int led);
void DeviceUpdateMode();
private:
SkydimoSerialController* controller;
};

View File

@@ -0,0 +1,548 @@
/*---------------------------------------------------------*\
| SkydimoSerialController.cpp |
| |
| Driver for Skydimo serial devices |
| |
| Bartholomew Ho <qiangqiang101@hotmail.com> 30 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <algorithm>
#include <cctype>
#include "LogManager.h"
#include "SkydimoSerialController.h"
enum SkydimoLayoutKind
{
SKYDIMO_LAYOUT_NONE,
SKYDIMO_LAYOUT_STRIP_1,
SKYDIMO_LAYOUT_PERIMETER_3,
SKYDIMO_LAYOUT_PERIMETER_4,
SKYDIMO_LAYOUT_SIDES_2
};
struct SkydimoModelEntry
{
const char* model;
SkydimoLayoutKind layout;
unsigned int zone_leds[4];
unsigned int total_leds;
unsigned int max_leds;
};
static const SkydimoModelEntry skydimo_model_entries[] =
{
{ "SK0121", SKYDIMO_LAYOUT_PERIMETER_3, { 13, 25, 13, 0 }, 51, 0 },
{ "SK0124", SKYDIMO_LAYOUT_PERIMETER_3, { 14, 26, 14, 0 }, 54, 0 },
{ "SK0127", SKYDIMO_LAYOUT_PERIMETER_3, { 17, 31, 17, 0 }, 65, 0 },
{ "SK0132", SKYDIMO_LAYOUT_PERIMETER_3, { 20, 37, 20, 0 }, 77, 0 },
{ "SK0134", SKYDIMO_LAYOUT_PERIMETER_3, { 15, 41, 15, 0 }, 71, 0 },
{ "SK0149", SKYDIMO_LAYOUT_PERIMETER_3, { 19, 69, 19, 0 }, 107, 0 },
{ "SK0201", SKYDIMO_LAYOUT_SIDES_2, { 20, 20, 0, 0 }, 40, 0 },
{ "SK0202", SKYDIMO_LAYOUT_SIDES_2, { 30, 30, 0, 0 }, 60, 0 },
{ "SK0204", SKYDIMO_LAYOUT_SIDES_2, { 25, 25, 0, 0 }, 50, 0 },
{ "SK0402", SKYDIMO_LAYOUT_STRIP_1, { 72, 0, 0, 0 }, 72, 0 },
{ "SK0403", SKYDIMO_LAYOUT_STRIP_1, { 96, 0, 0, 0 }, 96, 0 },
{ "SK0404", SKYDIMO_LAYOUT_STRIP_1, { 144, 0, 0, 0 }, 144, 0 },
{ "SK0410", SKYDIMO_LAYOUT_NONE, { 0, 0, 0, 0 }, 0, 300 },
{ "SK0801", SKYDIMO_LAYOUT_STRIP_1, { 2, 0, 0, 0 }, 2, 0 },
{ "SK0802", SKYDIMO_LAYOUT_STRIP_1, { 18, 0, 0, 0 }, 18, 0 },
{ "SK0803", SKYDIMO_LAYOUT_STRIP_1, { 10, 0, 0, 0 }, 10, 0 },
{ "SK0901", SKYDIMO_LAYOUT_STRIP_1, { 14, 0, 0, 0 }, 14, 0 },
{ "SK0E01", SKYDIMO_LAYOUT_STRIP_1, { 16, 0, 0, 0 }, 16, 0 },
{ "SK0F01", SKYDIMO_LAYOUT_SIDES_2, { 29, 29, 0, 0 }, 58, 0 },
{ "SK0F02", SKYDIMO_LAYOUT_SIDES_2, { 25, 25, 0, 0 }, 50, 0 },
{ "SK0H01", SKYDIMO_LAYOUT_STRIP_1, { 2, 0, 0, 0 }, 2, 0 },
{ "SK0H02", SKYDIMO_LAYOUT_STRIP_1, { 4, 0, 0, 0 }, 4, 0 },
{ "SK0K01", SKYDIMO_LAYOUT_STRIP_1, { 120, 0, 0, 0 }, 120, 0 },
{ "SK0K02", SKYDIMO_LAYOUT_STRIP_1, { 15, 0, 0, 0 }, 15, 0 },
{ "SK0L21", SKYDIMO_LAYOUT_PERIMETER_4, { 13, 25, 13, 25 }, 76, 0 },
{ "SK0L24", SKYDIMO_LAYOUT_PERIMETER_4, { 14, 26, 14, 26 }, 80, 0 },
{ "SK0L27", SKYDIMO_LAYOUT_PERIMETER_4, { 17, 31, 17, 31 }, 96, 0 },
{ "SK0L32", SKYDIMO_LAYOUT_PERIMETER_4, { 20, 37, 20, 37 }, 114, 0 },
{ "SK0L34", SKYDIMO_LAYOUT_PERIMETER_4, { 15, 41, 15, 41 }, 112, 0 },
{ "SK0M01", SKYDIMO_LAYOUT_STRIP_1, { 24, 0, 0, 0 }, 24, 0 },
{ "SK0N01", SKYDIMO_LAYOUT_STRIP_1, { 256, 0, 0, 0 }, 256, 0 },
{ "SK0N02", SKYDIMO_LAYOUT_STRIP_1, { 1024, 0, 0, 0 }, 1024, 0 },
{ "SK0N03", SKYDIMO_LAYOUT_STRIP_1, { 253, 0, 0, 0 }, 253, 0 },
{ "SK0S01", SKYDIMO_LAYOUT_STRIP_1, { 32, 0, 0, 0 }, 32, 0 },
{ "SKA124", SKYDIMO_LAYOUT_PERIMETER_3, { 18, 34, 18, 0 }, 70, 0 },
{ "SKA127", SKYDIMO_LAYOUT_PERIMETER_3, { 20, 41, 20, 0 }, 81, 0 },
{ "SKA132", SKYDIMO_LAYOUT_PERIMETER_3, { 25, 45, 25, 0 }, 95, 0 },
{ "SKA134", SKYDIMO_LAYOUT_PERIMETER_3, { 21, 51, 21, 0 }, 93, 0 },
{ "SKB124", SKYDIMO_LAYOUT_PERIMETER_4, { 18, 34, 18, 34 }, 104, 0 },
{ "SKB127", SKYDIMO_LAYOUT_PERIMETER_4, { 20, 41, 20, 41 }, 122, 0 },
{ "SKB132", SKYDIMO_LAYOUT_PERIMETER_4, { 25, 44, 25, 44 }, 138, 0 },
{ "SKB134", SKYDIMO_LAYOUT_PERIMETER_4, { 21, 50, 21, 50 }, 142, 0 }
};
#define SKYDIMO_MODEL_ENTRY_COUNT (sizeof(skydimo_model_entries) / sizeof(skydimo_model_entries[0]))
static const unsigned char skydimo_serial_frame_header[] =
{
0x41, 0x64, 0x61, 0x00
};
static std::string StringToUpper(const std::string& value)
{
std::string result = value;
for(unsigned int char_idx = 0; char_idx < result.size(); char_idx++)
{
result[char_idx] = (char)std::toupper((unsigned char)result[char_idx]);
}
return(result);
}
static const SkydimoModelEntry* FindModelEntry(const std::string& model)
{
for(unsigned int entry_idx = 0; entry_idx < SKYDIMO_MODEL_ENTRY_COUNT; entry_idx++)
{
if(model == skydimo_model_entries[entry_idx].model)
{
return(&skydimo_model_entries[entry_idx]);
}
}
return(nullptr);
}
static std::string BytesToHex(const std::vector<unsigned char>& bytes, unsigned int offset)
{
static const char hex[] = "0123456789ABCDEF";
std::string result;
result.reserve((bytes.size() - offset) * 2);
for(unsigned int byte_idx = offset; byte_idx < bytes.size(); byte_idx++)
{
result.push_back(hex[(bytes[byte_idx] >> 4) & 0x0F]);
result.push_back(hex[bytes[byte_idx] & 0x0F]);
}
return(result);
}
SkydimoSerialController::SkydimoSerialController(const std::string& port_name_arg)
{
port_name = port_name_arg;
model_name = "Skydimo UNKNOWN";
model_id = "UNKNOWN";
serial_id = "000000";
port_open = serial_port_interface.serial_open(port_name.c_str(), SKYDIMO_SERIAL_BAUD_RATE);
present = false;
resizable = true;
leds_count = SKYDIMO_SERIAL_DEFAULT_LED_COUNT;
leds_min = SKYDIMO_SERIAL_DEFAULT_LED_COUNT;
leds_max = SKYDIMO_SERIAL_DEFAULT_MAX_LED_COUNT;
matrix_width = 0;
matrix_height = 0;
if(!port_open)
{
LOG_DEBUG("[SkydimoSerialController] Failed to open serial port %s", port_name.c_str());
}
}
SkydimoSerialController::~SkydimoSerialController()
{
}
bool SkydimoSerialController::IsPresent()
{
if(present)
{
return(true);
}
present = QueryInfo();
return(present);
}
bool SkydimoSerialController::QueryInfo()
{
if(!port_open)
{
return(false);
}
serial_port_interface.serial_flush_rx();
char query[] = SKYDIMO_SERIAL_QUERY;
int bytes_written = serial_port_interface.serial_write(query, SKYDIMO_SERIAL_QUERY_LENGTH);
if(bytes_written != SKYDIMO_SERIAL_QUERY_LENGTH)
{
LOG_DEBUG("[SkydimoSerialController] Failed to send identification query to %s (%d/%u bytes)",
port_name.c_str(), bytes_written, (unsigned int)SKYDIMO_SERIAL_QUERY_LENGTH);
return(false);
}
std::vector<unsigned char> response;
for(unsigned int attempt = 0; attempt < SKYDIMO_SERIAL_QUERY_ATTEMPTS; attempt++)
{
char buffer[SKYDIMO_SERIAL_READ_BUFFER_SIZE];
int bytes_read = serial_port_interface.serial_read(buffer, SKYDIMO_SERIAL_READ_BUFFER_SIZE);
if(bytes_read < 0)
{
LOG_DEBUG("[SkydimoSerialController] Failed to read identification response from %s", port_name.c_str());
return(false);
}
if(bytes_read > 0)
{
response.insert(response.end(), buffer, buffer + bytes_read);
}
if(response.size() >= SKYDIMO_SERIAL_MAX_RESPONSE_SIZE)
{
break;
}
if(!response.empty())
{
unsigned char last = response[response.size() - 1];
unsigned char prev = response.size() >= 2 ? response[response.size() - 2] : 0;
if((last == '\r') || (last == '\n') || (prev == '\r'))
{
break;
}
}
}
while(!response.empty() && ((response.back() == '\r') || (response.back() == '\n')))
{
response.pop_back();
}
if(response.size() < SKYDIMO_SERIAL_MODEL_ID_LENGTH)
{
LOG_TRACE("[SkydimoSerialController] No Skydimo identification response from %s", port_name.c_str());
return(false);
}
if((std::toupper(response[0]) != 'S') || (std::toupper(response[1]) != 'K'))
{
LOG_TRACE("[SkydimoSerialController] Ignoring non-Skydimo serial device at %s", port_name.c_str());
return(false);
}
model_id = StringToUpper(std::string(response.begin(), response.begin() + SKYDIMO_SERIAL_MODEL_ID_LENGTH));
model_name = "Skydimo " + model_id;
serial_id = "000000";
unsigned int comma_offset = (unsigned int)response.size();
for(unsigned int byte_idx = 0; byte_idx < response.size(); byte_idx++)
{
if(response[byte_idx] == ',')
{
comma_offset = byte_idx;
break;
}
}
if(comma_offset < response.size())
{
if(comma_offset < SKYDIMO_SERIAL_MODEL_ID_LENGTH)
{
LOG_DEBUG("[SkydimoSerialController] Invalid identification response from %s", port_name.c_str());
return(false);
}
if((comma_offset + 1) < response.size())
{
serial_id = BytesToHex(response, comma_offset + 1);
}
}
ConfigureOutput();
return(true);
}
void SkydimoSerialController::ConfigureOutput()
{
const SkydimoModelEntry* entry = FindModelEntry(model_id);
resizable = true;
leds_count = SKYDIMO_SERIAL_DEFAULT_LED_COUNT;
leds_min = SKYDIMO_SERIAL_DEFAULT_LED_COUNT;
leds_max = SKYDIMO_SERIAL_DEFAULT_MAX_LED_COUNT;
matrix_width = 0;
matrix_height = 0;
matrix_map.clear();
if((entry != nullptr) && (entry->max_leds != 0))
{
leds_max = entry->max_leds;
}
if((entry == nullptr) || (entry->layout == SKYDIMO_LAYOUT_NONE))
{
return;
}
resizable = false;
leds_count = entry->total_leds;
leds_min = entry->total_leds;
leds_max = entry->total_leds;
if(entry->layout == SKYDIMO_LAYOUT_STRIP_1)
{
return;
}
const unsigned int zone_1_leds = entry->zone_leds[0];
const unsigned int zone_2_leds = entry->zone_leds[1];
const unsigned int zone_3_leds = entry->zone_leds[2];
const unsigned int zone_4_leds = entry->zone_leds[3];
if(entry->layout == SKYDIMO_LAYOUT_PERIMETER_4)
{
matrix_height = std::max(zone_1_leds, zone_3_leds) + 2;
matrix_width = std::max(zone_2_leds, zone_4_leds) + 2;
}
else if(entry->layout == SKYDIMO_LAYOUT_PERIMETER_3)
{
matrix_height = std::max(zone_1_leds, zone_3_leds) + 1;
matrix_width = zone_2_leds + 2;
}
else
{
matrix_height = std::max(zone_1_leds, zone_2_leds) + 2;
matrix_width = std::max(3U, ((16 * matrix_height) + 4) / 9);
}
matrix_map.assign(matrix_width * matrix_height, SKYDIMO_SERIAL_MATRIX_NA);
unsigned int led_index = 0;
if(entry->layout == SKYDIMO_LAYOUT_SIDES_2)
{
unsigned int placed = 0;
int y = (int)matrix_height - 2;
while((placed < zone_1_leds) && (y >= 1))
{
SetMatrixCell((unsigned int)y, 0, led_index);
placed++;
y--;
}
}
else
{
unsigned int placed = 0;
int y = entry->layout == SKYDIMO_LAYOUT_PERIMETER_3 ? (int)matrix_height - 1
: (int)matrix_height - 2;
while((placed < zone_1_leds) && (y >= 1))
{
SetMatrixCell((unsigned int)y, matrix_width - 1, led_index);
placed++;
y--;
}
}
if((entry->layout == SKYDIMO_LAYOUT_PERIMETER_3) || (entry->layout == SKYDIMO_LAYOUT_PERIMETER_4))
{
unsigned int placed = 0;
int x = (int)matrix_width - 2;
while((placed < zone_2_leds) && (x >= 1))
{
SetMatrixCell(0, (unsigned int)x, led_index);
placed++;
x--;
}
}
else if(entry->layout == SKYDIMO_LAYOUT_SIDES_2)
{
unsigned int placed = 0;
unsigned int y = 1;
while((placed < zone_2_leds) && (y <= (matrix_height - 2)))
{
SetMatrixCell(y, matrix_width - 1, led_index);
placed++;
y++;
}
}
unsigned int end_y = entry->layout == SKYDIMO_LAYOUT_PERIMETER_3 ? matrix_height - 1 : matrix_height - 2;
unsigned int placed = 0;
unsigned int y = 1;
while((placed < zone_3_leds) && (y <= end_y))
{
SetMatrixCell(y, 0, led_index);
placed++;
y++;
}
if(entry->layout == SKYDIMO_LAYOUT_PERIMETER_4)
{
placed = 0;
unsigned int x = 1;
while((placed < zone_4_leds) && (x <= (matrix_width - 2)))
{
SetMatrixCell(matrix_height - 1, x, led_index);
placed++;
x++;
}
}
if(led_index != entry->total_leds)
{
LOG_ERROR("[SkydimoSerialController] Invalid matrix map for %s (%u/%u LEDs placed)",
model_id.c_str(), led_index, entry->total_leds);
}
}
void SkydimoSerialController::SetMatrixCell(unsigned int y, unsigned int x, unsigned int& led_index)
{
if((y < matrix_height) && (x < matrix_width))
{
matrix_map[(y * matrix_width) + x] = led_index++;
}
}
bool SkydimoSerialController::WriteFrame(const std::vector<RGBColor>& colors, unsigned int count)
{
std::lock_guard<std::mutex> guard(device_mutex);
if(!port_open)
{
return(false);
}
if(count > colors.size())
{
count = (unsigned int)colors.size();
}
std::vector<unsigned char> packet;
packet.reserve(sizeof(skydimo_serial_frame_header) + SKYDIMO_SERIAL_FRAME_COUNT_SIZE + (count * SKYDIMO_SERIAL_BYTES_PER_LED));
for(unsigned int header_idx = 0; header_idx < sizeof(skydimo_serial_frame_header); header_idx++)
{
packet.push_back(skydimo_serial_frame_header[header_idx]);
}
packet.push_back((unsigned char)((count >> 8) & 0xFF));
packet.push_back((unsigned char)(count & 0xFF));
for(unsigned int led_idx = 0; led_idx < count; led_idx++)
{
packet.push_back(RGBGetRValue(colors[led_idx]));
packet.push_back(RGBGetGValue(colors[led_idx]));
packet.push_back(RGBGetBValue(colors[led_idx]));
}
int packet_size = (int)packet.size();
int total_bytes_written = 0;
while(total_bytes_written < packet_size)
{
int bytes_written = serial_port_interface.serial_write((char*)packet.data() + total_bytes_written,
packet_size - total_bytes_written);
if(bytes_written <= 0)
{
LOG_ERROR("[SkydimoSerialController] Failed to write RGB frame to %s (%d/%d bytes)",
port_name.c_str(), total_bytes_written, packet_size);
return(false);
}
total_bytes_written += bytes_written;
}
return(true);
}
void SkydimoSerialController::SetLEDs(const std::vector<RGBColor>& colors)
{
WriteFrame(colors, leds_count);
}
void SkydimoSerialController::SetBlack()
{
std::vector<RGBColor> black_colors;
black_colors.resize(leds_count, ToRGBColor(0, 0, 0));
WriteFrame(black_colors, leds_count);
}
void SkydimoSerialController::SetLEDCount(unsigned int led_count)
{
if(!resizable)
{
return;
}
if((led_count < leds_min) || (led_count > leds_max))
{
LOG_WARNING("[SkydimoSerialController] Ignoring invalid LED count %u for %s (valid range %u-%u)",
led_count, model_id.c_str(), leds_min, leds_max);
return;
}
leds_count = led_count;
}
std::string SkydimoSerialController::GetName() const
{
return(model_name);
}
std::string SkydimoSerialController::GetSerial() const
{
return(serial_id);
}
std::string SkydimoSerialController::GetLocation() const
{
return("Serial: " + port_name);
}
bool SkydimoSerialController::IsResizable() const
{
return(resizable);
}
unsigned int SkydimoSerialController::GetLEDCount() const
{
return(leds_count);
}
unsigned int SkydimoSerialController::GetMinLEDCount() const
{
return(leds_min);
}
unsigned int SkydimoSerialController::GetMaxLEDCount() const
{
return(leds_max);
}
unsigned int SkydimoSerialController::GetMatrixWidth() const
{
return(matrix_width);
}
unsigned int SkydimoSerialController::GetMatrixHeight() const
{
return(matrix_height);
}
const std::vector<unsigned int>& SkydimoSerialController::GetMatrixMap() const
{
return(matrix_map);
}

View File

@@ -0,0 +1,82 @@
/*---------------------------------------------------------*\
| SkydimoSerialController.h |
| |
| Driver for Skydimo serial devices |
| |
| Bartholomew Ho <qiangqiang101@hotmail.com> 30 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <mutex>
#include <string>
#include <vector>
#include "RGBController.h"
#include "serial_port.h"
#define SKYDIMO_SERIAL_BAUD_RATE 115200
#define SKYDIMO_SERIAL_QUERY "Moni-A"
#define SKYDIMO_SERIAL_QUERY_LENGTH 6
#define SKYDIMO_SERIAL_MODEL_ID_LENGTH 6
#define SKYDIMO_SERIAL_READ_BUFFER_SIZE 64
#define SKYDIMO_SERIAL_MAX_RESPONSE_SIZE 63
#define SKYDIMO_SERIAL_QUERY_ATTEMPTS 10
#define SKYDIMO_SERIAL_DEFAULT_LED_COUNT 0
#define SKYDIMO_SERIAL_DEFAULT_MAX_LED_COUNT 150
#define SKYDIMO_SERIAL_BYTES_PER_LED 3
#define SKYDIMO_SERIAL_FRAME_COUNT_SIZE 2
#define SKYDIMO_SERIAL_MATRIX_NA 0xFFFFFFFF
class SkydimoSerialController
{
public:
SkydimoSerialController(const std::string& port_name_arg);
~SkydimoSerialController();
bool IsPresent();
void SetLEDs(const std::vector<RGBColor>& colors);
void SetBlack();
void SetLEDCount(unsigned int led_count);
std::string GetName() const;
std::string GetSerial() const;
std::string GetLocation() const;
bool IsResizable() const;
unsigned int GetLEDCount() const;
unsigned int GetMinLEDCount() const;
unsigned int GetMaxLEDCount() const;
unsigned int GetMatrixWidth() const;
unsigned int GetMatrixHeight() const;
const std::vector<unsigned int>& GetMatrixMap() const;
private:
bool QueryInfo();
void ConfigureOutput();
void SetMatrixCell(unsigned int y, unsigned int x, unsigned int& led_index);
bool WriteFrame(const std::vector<RGBColor>& colors, unsigned int count);
std::string port_name;
std::string model_name;
std::string model_id;
std::string serial_id;
bool port_open;
bool present;
bool resizable;
unsigned int leds_count;
unsigned int leds_min;
unsigned int leds_max;
unsigned int matrix_width;
unsigned int matrix_height;
std::vector<unsigned int> matrix_map;
serial_port serial_port_interface;
std::mutex device_mutex;
};

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------*\
| SkydimoSerialControllerDetect.cpp |
| |
| Detector for Skydimo serial devices |
| |
| Bartholomew Ho <qiangqiang101@hotmail.com> 30 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <vector>
#include "DetectionManager.h"
#include "LogManager.h"
#include "RGBController_SkydimoSerial.h"
#include "SkydimoSerialController.h"
#include "find_usb_serial_port.h"
/*---------------------------------------------------------*\
| Skydimo serial vendor and product IDs |
\*---------------------------------------------------------*/
#define SKYDIMO_SERIAL_VID 0x1A86
#define SKYDIMO_SERIAL_PID 0x7523
DetectedControllers DetectSkydimoSerialControllers()
{
DetectedControllers detected_controllers;
std::vector<std::string> ports = find_usb_serial_port(SKYDIMO_SERIAL_VID, SKYDIMO_SERIAL_PID);
for(unsigned int port_idx = 0; port_idx < ports.size(); port_idx++)
{
SkydimoSerialController* controller = new SkydimoSerialController(ports[port_idx]);
if(controller->IsPresent())
{
RGBController_SkydimoSerial* rgb_controller = new RGBController_SkydimoSerial(controller);
detected_controllers.push_back(rgb_controller);
LOG_INFO("[SkydimoSerialControllerDetect] Detected %s at %s",
controller->GetName().c_str(), ports[port_idx].c_str());
}
else
{
delete controller;
}
}
return(detected_controllers);
}
REGISTER_DETECTOR("Skydimo Serial", DetectSkydimoSerialControllers);
/*---------------------------------------------------------------------------------------------------------*\
| Entries for dynamic UDEV rules |
| |
| DUMMY_DEVICE_DETECTOR("Skydimo Serial", DetectSkydimoSerialControllers, 0x1A86, 0x7523 ) |
\*---------------------------------------------------------------------------------------------------------*/