diff --git a/Controllers/EVGACLCController/EVGACLCController.cpp b/Controllers/EVGACLCController/EVGACLCController.cpp new file mode 100644 index 000000000..edd5a7ce3 --- /dev/null +++ b/Controllers/EVGACLCController/EVGACLCController.cpp @@ -0,0 +1,367 @@ +/*---------------------------------------------------------*\ +| EVGACLCController.cpp | +| | +| Driver for EVGA CLC liquid coolers (Asetek 690LC) | +| | +| Protocol based on liquidctl's Asetek 690LC driver | +| https://github.com/liquidctl/liquidctl | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#include +#include +#include +#include "EVGACLCController.h" + +EVGACLCController::EVGACLCController(libusb_device_handle* dev_handle, std::string dev_name) +{ + dev = dev_handle; + name = dev_name; + + if(!dev) + { + firmware_version = "Unknown"; + location = "Unknown"; + return; + } + + /*-----------------------------------------------------*\ + | Fill in location string with USB ID | + \*-----------------------------------------------------*/ + libusb_device_descriptor descriptor; + int ret = libusb_get_device_descriptor(libusb_get_device(dev_handle), &descriptor); + + if(ret < 0) + { + location = "Unknown"; + } + else + { + std::stringstream location_stream; + location_stream << std::hex << std::setfill('0') << std::setw(4) << descriptor.idVendor + << ":" << std::hex << std::setfill('0') << std::setw(4) << descriptor.idProduct; + location = location_stream.str(); + } + + SendInit(); + SendFirmwareRequest(); +} + +EVGACLCController::~EVGACLCController() +{ + if(dev) + { + libusb_close(dev); + } +} + +std::string EVGACLCController::GetFirmwareString() +{ + return firmware_version; +} + +std::string EVGACLCController::GetLocation() +{ + return "USB: " + location; +} + +std::string EVGACLCController::GetNameString() +{ + return name; +} + +void EVGACLCController::SetFixed(unsigned char r, unsigned char g, unsigned char b) +{ + if(!dev) + { + return; + } + + if(name.empty()) + { + return; + } + + SendConfigPacket(r, g, b, 0, 0, 0, 0, 0, true, false, false, false); + SendRainbowSpeed(0); +} + +void EVGACLCController::SetFading + ( + unsigned char r1, unsigned char g1, unsigned char b1, + unsigned char r2, unsigned char g2, unsigned char b2, + unsigned char speed + ) +{ + if(!dev) + { + return; + } + + if(speed < 1) + { + speed = 1; + } + + SendConfigPacket(r1, g1, b1, r2, g2, b2, speed, 0, true, true, false, false); + SendRainbowSpeed(0); +} + +void EVGACLCController::SetBlinking + ( + unsigned char r, unsigned char g, unsigned char b, + unsigned char speed + ) +{ + if(!dev) + { + return; + } + + if(speed < 1) + { + speed = 1; + } + + SendConfigPacket(r, g, b, 0, 0, 0, speed, speed, true, false, true, false); + SendRainbowSpeed(0); +} + +void EVGACLCController::SetRainbow(unsigned char speed) +{ + if(!dev) + { + return; + } + + if(speed > 6) + { + speed = 6; + } + + SendConfigPacket(0, 0, 0, 0, 0, 0, 0, 0, true, false, false, false); + SendRainbowSpeed(speed); +} + +/*-------------------------------------------------------------------------------------------------*\ +| Private packet sending functions. | +\*-------------------------------------------------------------------------------------------------*/ + +void EVGACLCController::SendInit() +{ + if(!dev) + { + return; + } + + if(!libusb_get_device(dev)) + { + return; + } + + /*-----------------------------------------------------*\ + | USBXpress initialization - same as Corsair Hydro | + | Open device and enable clear-to-send | + \*-----------------------------------------------------*/ + int ret = libusb_control_transfer(dev, 0x40, 0x00, 0xFFFF, 0x0000, NULL, 0, 0); + + if(ret < 0) + { + return; + } + + ret = libusb_control_transfer(dev, 0x40, 0x02, 0x0002, 0x0000, NULL, 0, 0); + + if(ret < 0) + { + return; + } +} + +void EVGACLCController::FlushBuffers() +{ + if(!dev) + { + return; + } + + if(!libusb_get_device(dev)) + { + return; + } + + /*-----------------------------------------------------*\ + | USBXpress flush - must be called before each command | + \*-----------------------------------------------------*/ + int ret = libusb_control_transfer(dev, 0x40, 0x02, 0x0001, 0x0000, NULL, 0, 0); + + if(ret < 0) + { + return; + } +} + +void EVGACLCController::SendFirmwareRequest() +{ + if(!dev) + { + firmware_version = "Unknown"; + return; + } + + if(!libusb_get_device(dev)) + { + firmware_version = "Unknown"; + return; + } + + unsigned char usb_buf[32]; + int actual; + int ret; + + memset(usb_buf, 0, sizeof(usb_buf)); + + FlushBuffers(); + + /*-----------------------------------------------------*\ + | Send firmware/status request (command 0x14) | + \*-----------------------------------------------------*/ + usb_buf[0] = 0x14; + + ret = libusb_bulk_transfer(dev, 0x02, usb_buf, 4, &actual, 1000); + + if(ret < 0) + { + firmware_version = "Unknown"; + return; + } + + ret = libusb_bulk_transfer(dev, 0x82, usb_buf, 32, &actual, 1000); + + if(ret < 0) + { + firmware_version = "Unknown"; + return; + } + + firmware_version = std::to_string(usb_buf[0x17]) + "." + + std::to_string(usb_buf[0x18]) + "." + + std::to_string(usb_buf[0x19]) + "." + + std::to_string(usb_buf[0x1A]); +} + +void EVGACLCController::SendConfigPacket + ( + unsigned char r1, + unsigned char g1, + unsigned char b1, + unsigned char r2, + unsigned char g2, + unsigned char b2, + unsigned char interval1, + unsigned char interval2, + bool not_blackout, + bool fading, + bool blinking, + bool alert + ) +{ + if(!dev) + { + return; + } + + if(!libusb_get_device(dev)) + { + return; + } + + unsigned char usb_buf[32]; + int actual; + int ret; + + memset(usb_buf, 0, sizeof(usb_buf)); + + FlushBuffers(); + + /*-----------------------------------------------------*\ + | Build the 0x10 runtime configuration packet | + \*-----------------------------------------------------*/ + usb_buf[0] = 0x10; // Command: runtime config + usb_buf[1] = r1; // Color 1 R + usb_buf[2] = g1; // Color 1 G + usb_buf[3] = b1; // Color 1 B + usb_buf[4] = r2; // Color 2 R + usb_buf[5] = g2; // Color 2 G + usb_buf[6] = b2; // Color 2 B + usb_buf[7] = 0xFF; // Alert color R + usb_buf[8] = 0x00; // Alert color G + usb_buf[9] = 0x00; // Alert color B + usb_buf[10] = 0x2D; // Alert temp threshold (45C) + usb_buf[11] = interval1; // Interval 1 + usb_buf[12] = interval2; // Interval 2 + usb_buf[13] = not_blackout ? 0x01 : 0x00; // NOT blackout (1 = LED on) + usb_buf[14] = fading ? 0x01 : 0x00; // Fading mode + usb_buf[15] = blinking ? 0x01 : 0x00; // Blinking mode + usb_buf[16] = alert ? 0x01 : 0x00; // Enable alert + usb_buf[17] = 0x00; // Unknown + usb_buf[18] = 0x01; // Unknown + + ret = libusb_bulk_transfer(dev, 0x02, usb_buf, 19, &actual, 1000); + + if(ret < 0) + { + return; + } + + ret = libusb_bulk_transfer(dev, 0x82, usb_buf, 32, &actual, 1000); + + if(ret < 0) + { + return; + } +} + +void EVGACLCController::SendRainbowSpeed(unsigned char speed) +{ + if(!dev) + { + return; + } + + if(!libusb_get_device(dev)) + { + return; + } + + unsigned char usb_buf[32]; + int actual; + int ret; + + memset(usb_buf, 0, sizeof(usb_buf)); + + FlushBuffers(); + + /*-----------------------------------------------------*\ + | Rainbow speed command (0x23) | + | speed 0 = rainbow off, 1-6 = rainbow speed | + \*-----------------------------------------------------*/ + usb_buf[0] = 0x23; + usb_buf[1] = speed; + + ret = libusb_bulk_transfer(dev, 0x02, usb_buf, 2, &actual, 1000); + + if(ret < 0) + { + return; + } + + ret = libusb_bulk_transfer(dev, 0x82, usb_buf, 32, &actual, 1000); + + if(ret < 0) + { + return; + } +} diff --git a/Controllers/EVGACLCController/EVGACLCController.h b/Controllers/EVGACLCController/EVGACLCController.h new file mode 100644 index 000000000..c78023a1d --- /dev/null +++ b/Controllers/EVGACLCController/EVGACLCController.h @@ -0,0 +1,95 @@ +/*---------------------------------------------------------*\ +| EVGACLCController.h | +| | +| Driver for EVGA CLC liquid coolers (Asetek 690LC) | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#pragma once + +#include +#include +#include "RGBController.h" + +enum +{ + EVGA_CLC_MODE_FIXED = 0, + EVGA_CLC_MODE_FADING = 1, + EVGA_CLC_MODE_BLINKING = 2, + EVGA_CLC_MODE_RAINBOW = 3, +}; + +class EVGACLCController +{ +public: + EVGACLCController(libusb_device_handle* dev_handle, std::string dev_name); + ~EVGACLCController(); + + std::string GetFirmwareString(); + std::string GetLocation(); + std::string GetNameString(); + + void SetFixed + ( + unsigned char r, + unsigned char g, + unsigned char b + ); + + void SetFading + ( + unsigned char r1, + unsigned char g1, + unsigned char b1, + unsigned char r2, + unsigned char g2, + unsigned char b2, + unsigned char speed + ); + + void SetBlinking + ( + unsigned char r, + unsigned char g, + unsigned char b, + unsigned char speed + ); + + void SetRainbow + ( + unsigned char speed + ); + +private: + libusb_device_handle* dev; + std::string firmware_version; + std::string location; + std::string name; + + void SendInit(); + void SendFirmwareRequest(); + void FlushBuffers(); + + void SendConfigPacket + ( + unsigned char r1, + unsigned char g1, + unsigned char b1, + unsigned char r2, + unsigned char g2, + unsigned char b2, + unsigned char interval1, + unsigned char interval2, + bool not_blackout, + bool fading, + bool blinking, + bool alert + ); + + void SendRainbowSpeed + ( + unsigned char speed + ); +}; diff --git a/Controllers/EVGACLCController/EVGACLCControllerDetect.cpp b/Controllers/EVGACLCController/EVGACLCControllerDetect.cpp new file mode 100644 index 000000000..92d24eace --- /dev/null +++ b/Controllers/EVGACLCController/EVGACLCControllerDetect.cpp @@ -0,0 +1,94 @@ +/*---------------------------------------------------------*\ +| EVGACLCControllerDetect.cpp | +| | +| Detector for EVGA CLC liquid coolers (Asetek 690LC) | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#include +#include "Detector.h" +#include "EVGACLCController.h" +#include "RGBController_EVGACLC.h" + +/*-----------------------------------------------------*\ +| Asetek vendor ID (used by EVGA CLC coolers) | +\*-----------------------------------------------------*/ +#define ASETEK_VID 0x2433 + +/*-----------------------------------------------------*\ +| EVGA CLC product IDs | +\*-----------------------------------------------------*/ +#define EVGA_CLC_PID 0xB200 + +typedef struct +{ + unsigned short usb_vid; + unsigned short usb_pid; + unsigned char usb_interface; + const char * name; +} evga_clc_device; + +#define EVGA_CLC_NUM_DEVICES (sizeof(device_list) / sizeof(device_list[0])) + +static const evga_clc_device device_list[] = +{ + /*-----------------------------------------------------------------------------------------------------*\ + | EVGA CLC Coolers (Asetek 690LC) | + | Covers EVGA CLC 120, 240, 280, and 360 | + \*-----------------------------------------------------------------------------------------------------*/ + { ASETEK_VID, EVGA_CLC_PID, 0, "EVGA CLC" }, +}; + +/******************************************************************************************\ +* * +* DetectEVGACLCControllers * +* * +* Tests the USB address to see if an EVGA CLC controller exists there. * +* * +\******************************************************************************************/ + +void DetectEVGACLCControllers() +{ + int ret = libusb_init(NULL); + + if(ret < 0) + { + return; + } + + #ifdef _WIN32 + (void)libusb_set_option(NULL, LIBUSB_OPTION_USE_USBDK); + #endif + + for(std::size_t device_idx = 0; device_idx < EVGA_CLC_NUM_DEVICES; device_idx++) + { + libusb_device_handle * dev = libusb_open_device_with_vid_pid(NULL, device_list[device_idx].usb_vid, device_list[device_idx].usb_pid); + + if(dev) + { + (void)libusb_detach_kernel_driver(dev, 0); + + ret = libusb_claim_interface(dev, 0); + + if(ret < 0) + { + libusb_close(dev); + continue; + } + + EVGACLCController* controller = new EVGACLCController(dev, device_list[device_idx].name); + RGBController_EVGACLC* rgb_controller = new RGBController_EVGACLC(controller, "EVGA CLC"); + + ResourceManager::get()->RegisterRGBController(rgb_controller); + } + } +} /* DetectEVGACLCControllers() */ + +REGISTER_DETECTOR("EVGA CLC", DetectEVGACLCControllers); +/*---------------------------------------------------------------------------------------------------------*\ +| Entries for dynamic UDEV rules | +| | +| DUMMY_DEVICE_DETECTOR("EVGA CLC", DetectEVGACLCControllers, 0x2433, 0xB200 ) | +\*---------------------------------------------------------------------------------------------------------*/ diff --git a/Controllers/EVGACLCController/RGBController_EVGACLC.cpp b/Controllers/EVGACLCController/RGBController_EVGACLC.cpp new file mode 100644 index 000000000..c5e14b92b --- /dev/null +++ b/Controllers/EVGACLCController/RGBController_EVGACLC.cpp @@ -0,0 +1,295 @@ +/*---------------------------------------------------------*\ +| RGBController_EVGACLC.cpp | +| | +| RGBController for EVGA CLC liquid coolers | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#include "RGBController_EVGACLC.h" +#include "SettingsManager.h" +#include "ResourceManager.h" + +/**------------------------------------------------------------------*\ + @name EVGA CLC + @category Cooler + @type USB + @save :x: + @direct :white_check_mark: + @effects :white_check_mark: + @detectors DetectEVGACLCControllers + @comment EVGA CLC liquid coolers using the Asetek 690LC platform. + Supports fixed color, fading, blinking, and rainbow modes. + Color calibration can be configured in Settings > EVGA CLC. + Set R/G/B scale values (0-100) to match other devices. +\*-------------------------------------------------------------------*/ + +RGBController_EVGACLC::RGBController_EVGACLC(EVGACLCController* controller_ptr, const std::string& detector_name) +{ + controller = controller_ptr; + det_name = detector_name; + + if(!controller) + { + name = "Unknown"; + vendor = "EVGA"; + description = "EVGA CLC Liquid Cooler"; + version = "Unknown"; + type = DEVICE_TYPE_COOLER; + location = "Unknown"; + return; + } + + if(detector_name.empty()) + { + det_name = "EVGA CLC"; + } + + name = controller->GetNameString(); + vendor = "EVGA"; + description = "EVGA CLC Liquid Cooler"; + version = controller->GetFirmwareString(); + type = DEVICE_TYPE_COOLER; + location = controller->GetLocation(); + + /*---------------------------------------------------------*\ + | Load color calibration from settings | + \*---------------------------------------------------------*/ + LoadCalibration(); + + InitModes(); + SetupZones(); +} + +void RGBController_EVGACLC::InitModes() +{ + if(!modes.empty()) + { + return; + } + + if(!controller) + { + return; + } + + mode Direct; + Direct.name = "Direct"; + Direct.value = EVGA_CLC_MODE_FIXED; + Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR; + Direct.color_mode = MODE_COLORS_PER_LED; + modes.push_back(Direct); + + mode Fading; + Fading.name = "Fading"; + Fading.value = EVGA_CLC_MODE_FADING; + Fading.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR; + Fading.color_mode = MODE_COLORS_MODE_SPECIFIC; + Fading.speed_min = 1; + Fading.speed_max = 255; + Fading.speed = 5; + Fading.colors_min = 2; + Fading.colors_max = 2; + Fading.colors.resize(2); + modes.push_back(Fading); + + mode Blinking; + Blinking.name = "Blinking"; + Blinking.value = EVGA_CLC_MODE_BLINKING; + Blinking.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR; + Blinking.color_mode = MODE_COLORS_MODE_SPECIFIC; + Blinking.speed_min = 1; + Blinking.speed_max = 255; + Blinking.speed = 5; + Blinking.colors_min = 1; + Blinking.colors_max = 1; + Blinking.colors.resize(1); + modes.push_back(Blinking); + + mode Rainbow; + Rainbow.name = "Rainbow"; + Rainbow.value = EVGA_CLC_MODE_RAINBOW; + Rainbow.flags = MODE_FLAG_HAS_SPEED; + Rainbow.color_mode = MODE_COLORS_NONE; + Rainbow.speed_min = 1; + Rainbow.speed_max = 6; + Rainbow.speed = 3; + modes.push_back(Rainbow); +} + +RGBController_EVGACLC::~RGBController_EVGACLC() +{ + delete controller; +} + +void RGBController_EVGACLC::LoadCalibration() +{ + /*---------------------------------------------------------*\ + | Default calibration: compensates for the warm phosphor | + | bias typical of EVGA CLC pump LEDs. R and G channels | + | are scaled to 20% to produce a neutral white that matches | + | most GPU and motherboard LEDs. Adjust in OpenRGB.json | + | under "EVGA CLC" > "Calibration" if needed. | + \*---------------------------------------------------------*/ + cal_r = 20; + cal_g = 20; + cal_b = 100; + + SettingsManager* settings_manager = ResourceManager::get()->GetSettingsManager(); + + if(settings_manager != nullptr) + { + json device_settings = settings_manager->GetSettings(det_name); + + if(device_settings.contains("Calibration")) + { + json cal = device_settings["Calibration"]; + + if(cal.contains("R_Scale")) + { + cal_r = cal["R_Scale"].get(); + } + + if(cal.contains("G_Scale")) + { + cal_g = cal["G_Scale"].get(); + } + + if(cal.contains("B_Scale")) + { + cal_b = cal["B_Scale"].get(); + } + } + else + { + /*-------------------------------------------------*\ + | Write default calibration so users can find and | + | edit it in OpenRGB.json | + \*-------------------------------------------------*/ + device_settings["Calibration"]["R_Scale"] = cal_r; + device_settings["Calibration"]["G_Scale"] = cal_g; + device_settings["Calibration"]["B_Scale"] = cal_b; + settings_manager->SetSettings(det_name, device_settings); + settings_manager->SaveSettings(); + } + } +} + +unsigned char RGBController_EVGACLC::CalibrateChannel(unsigned char value, unsigned char scale) +{ + if(scale >= 100) + { + return value; + } + + unsigned int result = (value * scale) / 100; + + if(result > 255) + { + result = 255; + } + + return (unsigned char)(result); +} + +void RGBController_EVGACLC::SetupZones() +{ + if(!zones.empty()) + { + return; + } + + if(!controller) + { + return; + } + + zone new_zone; + + new_zone.name = "Logo"; + new_zone.type = ZONE_TYPE_SINGLE; + new_zone.leds_min = 1; + new_zone.leds_max = 1; + new_zone.leds_count = 1; + new_zone.matrix_map = NULL; + zones.push_back(new_zone); + + led new_led; + + new_led.name = "Logo LED"; + leds.push_back(new_led); + + SetupColors(); +} + +void RGBController_EVGACLC::ResizeZone(int /*zone*/, int /*new_size*/) +{ + /*---------------------------------------------------------*\ + | This device does not support resizing zones | + \*---------------------------------------------------------*/ +} + +void RGBController_EVGACLC::DeviceUpdateLEDs() +{ + DeviceUpdateMode(); +} + +void RGBController_EVGACLC::UpdateZoneLEDs(int /*zone*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_EVGACLC::UpdateSingleLED(int /*led*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_EVGACLC::DeviceUpdateMode() +{ + if(!controller) + { + return; + } + + if(active_mode < 0 || active_mode >= (int)modes.size()) + { + return; + } + + switch(modes[active_mode].value) + { + case EVGA_CLC_MODE_FIXED: + controller->SetFixed( + CalibrateChannel(RGBGetRValue(colors[0]), cal_r), + CalibrateChannel(RGBGetGValue(colors[0]), cal_g), + CalibrateChannel(RGBGetBValue(colors[0]), cal_b) + ); + break; + + case EVGA_CLC_MODE_FADING: + controller->SetFading( + CalibrateChannel(RGBGetRValue(modes[active_mode].colors[0]), cal_r), + CalibrateChannel(RGBGetGValue(modes[active_mode].colors[0]), cal_g), + CalibrateChannel(RGBGetBValue(modes[active_mode].colors[0]), cal_b), + CalibrateChannel(RGBGetRValue(modes[active_mode].colors[1]), cal_r), + CalibrateChannel(RGBGetGValue(modes[active_mode].colors[1]), cal_g), + CalibrateChannel(RGBGetBValue(modes[active_mode].colors[1]), cal_b), + modes[active_mode].speed + ); + break; + + case EVGA_CLC_MODE_BLINKING: + controller->SetBlinking( + CalibrateChannel(RGBGetRValue(modes[active_mode].colors[0]), cal_r), + CalibrateChannel(RGBGetGValue(modes[active_mode].colors[0]), cal_g), + CalibrateChannel(RGBGetBValue(modes[active_mode].colors[0]), cal_b), + modes[active_mode].speed + ); + break; + + case EVGA_CLC_MODE_RAINBOW: + controller->SetRainbow(modes[active_mode].speed); + break; + } +} diff --git a/Controllers/EVGACLCController/RGBController_EVGACLC.h b/Controllers/EVGACLCController/RGBController_EVGACLC.h new file mode 100644 index 000000000..fa1a21351 --- /dev/null +++ b/Controllers/EVGACLCController/RGBController_EVGACLC.h @@ -0,0 +1,42 @@ +/*---------------------------------------------------------*\ +| RGBController_EVGACLC.h | +| | +| RGBController for EVGA CLC liquid coolers | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#pragma once + +#include "RGBController.h" +#include "EVGACLCController.h" + +class RGBController_EVGACLC : public RGBController +{ +public: + RGBController_EVGACLC(EVGACLCController* controller_ptr, const std::string& detector_name); + ~RGBController_EVGACLC(); + + void SetupZones(); + + void ResizeZone(int zone, int new_size); + + void DeviceUpdateLEDs(); + void UpdateZoneLEDs(int zone); + void UpdateSingleLED(int led); + + void DeviceUpdateMode(); + +private: + EVGACLCController* controller; + std::string det_name; + unsigned char cal_r; + unsigned char cal_g; + unsigned char cal_b; + + void InitModes(); + void LoadCalibration(); + void SaveCalibration(); + unsigned char CalibrateChannel(unsigned char value, unsigned char scale); +};