From 94042375a7b78cd2eb76418590b70096a0d7a362 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Fri, 26 Jun 2026 01:16:18 +0000 Subject: [PATCH] Add support for SayoDevice E1 --- .../RGBController_SayoDevice.cpp | 169 ++++++++++++++++++ .../RGBController_SayoDevice.h | 36 ++++ .../SayoDeviceController.cpp | 128 +++++++++++++ .../SayoDeviceController.h | 103 +++++++++++ .../SayoDeviceControllerDetect.cpp | 42 +++++ 5 files changed, 478 insertions(+) create mode 100644 Controllers/SayoDeviceController/RGBController_SayoDevice.cpp create mode 100644 Controllers/SayoDeviceController/RGBController_SayoDevice.h create mode 100644 Controllers/SayoDeviceController/SayoDeviceController.cpp create mode 100644 Controllers/SayoDeviceController/SayoDeviceController.h create mode 100644 Controllers/SayoDeviceController/SayoDeviceControllerDetect.cpp diff --git a/Controllers/SayoDeviceController/RGBController_SayoDevice.cpp b/Controllers/SayoDeviceController/RGBController_SayoDevice.cpp new file mode 100644 index 000000000..216e1d7ad --- /dev/null +++ b/Controllers/SayoDeviceController/RGBController_SayoDevice.cpp @@ -0,0 +1,169 @@ +/*---------------------------------------------------------*\ +| RGBController_SayoDevice.cpp | +| | +| Controller for Sayo Devices | +| | +| Richard Harris 24 Jun 2026 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#include "RGBController_SayoDevice.h" +#include "SayoDeviceController.h" +#include "LogManager.h" + +/**--------------------------------------------------------------------*\ + @name SayoDevice E1 + @category Keyboard + @type USB + @save :white_check_mark: + @direct :white_check_mark: + @effects :white_check_mark: + @detectors DetectSayoDeviceController + @comment +\*---------------------------------------------------------------------*/ + +RGBController_SayoDevice::RGBController_SayoDevice(SayoDeviceController* controller_ptr) +{ + controller = controller_ptr; + + name = controller->GetDeviceName(); + vendor = "SayoDevice"; + description = "SayoDevice E1 Knob"; + type = DEVICE_TYPE_KEYBOARD; + location = controller->GetDeviceLocation(); + + mode Direct; + Direct.name = "Direct"; + Direct.value = SAYO_MODE_DIRECT; + Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE; + Direct.color_mode = MODE_COLORS_PER_LED; + modes.push_back(Direct); + + /*-----------------------------------------------------*\ + | Breathing - pulses a single color | + \*-----------------------------------------------------*/ + mode Breathing; + Breathing.name = "Breathing"; + Breathing.value = SAYO_MODE_BREATHING; + Breathing.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_RANDOM_COLOR; + Breathing.color_mode = MODE_COLORS_PER_LED; + Breathing.speed_min = 0; + Breathing.speed_max = 3; + Breathing.speed = 1; + modes.push_back(Breathing); + + /*-----------------------------------------------------*\ + | Wave - fades through multiple colors | + \*-----------------------------------------------------*/ + mode Wave; + Wave.name = "Wave"; + Wave.value = SAYO_MODE_WAVE; + Wave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_RANDOM_COLOR; + Wave.color_mode = MODE_COLORS_NONE; + Wave.speed_min = 0; + Wave.speed_max = 3; + Wave.speed = 1; + modes.push_back(Wave); + + /*-----------------------------------------------------*\ + | Switch - alternates multiple colors | + \*-----------------------------------------------------*/ + mode Switch; + Switch.name = "Switch"; + Switch.value = SAYO_MODE_SWITCH; + Switch.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_RANDOM_COLOR; + Switch.color_mode = MODE_COLORS_NONE; + Switch.speed_min = 0; + Switch.speed_max = 3; + Switch.speed = 1; + modes.push_back(Switch); + + /*-----------------------------------------------------*\ + | Blink - blinks on and off | + \*-----------------------------------------------------*/ + mode Blink; + Blink.name = "Blink"; + Blink.value = SAYO_MODE_BLINK; + Blink.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_RANDOM_COLOR; + Blink.color_mode = MODE_COLORS_PER_LED; + Blink.speed_min = 0; + Blink.speed_max = 3; + Blink.speed = 1; + modes.push_back(Blink); + + SetupZones(); +}; + +RGBController_SayoDevice::~RGBController_SayoDevice() +{ + delete controller; +} + +void RGBController_SayoDevice::SetupZones() +{ + const int led_count = 1; + + zone zone; + zone.name = "Underglow"; + zone.type = ZONE_TYPE_SINGLE; + zone.leds_count = led_count; + zone.leds_min = led_count; + zone.leds_max = led_count; + zone.matrix_map = NULL; + + zones.clear(); + zones.push_back(zone); + + leds.clear(); + leds.resize(led_count); + 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; + leds.push_back(new_led); + } + + SetupColors(); +} + +void RGBController_SayoDevice::ResizeZone(int /*zone*/, int /*new_size*/) +{ + /*-----------------------------------------------------*\ + | This device does not support resizing zones | + \*-----------------------------------------------------*/ +} + +void RGBController_SayoDevice::DeviceUpdateLEDs() +{ + unsigned int hw_mode = (modes[active_mode].value == SAYO_MODE_DIRECT) ? SAYO_MODE_STATIC : modes[active_mode].value; + bool random = (modes[active_mode].color_mode == MODE_COLORS_RANDOM); + unsigned int speed = 3 - modes[active_mode].speed; + + controller->SetMode(hw_mode, speed, colors[0], random); +} + +void RGBController_SayoDevice::UpdateZoneLEDs(int /*zone*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_SayoDevice::UpdateSingleLED(int /*led*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_SayoDevice::DeviceUpdateMode() +{ + /*-----------------------------------------------------*\ + | Mode and color are always set together. | + \*-----------------------------------------------------*/ + DeviceUpdateLEDs(); +} + +void RGBController_SayoDevice::DeviceSaveMode() +{ + controller->Save(); +} diff --git a/Controllers/SayoDeviceController/RGBController_SayoDevice.h b/Controllers/SayoDeviceController/RGBController_SayoDevice.h new file mode 100644 index 000000000..d6e18989d --- /dev/null +++ b/Controllers/SayoDeviceController/RGBController_SayoDevice.h @@ -0,0 +1,36 @@ +/*---------------------------------------------------------*\ +| RGBController_SayoDevice.h | +| | +| Controller for Sayo Devices | +| | +| Richard Harris 24 Jun 2026 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#pragma once + +#include "RGBController.h" +#include "SayoDeviceController.h" + +class RGBController_SayoDevice : public RGBController +{ +public: + RGBController_SayoDevice(SayoDeviceController* controller_ptr); + ~RGBController_SayoDevice(); + + void SetupZones(); + + void ResizeZone(int zone, int new_size); + + void DeviceUpdateLEDs(); + void UpdateZoneLEDs(int zone); + void UpdateSingleLED(int led); + + void DeviceUpdateMode(); + void DeviceSaveMode(); + +private: + SayoDeviceController* controller; +}; diff --git a/Controllers/SayoDeviceController/SayoDeviceController.cpp b/Controllers/SayoDeviceController/SayoDeviceController.cpp new file mode 100644 index 000000000..2cc2fa9f2 --- /dev/null +++ b/Controllers/SayoDeviceController/SayoDeviceController.cpp @@ -0,0 +1,128 @@ +/*---------------------------------------------------------*\ +| SayoDeviceController.cpp | +| | +| Controller for Sayo Devices (USB HID) | +| | +| Richard Harris 24 Jun 2026 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#include +#include "LogManager.h" +#include "SayoDeviceController.h" +#include "RGBController.h" +#include "Colors.h" + +SayoDeviceController::SayoDeviceController(hid_device* dev_handle, const char* path, std::string dev_name) +{ + dev = dev_handle; + location = path; + name = dev_name; +} + +SayoDeviceController::~SayoDeviceController() +{ + hid_close(dev); +} + +std::string SayoDeviceController::GetDeviceLocation() +{ + return location; +} + +std::string SayoDeviceController::GetDeviceName() +{ + return name; +} + +void SayoDeviceController::SetMode(unsigned int mode, unsigned int speed, RGBColor color, bool random) +{ + /*-----------------------------------------------------*\ + | Loop color table creates a rainbow effect which looks | + | better than truly random colors, so use that. | + \*-----------------------------------------------------*/ + unsigned int color_mode = random ? SAYO_COLOR_LOOP_TABLE : SAYO_COLOR_STATIC; + unsigned char mode_byte = SAYO_MODE_PACK(speed, color_mode, mode); + + /*-----------------------------------------------------*\ + | 0x1C (0x00) is potentially length (28) as the payload | + | is 27 bytes in size. Could include a zero as many | + | fields seem to be 2-byte. | + | Other fields have not been mapped out at this time. | + \*-----------------------------------------------------*/ + std::vector payload = + { + 0x1C, 0x00, SAYO_CMD_LIGHTING_SET, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x28, 0x00, 0x26, 0x00, 0x4C, 0x00, + 0x26, 0x00, 0x00, 0x00, mode_byte, 0x00, 0x80, 0x80, + (unsigned char)RGBGetRValue(color), + (unsigned char)RGBGetGValue(color), + (unsigned char)RGBGetBValue(color), + }; + SendPacket(payload, true); +} + +/*---------------------------------------------------------*\ +| Persist LED settings to flash memory. | +\*---------------------------------------------------------*/ +void SayoDeviceController::Save() +{ + /*-----------------------------------------------------*\ + | 0x06 0x00 feels like a length too - 6 bytes | + \*-----------------------------------------------------*/ + std::vector payload = { 0x06, 0x00, 0x0d, 0x00, 0x96, 0x72 }; + SendPacket(payload, true); +} + +void SayoDeviceController::SendPacket(const std::vector& command, bool flush) +{ + unsigned char length = (unsigned char)(command.size() + 4); + + if(length > 64) + { + LOG_ERROR("[SayoDevice] SendPacket: command size (%d) is too large, rejecting", (int)command.size()); + return; + } + + std::vector packet = {0x21, 0x12}; + + /*-----------------------------------------------------*\ + | Checksum of header + command, as little endian 2-byte | + | pairs. Checksum is in same format. | + \*-----------------------------------------------------*/ + unsigned short checksum = (unsigned short)(packet[0] | (packet[1] << 8)); + for(std::size_t i = 0; i < command.size(); i += 2) + { + unsigned short word = command[i]; + + if(i + 1 < command.size()) + { + word |= (unsigned short)(command[i + 1] << 8); + } + + checksum = (checksum + word) & 0xFFFF; + } + + packet.push_back(checksum & 0xFF); + packet.push_back(checksum >> 8); + packet.insert(packet.end(), command.begin(), command.end()); + packet.resize(64, 0u); + + hid_write(dev, packet.data(), packet.size()); + + 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); + } + } +} diff --git a/Controllers/SayoDeviceController/SayoDeviceController.h b/Controllers/SayoDeviceController/SayoDeviceController.h new file mode 100644 index 000000000..a9dbffa87 --- /dev/null +++ b/Controllers/SayoDeviceController/SayoDeviceController.h @@ -0,0 +1,103 @@ +/*---------------------------------------------------------*\ +| SayoDeviceController.h | +| | +| Controller for Sayo Devices | +| | +| Richard Harris 24 Jun 2026 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#pragma once + +#include +#include +#include +#include "RGBController.h" + +/*-----------------------------------------*\ +| Lighting modes | +\*-----------------------------------------*/ +enum +{ + SAYO_MODE_STATIC = 0x00, + SAYO_MODE_INDICATOR = 0x01, + SAYO_MODE_BREATHING = 0x02, + SAYO_MODE_BREATHING_ONCE = 0x03, + SAYO_MODE_WAVE = 0x04, + SAYO_MODE_SWITCH = 0x06, + SAYO_MODE_SWITCH_ONCE = 0x07, + SAYO_MODE_BLINK = 0x08, + SAYO_MODE_BLINK_ONCE = 0x09, + SAYO_MODE_FADE_OUT = 0x0E, + SAYO_MODE_FADE_IN = 0x0F, + /*-------------------------------------*\ + | Virtual modes | + \*-------------------------------------*/ + SAYO_MODE_DIRECT = 0xFF, +}; + +/*-----------------------------------------*\ +| Animation speeds | +\*-----------------------------------------*/ +enum +{ + SAYO_SPEED_1X = 3, + SAYO_SPEED_2X = 2, + SAYO_SPEED_4X = 1, + SAYO_SPEED_8X = 0, +}; + +/*-----------------------------------------*\ +| Animation color mode | +| TABLE modes either loop through or pick | +| randomly from a palette of colors. | +| RANDOM is truly random. | +\*-----------------------------------------*/ +enum +{ + SAYO_COLOR_STATIC = 0, + SAYO_COLOR_LOOP_TABLE = 1, + SAYO_COLOR_RANDOM_TABLE = 2, + SAYO_COLOR_RANDOM = 3, +}; + +#define SAYO_MODE_PACK(speed, color_mode, mode) \ + ((unsigned char)((((speed) & 0x3) << 6) | \ + (((color_mode) & 0x3) << 4) | \ + ((mode) & 0xF))) + +/*-----------------------------------------*\ +| Commands | +\*-----------------------------------------*/ +enum +{ + SAYO_CMD_API_LIST = 0x00, + SAYO_CMD_KEY_GET = 0x02, // maybe? + SAYO_CMD_SETTINGS = 0x03, + SAYO_CMD_KEY_SET = 0x10, + SAYO_CMD_LIGHTING_SET = 0x11, + SAYO_CMD_REBOOT = 0x0E, + +}; + +class SayoDeviceController +{ +public: + SayoDeviceController(hid_device* dev_handle, const char* path, std::string dev_name); + ~SayoDeviceController(); + + std::string GetDeviceLocation(); + std::string GetDeviceName(); + + void SetMode(unsigned int mode, unsigned int speed, RGBColor color, bool random); + void Save(); + +private: + hid_device* dev; + std::string name; + std::string location; + + void SendPacket(const std::vector& command, bool flush = true); +}; diff --git a/Controllers/SayoDeviceController/SayoDeviceControllerDetect.cpp b/Controllers/SayoDeviceController/SayoDeviceControllerDetect.cpp new file mode 100644 index 000000000..0d073fc95 --- /dev/null +++ b/Controllers/SayoDeviceController/SayoDeviceControllerDetect.cpp @@ -0,0 +1,42 @@ +/*---------------------------------------------------------*\ +| SayoDeviceControllerDetect.cpp | +| | +| Detector for Sayo Devices | +| | +| Richard Harris 24 Jun 2026 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#include "Detector.h" +#include "SayoDeviceController.h" +#include "RGBController_SayoDevice.h" + +#define SAYO_USB_VID 0x8089 +#define SAYO_USB_PID_E1 0x0007 + +/*----------------------------------------------------------*\ +| | +| DetectSayoDevice Controller | +| | +| Detect Sayo Devices | +| | +\*----------------------------------------------------------*/ + +void DetectSayoDeviceController + ( + hid_device_info* info, + const std::string& name + ) +{ + hid_device* dev = hid_open_path(info->path); + if(dev != nullptr) + { + SayoDeviceController* controller = new SayoDeviceController(dev, info->path, name); + RGBController_SayoDevice *rgb_controller = new RGBController_SayoDevice(controller); + ResourceManager::get()->RegisterRGBController(rgb_controller); + } +} + +REGISTER_HID_DETECTOR_PU("SayoDevice E1", DetectSayoDeviceController, SAYO_USB_VID, SAYO_USB_PID_E1, 0xFF11, 0x0002);