From 4e14f0359df535ed62108669fa941235d4c91a50 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 30 Aug 2023 01:43:38 +0200 Subject: [PATCH] Add support for Arctic RGB Controller The Arctic RGB controller support 4 RGB channel and can be controlled over a CH341 USB-to-serial chip. The controller support two commands, one for identifying the controller on a serial port and one for setting the RGB values for each RGB channel. Since the controllers disables the RGB channels after ~1s, a keepalive thread is used. --- .../ArcticController/ArcticController.cpp | 153 ++++++++++++++++++ .../ArcticController/ArcticController.h | 27 ++++ .../ArcticControllerDetect.cpp | 49 ++++++ .../ArcticController/RGBController_Arctic.cpp | 131 +++++++++++++++ .../ArcticController/RGBController_Arctic.h | 40 +++++ OpenRGB.pro | 5 + 6 files changed, 405 insertions(+) create mode 100644 Controllers/ArcticController/ArcticController.cpp create mode 100644 Controllers/ArcticController/ArcticController.h create mode 100644 Controllers/ArcticController/ArcticControllerDetect.cpp create mode 100644 Controllers/ArcticController/RGBController_Arctic.cpp create mode 100644 Controllers/ArcticController/RGBController_Arctic.h diff --git a/Controllers/ArcticController/ArcticController.cpp b/Controllers/ArcticController/ArcticController.cpp new file mode 100644 index 00000000..aebc9585 --- /dev/null +++ b/Controllers/ArcticController/ArcticController.cpp @@ -0,0 +1,153 @@ +/*-----------------------------------------*\ +| ArcticController.h | +| | +| Controller Interface for Arctic devices | +| | +| Armin Wolf (Wer-Wolf) 01/09/2023 | +\*-----------------------------------------*/ + +#include "ArcticController.h" +#include + +using namespace std::chrono_literals; + +#define ARCTIC_COMMAND_SET_RGB 0x00 +#define ARCTIC_COMMAND_IDENTIFY 0x5C + +#define ARCTIC_RESPONSE_BUFFER_LENGTH 15 +#define ARCTIC_RESPONSE_COMMAND_OFFSET 0 +#define ARCTIC_RESPONSE_DATA_OFFSET 1 +#define ARCTIC_RESPONSE_DATA_LENGTH 12 +#define ARCTIC_RESPONSE_XOR_CSUM_OFFSET 13 +#define ARCTIC_RESPONSE_ADD_CSUM_OFFSET 14 + +#define ARCTIC_COMMAND_BUFFER_LENGTH(payload_size) (sizeof(header) + 1 + payload_size) +#define ARCTIC_COMMAND_COMMAND_OFFSET (sizeof(header)) +#define ARCTIC_COMMAND_PAYLOAD_OFFSET (sizeof(header) + 1) + +const unsigned char header[] = +{ + 0x01, + 0x02, + 0x03, + 0xFF, + 0x05, + 0xFF, + 0x02, + 0x03 +}; + +const unsigned char identify_payload[] = +{ + 0x01, + 0xFE, + 0x01, + 0xFE +}; + +ArcticController::ArcticController(const std::string &portname) +: port_name(portname), + serialport(portname.c_str(), 250000, SERIAL_PORT_PARITY_NONE, SERIAL_PORT_SIZE_8, SERIAL_PORT_STOP_BITS_2, false) +{ + serialport.serial_set_dtr(true); +} + +ArcticController::~ArcticController() +{ + serialport.serial_set_dtr(false); +} + +static void FormatCommandBuffer(char *buffer, char command) +{ + std::memcpy(buffer, header, sizeof(header)); + buffer[ARCTIC_COMMAND_COMMAND_OFFSET] = command; +} + +void ArcticController::SetChannels(std::vector colors) +{ + char buffer[ARCTIC_COMMAND_BUFFER_LENGTH(colors.size() * 3)]; + + FormatCommandBuffer(buffer, ARCTIC_COMMAND_SET_RGB); + + for(unsigned int channel = 0; channel < colors.size(); channel++) + { + const unsigned int offset = ARCTIC_COMMAND_PAYLOAD_OFFSET + channel * 3; + + buffer[offset + 0x00] = std::min(254, RGBGetRValue(colors[channel])); + buffer[offset + 0x01] = std::min(254, RGBGetGValue(colors[channel])); + buffer[offset + 0x02] = std::min(254, RGBGetBValue(colors[channel])); + } + + serialport.serial_write(buffer, sizeof(buffer)); +} + +static char XORChecksum(char *data, int length) +{ + char sum = 0; + + for(int i = 0; i < length; i++) + { + sum ^= data[i]; + } + + return sum; +} + +static char AddChecksum(char *data, int length) +{ + char sum = 0; + + for(int i = 0; i < length; i++) + { + sum = (char)(sum + data[i]); + } + + return sum; +} + +bool ArcticController::IsPresent() +{ + char buffer[ARCTIC_COMMAND_BUFFER_LENGTH(sizeof(identify_payload))]; + char response[ARCTIC_RESPONSE_BUFFER_LENGTH]; + int ret; + + FormatCommandBuffer(buffer, ARCTIC_COMMAND_IDENTIFY); + std::memcpy(buffer + ARCTIC_COMMAND_PAYLOAD_OFFSET, identify_payload, sizeof(identify_payload)); + + serialport.serial_flush_rx(); + ret = serialport.serial_write(buffer, sizeof(buffer)); + if(ret != sizeof(buffer)) + { + return false; + } + + std::this_thread::sleep_for(100ms); + + ret = serialport.serial_read(response, sizeof(response)); + if(ret != sizeof(response)) + { + return false; + } + + if(response[ARCTIC_RESPONSE_COMMAND_OFFSET] != ARCTIC_COMMAND_IDENTIFY) + { + return false; + } + + if(response[ARCTIC_RESPONSE_XOR_CSUM_OFFSET] != XORChecksum(&response[ARCTIC_RESPONSE_DATA_OFFSET], ARCTIC_RESPONSE_DATA_LENGTH)) + { + return false; + } + + if(response[ARCTIC_RESPONSE_ADD_CSUM_OFFSET] != AddChecksum(&response[ARCTIC_RESPONSE_DATA_OFFSET], ARCTIC_RESPONSE_DATA_LENGTH)) + { + return false; + } + + return true; +} + +std::string ArcticController::GetLocation() +{ + return port_name; +} diff --git a/Controllers/ArcticController/ArcticController.h b/Controllers/ArcticController/ArcticController.h new file mode 100644 index 00000000..94b2caa9 --- /dev/null +++ b/Controllers/ArcticController/ArcticController.h @@ -0,0 +1,27 @@ +/*-----------------------------------------*\ +| ArcticController.h | +| | +| Controller Interface for Arctic devices | +| | +| Armin Wolf (Wer-Wolf) 01/09/2023 | +\*-----------------------------------------*/ + +#pragma once +#include "RGBController.h" +#include "serial_port.h" + +class ArcticController +{ +public: + ArcticController(const std::string &portname); + ~ArcticController(); + + void SetChannels(std::vector colors); + bool IsPresent(); + + std::string GetLocation(); + +private: + std::string port_name; + serial_port serialport; +}; diff --git a/Controllers/ArcticController/ArcticControllerDetect.cpp b/Controllers/ArcticController/ArcticControllerDetect.cpp new file mode 100644 index 00000000..7c83c846 --- /dev/null +++ b/Controllers/ArcticController/ArcticControllerDetect.cpp @@ -0,0 +1,49 @@ +/*-----------------------------------------*\ +| ArcticControllerDetect.cpp | +| | +| Detect Arctic RGB controllers | +| | +| Armin Wolf (Wer-Wolf) 01/09/2023 | +\*-----------------------------------------*/ + +#include "Detector.h" +#include "ArcticController.h" +#include "RGBController.h" +#include "RGBController_Arctic.h" +#include "SettingsManager.h" +#include "find_usb_serial_port.h" +#include +#include +#include + +#define CH341_VID 0x1A86 +#define CH341_PID 0x7523 + +void DetectArcticControllers() +{ + std::vector ports = find_usb_serial_port(CH341_VID, CH341_PID); + + for(unsigned int device = 0; device < ports.size(); device++) + { + ArcticController *controller = new ArcticController(*ports[device]); + + if(controller->IsPresent()) + { + RGBController_Arctic *rgb_controller = new RGBController_Arctic(controller); + ResourceManager::get()->RegisterRGBController(rgb_controller); + } + else + { + delete controller; + } + + delete ports[device]; + } +} + +REGISTER_DETECTOR("Arctic RGB controller", DetectArcticControllers); +/*---------------------------------------------------------------------------------------------------------*\ +| Entries for dynamic UDEV rules | +| | +| DUMMY_DEVICE_DETECTOR("Arctic RGB controller", DetectArcticControllers, 0x1a86, 0x7523 ) | +\*---------------------------------------------------------------------------------------------------------*/ diff --git a/Controllers/ArcticController/RGBController_Arctic.cpp b/Controllers/ArcticController/RGBController_Arctic.cpp new file mode 100644 index 00000000..29da475a --- /dev/null +++ b/Controllers/ArcticController/RGBController_Arctic.cpp @@ -0,0 +1,131 @@ +/*-----------------------------------------*\ +| RGBController_Arctic.h | +| | +| Generic RGB Interface for Arctic devices | +| | +| Armin Wolf (Wer-Wolf) 01/09/2023 | +\*-----------------------------------------*/ + +#include "RGBController_Arctic.h" +#include + +using namespace std::chrono_literals; + +#define ARCTIC_NUM_CHANNELS 4 +#define ARCTIC_SLEEP_THRESHOLD 100ms +#define ARCTIC_KEEPALIVE_PERIOD 500ms /* Device requires at least 1s */ + +/**------------------------------------------------------------------*\ + @name Arctic RGB Controller Devices + @category LEDStrip + @type Serial + @save :x: + @direct :white_check_mark: + @effects :x: + @detectors DetectArcticControllers + @comment +\*-------------------------------------------------------------------*/ + +RGBController_Arctic::RGBController_Arctic(ArcticController* controller_ptr) +{ + controller = controller_ptr; + + name = "Arctic RGB Controller"; + vendor = "Arctic"; + description = "Arctic 4-Channel RGB Controller"; + location = controller->GetLocation(); + type = DEVICE_TYPE_LEDSTRIP; + + mode DirectMode; + DirectMode.name = "Direct"; + DirectMode.value = 0; + DirectMode.flags = MODE_FLAG_HAS_PER_LED_COLOR; + DirectMode.color_mode = MODE_COLORS_PER_LED; + + modes.push_back(DirectMode); + + SetupZones(); + + keepalive_thread_run = true; + keepalive_thread = std::thread(&RGBController_Arctic::KeepaliveThreadFunction, this); +} + +RGBController_Arctic::~RGBController_Arctic() +{ + keepalive_thread_run = false; + keepalive_thread.join(); + delete controller; +} + +void RGBController_Arctic::SetupZones() +{ + for(int channel = 0; channel < ARCTIC_NUM_CHANNELS; channel++) + { + zone LedZone; + LedZone.name = "LED Strip " + std::to_string(channel); + LedZone.type = ZONE_TYPE_SINGLE; + LedZone.leds_count = 1; + LedZone.leds_min = 1; + LedZone.leds_max = 1; + LedZone.matrix_map = nullptr; + + led Led; + Led.name = LedZone.name + " LED"; + Led.value = channel; + + zones.push_back(LedZone); + leds.push_back(Led); + } + + SetupColors(); +} + +void RGBController_Arctic::ResizeZone(int /* zone */, int /* new_size */) +{ + /*---------------------------------------------------------*\ + | This device does not support resizing zones | + \*---------------------------------------------------------*/ +} + +void RGBController_Arctic::DeviceUpdateLEDs() +{ + last_update_time = std::chrono::steady_clock::now(); + + controller->SetChannels(colors); +} + +void RGBController_Arctic::UpdateZoneLEDs(int /* zone */) +{ + DeviceUpdateLEDs(); +} + +void RGBController_Arctic::UpdateSingleLED(int /* led */) +{ + DeviceUpdateLEDs(); +} + +void RGBController_Arctic::DeviceUpdateMode() +{ + /*---------------------------------------------------------*\ + | This device does not support mode updates | + \*---------------------------------------------------------*/ +} + +void RGBController_Arctic::KeepaliveThreadFunction() +{ + std::chrono::nanoseconds sleep_time; + + while(keepalive_thread_run.load()) + { + sleep_time = ARCTIC_KEEPALIVE_PERIOD - (std::chrono::steady_clock::now() - last_update_time); + if(sleep_time <= ARCTIC_SLEEP_THRESHOLD) + { + UpdateLEDs(); // Already protected thru a device update thread + std::this_thread::sleep_for(ARCTIC_KEEPALIVE_PERIOD); + } + else + { + std::this_thread::sleep_for(sleep_time); + } + } +} diff --git a/Controllers/ArcticController/RGBController_Arctic.h b/Controllers/ArcticController/RGBController_Arctic.h new file mode 100644 index 00000000..3de7c711 --- /dev/null +++ b/Controllers/ArcticController/RGBController_Arctic.h @@ -0,0 +1,40 @@ +/*-----------------------------------------*\ +| RGBController_Arctic.h | +| | +| Generic RGB Interface for Arctic devices | +| | +| Armin Wolf (Wer-Wolf) 27/08/2023 | +\*-----------------------------------------*/ + +#pragma once +#include "ArcticController.h" +#include "RGBController.h" +#include "serial_port.h" +#include +#include + +class RGBController_Arctic : public RGBController +{ +public: + RGBController_Arctic(ArcticController* controller_ptr); + ~RGBController_Arctic(); + + void SetupZones(); + + void ResizeZone(int zone, int new_size); + + void DeviceUpdateLEDs(); + void UpdateZoneLEDs(int zone); + void UpdateSingleLED(int led); + + void DeviceUpdateMode(); + + void KeepaliveThreadFunction(); + +private: + ArcticController* controller; + std::chrono::time_point last_update_time; + std::atomic keepalive_thread_run; + std::thread keepalive_thread; +}; + diff --git a/OpenRGB.pro b/OpenRGB.pro index faba2180..3284c61c 100644 --- a/OpenRGB.pro +++ b/OpenRGB.pro @@ -337,6 +337,8 @@ HEADERS += Controllers/AOCMouseController/RGBController_AOCMouse.h \ Controllers/AOCMousematController/AOCMousematController.h \ Controllers/AOCMousematController/RGBController_AOCMousemat.h \ + Controllers/ArcticController/ArcticController.h \ + Controllers/ArcticController/RGBController_Arctic.h \ Controllers/ASRockPolychromeUSBController/ASRockPolychromeUSBController.h \ Controllers/ASRockPolychromeUSBController/RGBController_ASRockPolychromeUSB.h \ Controllers/ASRockSMBusController/ASRockASRRGBSMBusController.h \ @@ -938,6 +940,9 @@ SOURCES += Controllers/AOCMousematController/AOCMousematController.cpp \ Controllers/AOCMousematController/AOCMousematControllerDetect.cpp \ Controllers/AOCMousematController/RGBController_AOCMousemat.cpp \ + Controllers/ArcticController/ArcticController.cpp \ + Controllers/ArcticController/ArcticControllerDetect.cpp \ + Controllers/ArcticController/RGBController_Arctic.cpp \ Controllers/ASRockPolychromeUSBController/ASRockPolychromeUSBController.cpp \ Controllers/ASRockPolychromeUSBController/ASRockPolychromeUSBControllerDetect.cpp \ Controllers/ASRockPolychromeUSBController/RGBController_ASRockPolychromeUSB.cpp \