diff --git a/Controllers/ArcticController/ArcticController.cpp b/Controllers/ArcticController/ArcticController.cpp new file mode 100644 index 000000000..aebc9585c --- /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 000000000..94b2caa96 --- /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 000000000..7c83c8466 --- /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 000000000..ee0506483 --- /dev/null +++ b/Controllers/ArcticController/RGBController_Arctic.cpp @@ -0,0 +1,128 @@ +/*-----------------------------------------*\ +| 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 :robot: + @effects :x: + @detectors DetectArcticControllers + @comment +\*-------------------------------------------------------------------*/ + +RGBController_Arctic::RGBController_Arctic(ArcticController *arctic_controller) +: controller(arctic_controller), + keepalive_thread_run(true) +{ + 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 = 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 000000000..eb3efc7a8 --- /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 *arctic_controller); + ~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 db92f311e..06eff8266 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 \ @@ -935,6 +937,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 \ diff --git a/serial_port/serial_port.cpp b/serial_port/serial_port.cpp index b8d603102..e983dd8d7 100644 --- a/serial_port/serial_port.cpp +++ b/serial_port/serial_port.cpp @@ -318,10 +318,13 @@ bool serial_port::serial_open() options.c_lflag &= ~ECHOE; // Disable erasure options.c_lflag &= ~ECHONL; // Disable new-line echo options.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP + options.c_lflag &= ~IEXTEN; // Disable input processing options.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl options.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes + options.c_oflag &= ~OPOST; // Disable output processing; + /*-----------------------------------------*\ | Set the port configuration options | \*-----------------------------------------*/ @@ -420,7 +423,7 @@ bool serial_port::serial_open() /*-----------------------------------------*\ | Configure additional parameters | \*-----------------------------------------*/ - options.c_lflag &= ~(ICANON | ISIG | ECHO); + options.c_lflag &= ~(ICANON | IEXTEN | ISIG | ECHO); options.c_iflag &= ~(INLCR | ICRNL); options.c_iflag |= IGNPAR | IGNBRK; @@ -660,6 +663,53 @@ void serial_port::serial_break() #endif } +void serial_port::serial_set_dtr(bool dtr) +{ + /*-----------------------------------------------------*\ + | Windows-specific code path for serial set DTR | + \*-----------------------------------------------------*/ +#ifdef _WIN32 + if(dtr) + { + EscapeCommFunction(file_descriptor, SETDTR); + } + else + { + EscapeCommFunction(file_descriptor, CLRDTR); + } +#endif + + /*-----------------------------------------------------*\ + | Linux-specific code path for serial set DTR | + \*-----------------------------------------------------*/ +#ifdef __linux__ + const int DTRFLAG = TIOCM_DTR; + if(dtr) + { + ioctl(file_descriptor, TIOCMBIS, &DTRFLAG); + } + else + { + ioctl(file_descriptor, TIOCMBIC, &DTRFLAG); + } +#endif + + /*-----------------------------------------------------*\ + | MacOS-specific code path for serial set DTR | + \*-----------------------------------------------------*/ +#ifdef __APPLE__ + const int DTRFLAG = TIOCM_DTR; + if(dtr) + { + ioctl(file_descriptor, TIOCMBIS, &DTRFLAG); + } + else + { + ioctl(file_descriptor, TIOCMBIC, &DTRFLAG); + } +#endif +} + void serial_port::serial_set_rts(bool rts) { /*-----------------------------------------------------*\ diff --git a/serial_port/serial_port.h b/serial_port/serial_port.h index 0d60b162e..191120cf4 100644 --- a/serial_port/serial_port.h +++ b/serial_port/serial_port.h @@ -126,6 +126,7 @@ class serial_port void serial_flush_tx(); void serial_break(); + void serial_set_dtr(bool dtr); void serial_set_rts(bool rts); int serial_available();