mirror of
https://github.com/CalcProgrammer1/OpenRGB.git
synced 2026-04-08 16:07:38 -04:00
Robobloq Monitor Light Strip support
This commit is contained in:
committed by
Adam Honse
parent
de3482c791
commit
5cfd064da1
@@ -0,0 +1,224 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| RGBController_RobobloqLightStrip.cpp |
|
||||
| |
|
||||
| Detector for Robobloq Monitor Light Strip |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include "RGBController_RobobloqLightStrip.h"
|
||||
#include "RobobloqLightStripController.h"
|
||||
#include <LogManager.h>
|
||||
|
||||
/**--------------------------------------------------------------------*\
|
||||
@name Robobloq Monitor Light Strip
|
||||
@category LEDStrip
|
||||
@type USB
|
||||
@save :robot:
|
||||
@direct :white_check_mark:
|
||||
@effects :white_check_mark:
|
||||
@detectors DetectRobobloqLightStripController
|
||||
@comment
|
||||
\*---------------------------------------------------------------------*/
|
||||
|
||||
RGBController_RobobloqLightStrip::RGBController_RobobloqLightStrip(RobobloqLightStripController* controller_ptr)
|
||||
{
|
||||
controller = controller_ptr;
|
||||
|
||||
name = controller->GetDeviceName();
|
||||
vendor = "Robobloq";
|
||||
description = "Robobloq Monitor Light Strip (" + std::to_string(controller->GetPhysicalSizeInInches()) + "\")";
|
||||
type = DEVICE_TYPE_LEDSTRIP;
|
||||
location = controller->GetDeviceLocation();
|
||||
serial = controller->GetSerialString();
|
||||
version = controller->GetFirmwareVersion();
|
||||
|
||||
mode Static;
|
||||
Static.name = "Static";
|
||||
Static.value = ROBOBLOQ_MODE_STATIC;
|
||||
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
|
||||
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
|
||||
Static.brightness_min = 1;
|
||||
Static.brightness_max = 255;
|
||||
Static.brightness = 255;
|
||||
Static.colors_min = 1;
|
||||
Static.colors_max = 1;
|
||||
Static.colors.resize(1);
|
||||
modes.push_back(Static);
|
||||
|
||||
mode Direct;
|
||||
Direct.name = "Direct";
|
||||
Direct.value = ROBOBLOQ_MODE_DIRECT;
|
||||
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
|
||||
Direct.color_mode = MODE_COLORS_PER_LED;
|
||||
Direct.brightness_min = 1;
|
||||
Direct.brightness_max = 255;
|
||||
Direct.brightness = 255;
|
||||
modes.push_back(Direct);
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Add dynamic modes |
|
||||
\*-----------------------------------------------------*/
|
||||
struct DynamicMode
|
||||
{
|
||||
const char* name;
|
||||
int value;
|
||||
};
|
||||
|
||||
DynamicMode dynamic_modes[] =
|
||||
{
|
||||
{ "Rainbow Wave", ROBOBLOQ_MODE_DYNAMIC_RAINBOW },
|
||||
{ "Breathing", ROBOBLOQ_MODE_DYNAMIC_BREATHING },
|
||||
{ "Twist", ROBOBLOQ_MODE_DYNAMIC_TWIST },
|
||||
{ "Beat", ROBOBLOQ_MODE_DYNAMIC_BEAT },
|
||||
{ "Twirl", ROBOBLOQ_MODE_DYNAMIC_TWIRL },
|
||||
{ "Lemon", ROBOBLOQ_MODE_DYNAMIC_LEMON },
|
||||
{ "Electric", ROBOBLOQ_MODE_DYNAMIC_ELECTRIC },
|
||||
};
|
||||
|
||||
for(unsigned int i = 0; i < (sizeof(dynamic_modes) / sizeof(DynamicMode)); i++)
|
||||
{
|
||||
mode new_mode;
|
||||
new_mode.name = dynamic_modes[i].name;
|
||||
new_mode.value = dynamic_modes[i].value;
|
||||
new_mode.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
|
||||
new_mode.color_mode = MODE_COLORS_NONE;
|
||||
new_mode.speed_min = 0;
|
||||
new_mode.speed_max = 100;
|
||||
new_mode.speed = 50;
|
||||
new_mode.brightness_min = 1;
|
||||
new_mode.brightness_max = 255;
|
||||
new_mode.brightness = 255;
|
||||
modes.push_back(new_mode);
|
||||
}
|
||||
|
||||
mode Off;
|
||||
Off.name = "Off";
|
||||
Off.value = ROBOBLOQ_MODE_OFF;
|
||||
Off.flags = 0;
|
||||
Off.color_mode = MODE_COLORS_NONE;
|
||||
modes.push_back(Off);
|
||||
|
||||
SetupZones();
|
||||
};
|
||||
|
||||
RGBController_RobobloqLightStrip::~RGBController_RobobloqLightStrip()
|
||||
{
|
||||
delete controller;
|
||||
}
|
||||
|
||||
void RGBController_RobobloqLightStrip::SetupZones()
|
||||
{
|
||||
int led_count = controller->GetLEDCount();
|
||||
int leds_per_side = controller->GetLEDsPerSide();
|
||||
|
||||
struct Side
|
||||
{
|
||||
const char* name;
|
||||
int count;
|
||||
};
|
||||
|
||||
std::vector<Side> sides = { { "Light Strip", led_count } };
|
||||
if(leds_per_side > 0)
|
||||
{
|
||||
sides = {
|
||||
{ "Right", leds_per_side },
|
||||
{ "Top", led_count - (leds_per_side * 2) },
|
||||
{ "Left", leds_per_side },
|
||||
};
|
||||
}
|
||||
|
||||
zones.clear();
|
||||
zones.resize(sides.size());
|
||||
leds.clear();
|
||||
leds.resize(led_count);
|
||||
|
||||
for(unsigned int i = 0; i < sides.size(); i++)
|
||||
{
|
||||
zone& zone = zones[i];
|
||||
zone.name = sides[i].name;
|
||||
zone.type = ZONE_TYPE_LINEAR;
|
||||
zone.leds_count = sides[i].count;
|
||||
zone.leds_min = zone.leds_count;
|
||||
zone.leds_max = zone.leds_count;
|
||||
zone.matrix_map = NULL;
|
||||
}
|
||||
|
||||
for(int i = 0; i < led_count; i++)
|
||||
{
|
||||
led& new_led = leds[i];
|
||||
new_led.name = "LED " + std::to_string(i + 1);
|
||||
new_led.value = i;
|
||||
}
|
||||
|
||||
SetupColors();
|
||||
}
|
||||
|
||||
void RGBController_RobobloqLightStrip::ResizeZone(int /*zone*/, int /*new_size*/)
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| This device does not support resizing zones |
|
||||
\*-----------------------------------------------------*/
|
||||
}
|
||||
|
||||
void RGBController_RobobloqLightStrip::DeviceUpdateLEDs()
|
||||
{
|
||||
if(modes[active_mode].value == ROBOBLOQ_MODE_DIRECT)
|
||||
{
|
||||
controller->SetCustom(colors);
|
||||
}
|
||||
}
|
||||
|
||||
void RGBController_RobobloqLightStrip::UpdateZoneLEDs(int /*zone*/)
|
||||
{
|
||||
DeviceUpdateLEDs();
|
||||
}
|
||||
|
||||
void RGBController_RobobloqLightStrip::UpdateSingleLED(int led)
|
||||
{
|
||||
controller->SetLEDColor(led, modes[active_mode].colors[0]);
|
||||
}
|
||||
|
||||
void RGBController_RobobloqLightStrip::DeviceUpdateMode()
|
||||
{
|
||||
bool mode_changed = false;
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Cache mode to avoid repeated SetDynamicEffect calls |
|
||||
| when adjusting speed/brightness |
|
||||
\*-----------------------------------------------------*/
|
||||
if(modes[active_mode].value != cur_mode)
|
||||
{
|
||||
cur_mode = modes[active_mode].value;
|
||||
mode_changed = true;
|
||||
}
|
||||
|
||||
if(cur_mode == ROBOBLOQ_MODE_OFF)
|
||||
{
|
||||
controller->TurnOff();
|
||||
}
|
||||
else if(cur_mode == ROBOBLOQ_MODE_DIRECT)
|
||||
{
|
||||
controller->SetBrightness(modes[active_mode].brightness);
|
||||
}
|
||||
else if(cur_mode == ROBOBLOQ_MODE_STATIC)
|
||||
{
|
||||
controller->SetColor(modes[active_mode].colors[0]);
|
||||
controller->SetBrightness(modes[active_mode].brightness);
|
||||
}
|
||||
else if(ROBOBLOQ_IS_DYNAMIC_EFFECT(cur_mode))
|
||||
{
|
||||
if(mode_changed)
|
||||
{
|
||||
controller->SetDynamicEffect(cur_mode);
|
||||
}
|
||||
|
||||
controller->SetBrightness(modes[active_mode].brightness);
|
||||
controller->SetDynamicSpeed(modes[active_mode].speed);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR("[Robobloq] Requested mode (%02x) is not supported", cur_mode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| RGBController_RobobloqLightStrip.h |
|
||||
| |
|
||||
| Detector for Robobloq Monitor Light Strip |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RGBController.h"
|
||||
#include "RobobloqLightStripController.h"
|
||||
|
||||
class RGBController_RobobloqLightStrip : public RGBController
|
||||
{
|
||||
public:
|
||||
RGBController_RobobloqLightStrip(RobobloqLightStripController* controller_ptr);
|
||||
~RGBController_RobobloqLightStrip();
|
||||
|
||||
void SetupZones();
|
||||
|
||||
void ResizeZone(int zone, int new_size);
|
||||
|
||||
void DeviceUpdateLEDs();
|
||||
void UpdateZoneLEDs(int zone);
|
||||
void UpdateSingleLED(int led);
|
||||
|
||||
void DeviceUpdateMode();
|
||||
|
||||
private:
|
||||
/*-----------------------------------------------------*\
|
||||
| Last mode set via this controller |
|
||||
\*-----------------------------------------------------*/
|
||||
int cur_mode = -1;
|
||||
RobobloqLightStripController* controller;
|
||||
};
|
||||
@@ -0,0 +1,421 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| RobobloqLightStripController.cpp |
|
||||
| |
|
||||
| Detector for Robobloq Monitor Light Strip |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <numeric>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include "LogManager.h"
|
||||
#include "RobobloqLightStripController.h"
|
||||
#include "RobobloqRangeMerger.h"
|
||||
#include "RGBController.h"
|
||||
#include <Colors.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
RobobloqLightStripController::RobobloqLightStripController(hid_device* dev_handle, const char* path, std::string dev_name)
|
||||
{
|
||||
dev = dev_handle;
|
||||
location = path;
|
||||
name = dev_name;
|
||||
packet_index = 0x02;
|
||||
led_count = 0;
|
||||
|
||||
RequestDeviceInfo();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
RobobloqLightStripController::~RobobloqLightStripController()
|
||||
{
|
||||
hid_close(dev);
|
||||
}
|
||||
|
||||
std::string RobobloqLightStripController::GetDeviceLocation()
|
||||
{
|
||||
return location;
|
||||
}
|
||||
|
||||
std::string RobobloqLightStripController::GetDeviceName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string RobobloqLightStripController::GetSerialString()
|
||||
{
|
||||
return uuid;
|
||||
}
|
||||
|
||||
std::string RobobloqLightStripController::GetFirmwareVersion()
|
||||
{
|
||||
return firmware_version;
|
||||
}
|
||||
|
||||
int RobobloqLightStripController::GetLEDCount()
|
||||
{
|
||||
return led_count;
|
||||
}
|
||||
|
||||
int RobobloqLightStripController::GetLEDsPerSide()
|
||||
{
|
||||
switch(physical_size)
|
||||
{
|
||||
case 34:
|
||||
return 15;
|
||||
default:
|
||||
return 0; /* unknown */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int RobobloqLightStripController::GetPhysicalSizeInInches()
|
||||
{
|
||||
return physical_size;
|
||||
}
|
||||
|
||||
void RobobloqLightStripController::Initialize()
|
||||
{
|
||||
RequestDeviceInfo();
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| This tells the device (permanently) not to try to use |
|
||||
| its keyboard to open a URL to the driver download |
|
||||
| page. Yes, it really does that. |
|
||||
\*-----------------------------------------------------*/
|
||||
SendPacket({ROBOBLOQ_CMD_SET_OPEN_URL, 0x00});
|
||||
|
||||
SetBrightness(0xF9);
|
||||
SetDynamicSpeed(0x32);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Set the entire light strip to the specified color. |
|
||||
\*---------------------------------------------------------*/
|
||||
void RobobloqLightStripController::SetColor(RGBColor c)
|
||||
{
|
||||
std::vector<unsigned char> payload = {
|
||||
0x86,
|
||||
0x01,
|
||||
(unsigned char)RGBGetRValue(c),
|
||||
(unsigned char)RGBGetGValue(c),
|
||||
(unsigned char)RGBGetBValue(c),
|
||||
led_count,
|
||||
/*-------------------------------------------------*\
|
||||
| Add dummy range (matches original application) |
|
||||
\*-------------------------------------------------*/
|
||||
(unsigned char)(led_count + 1),
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xFE,
|
||||
};
|
||||
SendPacket(payload, false);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Set a single LED to the specified color. LED is 0-indexed |
|
||||
\*---------------------------------------------------------*/
|
||||
void RobobloqLightStripController::SetLEDColor(int led, RGBColor c)
|
||||
{
|
||||
std::vector<unsigned char> payload = {
|
||||
0x86,
|
||||
(unsigned char)(led + 1),
|
||||
(unsigned char)RGBGetRValue(c),
|
||||
(unsigned char)RGBGetGValue(c),
|
||||
(unsigned char)RGBGetBValue(c),
|
||||
(unsigned char)(led + 1),
|
||||
};
|
||||
SendPacket(payload, false);
|
||||
}
|
||||
|
||||
void RobobloqLightStripController::SetColorRanges(const std::vector<unsigned char>& ranges)
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| 6 bytes overhead (header + command + checksum) plus |
|
||||
| ranges must fit in 64 bytes |
|
||||
\*-----------------------------------------------------*/
|
||||
if((ranges.size() + 6) > 64)
|
||||
{
|
||||
LOG_ERROR("[Robobloq] SetColorRanges: Too many ranges (%d) for packet size", (int)ranges.size());
|
||||
return;
|
||||
}
|
||||
std::vector<unsigned char> payload = { 0x86 };
|
||||
payload.insert(payload.end(), ranges.begin(), ranges.end());
|
||||
SendPacket(payload, false);
|
||||
}
|
||||
|
||||
void RobobloqLightStripController::SetBrightness(unsigned char brightness)
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| 0 value is interpreted as max (255) |
|
||||
\*-----------------------------------------------------*/
|
||||
if(brightness == 0)
|
||||
{
|
||||
brightness = 1;
|
||||
}
|
||||
|
||||
SendPacket({ROBOBLOQ_CMD_SET_BRIGHTNESS, brightness});
|
||||
}
|
||||
|
||||
void RobobloqLightStripController::SetDynamicEffect(unsigned char effect)
|
||||
{
|
||||
SendPacket({ROBOBLOQ_CMD_SET_EFFECT, ROBOBLOQ_EFFECT_DYNAMIC, effect});
|
||||
}
|
||||
|
||||
void RobobloqLightStripController::SetDynamicSpeed(unsigned char speed)
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| Device expects 0x00 = fast, 0x64 = slow |
|
||||
\*-----------------------------------------------------*/
|
||||
speed = ROBOBLOQ_DYNAMIC_SPEED_MAX - speed;
|
||||
|
||||
SendPacket({ROBOBLOQ_CMD_SET_DYNAMIC_SPEED, speed});
|
||||
}
|
||||
|
||||
void RobobloqLightStripController::TurnOff()
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| The device doesn't really turn off: we send a command |
|
||||
| to disable any device-side animations, then set all |
|
||||
| LEDs to black. |
|
||||
\*-----------------------------------------------------*/
|
||||
SendPacket({ROBOBLOQ_CMD_TURN_OFF});
|
||||
SetColor(COLOR_BLACK);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Update all LED colors at once. |
|
||||
| |
|
||||
| The official app reduces 71 LED values to 34 distinct |
|
||||
| ranges before sending them to the device, so we do the |
|
||||
| same. SendSyncScreen is capable of sending any number of |
|
||||
| ranges (e.g 71 - 1 per pixel) but this has not been |
|
||||
| properly tested. |
|
||||
\*---------------------------------------------------------*/
|
||||
void RobobloqLightStripController::SetCustom(const std::vector<RGBColor>& colors)
|
||||
{
|
||||
int num_leds = (int)colors.size();
|
||||
if(num_leds != this->led_count)
|
||||
{
|
||||
LOG_ERROR("[Robobloq] SetCustom: Number of colors (%d) does not match LED count (%d), rejecting", num_leds, this->led_count);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> color_bytes = MergeRobobloqRanges(colors, ROBOBLOQ_TUPLE_COUNT);
|
||||
|
||||
SendSyncScreen(color_bytes);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Internal method to send color ranges to the device. |
|
||||
\*---------------------------------------------------------*/
|
||||
void RobobloqLightStripController::SendSyncScreen(const std::vector<unsigned char>& color_bytes)
|
||||
{
|
||||
if(color_bytes.size() % 5 != 0)
|
||||
{
|
||||
LOG_ERROR("[Robobloq] SendSyncScreen: color_bytes size (%d) is not a multiple of 5, rejecting", (int)color_bytes.size());
|
||||
return;
|
||||
}
|
||||
|
||||
SendMultiPacket(ROBOBLOQ_CMD_SET_SYNC_SCREEN, color_bytes);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Sends a multi-packet command to the device. |
|
||||
| |
|
||||
| This allows payloads >64 bytes, e.g. SendSyncScreen |
|
||||
\*---------------------------------------------------------*/
|
||||
void RobobloqLightStripController::SendMultiPacket(unsigned char command, const std::vector<unsigned char>& payload)
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| Allow for 6-byte header and trailing checksum |
|
||||
\*-----------------------------------------------------*/
|
||||
unsigned short len = (unsigned short)(payload.size() + 7);
|
||||
|
||||
std::vector<unsigned char> data = {0x53, 0x43, (unsigned char)(len >> 8), (unsigned char)(len & 0xFF), packet_index, command};
|
||||
data.insert(data.end(), payload.begin(), payload.end());
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Calculate checksum |
|
||||
\*-----------------------------------------------------*/
|
||||
unsigned int sum = 0;
|
||||
for(std::size_t i = 0; i < data.size(); i++)
|
||||
{
|
||||
sum += data[i];
|
||||
}
|
||||
data.push_back(sum & 0xFF);
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Pad to a multiple of 64 bytes |
|
||||
\*-----------------------------------------------------*/
|
||||
size_t remainder = data.size() % 64;
|
||||
if(remainder > 0)
|
||||
{
|
||||
data.resize(data.size() + 64 - remainder, 0x00);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Send in chunks of 64 bytes |
|
||||
\*-----------------------------------------------------*/
|
||||
for(size_t i = 0; i < data.size(); i += 64)
|
||||
{
|
||||
WriteReport(&data[i]);
|
||||
|
||||
/*-------------------------------------------------*\
|
||||
| Microsleep to avoid overloading the device |
|
||||
\*-------------------------------------------------*/
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
|
||||
IncPacketIndex();
|
||||
}
|
||||
|
||||
void RobobloqLightStripController::IncPacketIndex()
|
||||
{
|
||||
packet_index = (packet_index + 1) & 0xFF;
|
||||
}
|
||||
|
||||
void RobobloqLightStripController::WriteReport(const unsigned char* data /* 64 bytes*/)
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| Add report ID |
|
||||
\*-----------------------------------------------------*/
|
||||
std::vector<unsigned char> report(65);
|
||||
report[0] = 0x00;
|
||||
memcpy(&report[1], data, 64);
|
||||
|
||||
hid_write(dev, report.data(), report.size());
|
||||
}
|
||||
|
||||
void RobobloqLightStripController::SendPacket(const std::vector<unsigned char>& command, bool flush)
|
||||
{
|
||||
unsigned char length = (unsigned char)(command.size() + 5);
|
||||
|
||||
if(length > 64)
|
||||
{
|
||||
LOG_ERROR("[Robobloq] SendPacket: command size (%d) is too large, rejecting", (int)command.size());
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> packet = {0x52, 0x42, length, packet_index};
|
||||
packet.insert(packet.end(), command.begin(), command.end());
|
||||
|
||||
unsigned int csum = 0;
|
||||
for(std::size_t i = 0; i < packet.size(); i++)
|
||||
{
|
||||
csum += packet[i];
|
||||
}
|
||||
packet.push_back(csum & 0xFF);
|
||||
|
||||
packet.resize(64, 0x00);
|
||||
|
||||
WriteReport(packet.data());
|
||||
IncPacketIndex();
|
||||
|
||||
if(flush)
|
||||
{
|
||||
/*-------------------------------------------------*\
|
||||
| Flush away any awaiting IN packets |
|
||||
\*-------------------------------------------------*/
|
||||
unsigned char buf[64];
|
||||
|
||||
int res = 1;
|
||||
while(res > 0)
|
||||
{
|
||||
res = hid_read_timeout(dev, buf, 64, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned char> RobobloqLightStripController::SendPacketWithReply(const std::vector<unsigned char>& command)
|
||||
{
|
||||
unsigned char expected_id = packet_index;
|
||||
LOG_DEBUG("[Robobloq] WithReply: sending command %02x, expecting reply for packet ID %02x", command[0], expected_id);
|
||||
SendPacket(command, false);
|
||||
|
||||
int tries = 3;
|
||||
while(tries--)
|
||||
{
|
||||
unsigned char buf[64];
|
||||
int res = 1;
|
||||
while(res > 0)
|
||||
{
|
||||
std::this_thread::sleep_for(10ms);
|
||||
res = hid_read_timeout(dev, buf, 64, 1000);
|
||||
LOG_DEBUG("[Robobloq] WithReply: read call returned %d packet ID %02x", res, buf[3]);
|
||||
|
||||
if(buf[3] == expected_id)
|
||||
{
|
||||
return std::vector<unsigned char>(buf, buf + res);
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------*\
|
||||
| Sometimes device gets stuck and we need to send a |
|
||||
| new command before we can receive the previous |
|
||||
| command's reply, so send a no-op |
|
||||
\*-------------------------------------------------*/
|
||||
LOG_DEBUG("[Robobloq] No matching reply, sending no-op to try to unstick device");
|
||||
SendPacket({ROBOBLOQ_CMD_SET_OPEN_URL, 0x00}, false);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| We weren't able to get a reply for this message |
|
||||
\*-----------------------------------------------------*/
|
||||
return {};
|
||||
}
|
||||
|
||||
/***
|
||||
* Asks device for information and update our attributes. When sent 0x82, the device
|
||||
* returns information in the following form:
|
||||
*
|
||||
* header ID uuid v1.8.2
|
||||
* /------------\ /======\ /-----------------------\ /------\
|
||||
* 52 42 19 02 82 00 05 01 | 22 01 00 47 cd ab c5 74 | 25 bc b7 dc e5 01 08 02
|
||||
* | ? ? | ?
|
||||
* | led count (71)
|
||||
* display size (34")
|
||||
* v1.8.2 is device fw version
|
||||
*/
|
||||
bool RobobloqLightStripController::RequestDeviceInfo()
|
||||
{
|
||||
std::vector<unsigned char> data = SendPacketWithReply({ROBOBLOQ_CMD_READ_DEVICE_INFO});
|
||||
if(data.size() < 24)
|
||||
{
|
||||
LOG_DEBUG("[Robobloq] Device Info Data too small! LED count -> 0. Non-Direct modes will work");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->physical_size = data[8];
|
||||
this->led_count = data[11];
|
||||
|
||||
char id_buf[7];
|
||||
snprintf(id_buf, sizeof(id_buf), "%02x%02x%02x", data[5], data[6], data[7]);
|
||||
this->id = std::string(id_buf);
|
||||
|
||||
char uuid_buf[17];
|
||||
for(int i = 0; i < 8; i++)
|
||||
{
|
||||
sprintf(uuid_buf + (i * 2), "%02x", data[12 + i]);
|
||||
}
|
||||
this->uuid = std::string(uuid_buf);
|
||||
|
||||
char fw_buf[16];
|
||||
snprintf(fw_buf, sizeof(fw_buf), "%d.%d.%d", data[21], data[22], data[23]);
|
||||
this->firmware_version = std::string(fw_buf);
|
||||
|
||||
LOG_DEBUG("[Robobloq] Got device uuid: %s, fw: %s, size: %d\", leds: %d",
|
||||
this->uuid.c_str(), this->firmware_version.c_str(),
|
||||
this->physical_size, this->led_count);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| RobobloqLightStripController.h |
|
||||
| |
|
||||
| Detector for Robobloq Monitor Light Strips |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <hidapi.h>
|
||||
#include "RGBController.h"
|
||||
|
||||
/*-----------------------------------------*\
|
||||
| Lighting modes |
|
||||
\*-----------------------------------------*/
|
||||
enum
|
||||
{
|
||||
/*-------------------------------------*\
|
||||
| Dynamic |
|
||||
\*-------------------------------------*/
|
||||
ROBOBLOQ_MODE_DYNAMIC_RAINBOW = 0x00,
|
||||
ROBOBLOQ_MODE_DYNAMIC_BREATHING = 0x01,
|
||||
ROBOBLOQ_MODE_DYNAMIC_TWIST = 0x02,
|
||||
ROBOBLOQ_MODE_DYNAMIC_BEAT = 0x03,
|
||||
ROBOBLOQ_MODE_DYNAMIC_TWIRL = 0x04,
|
||||
ROBOBLOQ_MODE_DYNAMIC_LEMON = 0x05,
|
||||
ROBOBLOQ_MODE_DYNAMIC_ELECTRIC = 0x06,
|
||||
/*-------------------------------------*\
|
||||
| Rhythm (synced to music) |
|
||||
\*-------------------------------------*/
|
||||
ROBOBLOQ_MODE_RHYTHM_CIRCLES = 0x07,
|
||||
ROBOBLOQ_MODE_RHYTHM_TWIRL = 0x08,
|
||||
ROBOBLOQ_MODE_RHYTHM_SPARKLE = 0x09,
|
||||
ROBOBLOQ_MODE_RHYTHM_BUBBLES = 0x10,
|
||||
ROBOBLOQ_MODE_RHYTHM_SPOTLIGHT = 0x11,
|
||||
ROBOBLOQ_MODE_RHYTHM_RAINBOW = 0x12,
|
||||
ROBOBLOQ_MODE_RHYTHM_BLAST = 0x13,
|
||||
/*-------------------------------------*\
|
||||
| Virtual modes |
|
||||
\*-------------------------------------*/
|
||||
ROBOBLOQ_MODE_OFF = 0xFFFD,
|
||||
ROBOBLOQ_MODE_STATIC = 0xFFFE,
|
||||
ROBOBLOQ_MODE_DIRECT = 0xFFFF,
|
||||
};
|
||||
|
||||
#define ROBOBLOQ_IS_DYNAMIC_EFFECT(x) ((x) >= ROBOBLOQ_MODE_DYNAMIC_RAINBOW && (x) <= ROBOBLOQ_MODE_DYNAMIC_ELECTRIC)
|
||||
#define ROBOBLOQ_IS_RHYTHM_EFFECT(x) ((x) >= ROBOBLOQ_MODE_RHYTHM_CIRCLES && (x) <= ROBOBLOQ_MODE_RHYTHM_BLAST)
|
||||
|
||||
/*-----------------------------------------*\
|
||||
| Commands |
|
||||
\*-----------------------------------------*/
|
||||
enum
|
||||
{
|
||||
ROBOBLOQ_CMD_SET_SYNC_SCREEN = 0x80,
|
||||
ROBOBLOQ_CMD_READ_DEVICE_INFO = 0x82,
|
||||
ROBOBLOQ_CMD_SET_EFFECT = 0x85,
|
||||
ROBOBLOQ_CMD_SET_COLOR = 0x86,
|
||||
ROBOBLOQ_CMD_SET_BRIGHTNESS = 0x87,
|
||||
ROBOBLOQ_CMD_SET_DYNAMIC_SPEED = 0x8A,
|
||||
ROBOBLOQ_CMD_SET_OPEN_URL = 0x93,
|
||||
ROBOBLOQ_CMD_TURN_OFF = 0x97,
|
||||
};
|
||||
|
||||
/*-----------------------------------------*\
|
||||
| Effect categories |
|
||||
\*-----------------------------------------*/
|
||||
enum
|
||||
{
|
||||
ROBOBLOQ_EFFECT_DYNAMIC = 0x02,
|
||||
ROBOBLOQ_EFFECT_RHYTHM = 0x03,
|
||||
};
|
||||
|
||||
#define ROBOBLOQ_DYNAMIC_SPEED_MAX 0x64
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Number of (start, end, R, G, B) tuples that will be sent |
|
||||
| in a SetCustom call |
|
||||
\*---------------------------------------------------------*/
|
||||
#define ROBOBLOQ_TUPLE_COUNT 34
|
||||
|
||||
class RobobloqLightStripController
|
||||
{
|
||||
public:
|
||||
RobobloqLightStripController(hid_device* dev_handle, const char* path, std::string dev_name);
|
||||
~RobobloqLightStripController();
|
||||
|
||||
std::string GetDeviceLocation();
|
||||
std::string GetDeviceName();
|
||||
std::string GetSerialString();
|
||||
std::string GetFirmwareVersion();
|
||||
int GetLEDCount();
|
||||
int GetLEDsPerSide();
|
||||
int GetPhysicalSizeInInches();
|
||||
|
||||
void Initialize();
|
||||
void SetColor(RGBColor c);
|
||||
void SetLEDColor(int led, RGBColor c);
|
||||
void SetColorRanges(const std::vector<unsigned char>& ranges);
|
||||
void SetBrightness(unsigned char brightness);
|
||||
void SetDynamicEffect(unsigned char effect);
|
||||
void SetCustom(const std::vector<RGBColor>& colors);
|
||||
void SetDynamicSpeed(unsigned char speed);
|
||||
void TurnOff();
|
||||
|
||||
|
||||
private:
|
||||
hid_device* dev;
|
||||
std::string name;
|
||||
std::string location;
|
||||
std::string id;
|
||||
std::string uuid;
|
||||
std::string firmware_version;
|
||||
unsigned char led_count;
|
||||
unsigned char physical_size;
|
||||
unsigned char packet_index;
|
||||
|
||||
void SendPacket(const std::vector<unsigned char>& command, bool flush = true);
|
||||
std::vector<unsigned char> SendPacketWithReply(const std::vector<unsigned char>& command);
|
||||
void SendMultiPacket(unsigned char command, const std::vector<unsigned char>& payload);
|
||||
bool RequestDeviceInfo();
|
||||
void SendSyncScreen(const std::vector<unsigned char>& color_bytes);
|
||||
void IncPacketIndex();
|
||||
void WriteReport(const unsigned char* data);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| RobobloqLightStripControllerDetect.cpp |
|
||||
| |
|
||||
| Detector for Robobloq RGB Light Strips |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include "Detector.h"
|
||||
#include "RobobloqLightStripController.h"
|
||||
#include "RGBController_RobobloqLightStrip.h"
|
||||
|
||||
#define ROBOBLOQ_USB_VID 0x1A86
|
||||
|
||||
/*----------------------------------------------------------*\
|
||||
| |
|
||||
| DetectRobobloqLightStripController |
|
||||
| |
|
||||
| Detect Robobloq RGB Light Strips |
|
||||
| |
|
||||
\*----------------------------------------------------------*/
|
||||
|
||||
void DetectRobobloqLightStripController
|
||||
(
|
||||
hid_device_info* info,
|
||||
const std::string& name
|
||||
)
|
||||
{
|
||||
hid_device* dev = hid_open_path(info->path);
|
||||
if(dev != nullptr)
|
||||
{
|
||||
RobobloqLightStripController* controller = new RobobloqLightStripController(dev, info->path, name);
|
||||
RGBController_RobobloqLightStrip* rgb_controller = new RGBController_RobobloqLightStrip(controller);
|
||||
ResourceManager::get()->RegisterRGBController(rgb_controller);
|
||||
}
|
||||
}
|
||||
|
||||
REGISTER_HID_DETECTOR_PU("Robobloq Monitor Light Strip", DetectRobobloqLightStripController, ROBOBLOQ_USB_VID, 0xFE07, 0xFF00, 0x01);
|
||||
155
Controllers/RobobloqLightStripController/RobobloqRangeMerger.cpp
Normal file
155
Controllers/RobobloqLightStripController/RobobloqRangeMerger.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| RobobloqRangeMerger.cpp |
|
||||
| |
|
||||
| Helper for merging LED ranges for Robobloq |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include "RobobloqRangeMerger.h"
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <LogManager.h>
|
||||
|
||||
|
||||
/***
|
||||
* The official application does not send a full set of LED values to the device (i.e. 71 in the
|
||||
* 34" case) but rather compresses the 71 RGB values down to exactly 34 ranges. It seems to use
|
||||
* pre-configured ranges, but we can do better by calculating the ranges to use that create the
|
||||
* least error, preserving single pixel detail.
|
||||
*
|
||||
* We use a greedy merge algorithm: initially define a 1-length range for each pixel. Try to merge
|
||||
* any 2 adjacent ranges and pick the merge that creates the least difference. Repeat until we
|
||||
* have 34.
|
||||
*/
|
||||
std::vector<unsigned char> MergeRobobloqRanges(const std::vector<RGBColor>& colors, int tuple_count)
|
||||
{
|
||||
if(tuple_count == 0)
|
||||
{
|
||||
LOG_ERROR("[Robobloq] MergeRobobloqRanges called with tuple_count == 0");
|
||||
return {};
|
||||
}
|
||||
|
||||
struct LEDRange
|
||||
{
|
||||
int start; /* Start LED (1-indexed) */
|
||||
int end; /* End LED */
|
||||
int n; /* Number of LEDs in range */
|
||||
double sum_r; /* Sum of R values */
|
||||
double sum_g;
|
||||
double sum_b;
|
||||
double term; /* = sum_r^2 + sum_g^2 + sum_b^2 / n */
|
||||
};
|
||||
|
||||
int num_leds = (int)colors.size();
|
||||
std::vector<LEDRange> ranges;
|
||||
ranges.reserve(num_leds);
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| 1. Initialize ranges (one per pixel) |
|
||||
\*-----------------------------------------------------*/
|
||||
for(int i = 0; i < num_leds; i++)
|
||||
{
|
||||
LEDRange r;
|
||||
r.start = i + 1; /* 1-based index */
|
||||
r.end = i + 1;
|
||||
r.n = 1;
|
||||
r.sum_r = RGBGetRValue(colors[i]);
|
||||
r.sum_g = RGBGetGValue(colors[i]);
|
||||
r.sum_b = RGBGetBValue(colors[i]);
|
||||
r.term = (r.sum_r * r.sum_r + r.sum_g * r.sum_g + r.sum_b * r.sum_b);
|
||||
ranges.push_back(r);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| 2. Merge until we have tuple_count tuples |
|
||||
\*-----------------------------------------------------*/
|
||||
while((int)ranges.size() > tuple_count)
|
||||
{
|
||||
double best_delta = std::numeric_limits<double>::max();
|
||||
int best_idx = -1;
|
||||
double best_merged_term = 0;
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Find best adjacent pair to merge |
|
||||
\*-----------------------------------------------------*/
|
||||
|
||||
/***
|
||||
* Minimise Sum of Squared Errors (SSE) = sum(pixel - average)^2
|
||||
* = sum(pixel^2) - sum(n*average^2)
|
||||
*
|
||||
* As sum(pixel^2) is constant, we need to maximise sum(n*average^2).
|
||||
*
|
||||
* Since average = sum / n:
|
||||
* n * average^2 = n * (sum / n)^2 = n * (sum^2 / n^2) = sum^2 / n
|
||||
*
|
||||
* We need to maximise sum(sum_k^2/n_k) for all ranges k. We cache the
|
||||
* sum^2/n value as 'term'.
|
||||
**/
|
||||
for(size_t i = 0; i < ranges.size() - 1; i++)
|
||||
{
|
||||
const LEDRange& r1 = ranges[i];
|
||||
const LEDRange& r2 = ranges[i+1];
|
||||
|
||||
double sum_r = r1.sum_r + r2.sum_r;
|
||||
double sum_g = r1.sum_g + r2.sum_g;
|
||||
double sum_b = r1.sum_b + r2.sum_b;
|
||||
int n = r1.n + r2.n;
|
||||
|
||||
double term_merged = (sum_r * sum_r + sum_g * sum_g + sum_b * sum_b) / n;
|
||||
double delta = r1.term + r2.term - term_merged;
|
||||
|
||||
if(delta < best_delta)
|
||||
{
|
||||
best_delta = delta;
|
||||
best_idx = (int)i;
|
||||
best_merged_term = term_merged;
|
||||
}
|
||||
}
|
||||
|
||||
if(best_idx != -1)
|
||||
{
|
||||
/*---------------------------------------------*\
|
||||
| Merge best_idx and best_idx+1 |
|
||||
\*---------------------------------------------*/
|
||||
LEDRange& r_left = ranges[best_idx];
|
||||
const LEDRange& r_right = ranges[best_idx+1];
|
||||
|
||||
r_left.end = r_right.end;
|
||||
r_left.n += r_right.n;
|
||||
r_left.sum_r += r_right.sum_r;
|
||||
r_left.sum_g += r_right.sum_g;
|
||||
r_left.sum_b += r_right.sum_b;
|
||||
r_left.term = best_merged_term;
|
||||
|
||||
ranges.erase(ranges.begin() + best_idx + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*---------------------------------------------*\
|
||||
| No merge possible |
|
||||
\*---------------------------------------------*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned char> color_bytes;
|
||||
color_bytes.reserve(tuple_count * 5);
|
||||
|
||||
for(size_t i = 0; i < ranges.size(); i++)
|
||||
{
|
||||
const LEDRange& r = ranges[i];
|
||||
unsigned char avg_r = (unsigned char)std::round(r.sum_r / r.n);
|
||||
unsigned char avg_g = (unsigned char)std::round(r.sum_g / r.n);
|
||||
unsigned char avg_b = (unsigned char)std::round(r.sum_b / r.n);
|
||||
|
||||
color_bytes.push_back((unsigned char)r.start);
|
||||
color_bytes.push_back(avg_r);
|
||||
color_bytes.push_back(avg_g);
|
||||
color_bytes.push_back(avg_b);
|
||||
color_bytes.push_back((unsigned char)r.end);
|
||||
}
|
||||
|
||||
return color_bytes;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| RobobloqRangeMerger.h |
|
||||
| |
|
||||
| Helper for merging LED ranges for Robobloq |
|
||||
| |
|
||||
| This file is part of the OpenRGB project |
|
||||
| SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "RGBController.h"
|
||||
|
||||
std::vector<unsigned char> MergeRobobloqRanges(const std::vector<RGBColor>& colors, int tuple_count);
|
||||
Reference in New Issue
Block a user