huge aio logic rewrite
This commit is contained in:
@@ -13,13 +13,24 @@
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
// roughly converts fan speed to duty
|
||||
#define SPD2DTY(spd) MAX(0, MIN(spd * 100 / max_fan_speed, 100))
|
||||
struct aio_params_t {
|
||||
struct {
|
||||
uint8_t min_duty;
|
||||
uint8_t max_duty;
|
||||
bool fan_stop;
|
||||
float setpoint;
|
||||
struct {
|
||||
float Ki;
|
||||
float Kd;
|
||||
} ctrl;
|
||||
} fans;
|
||||
};
|
||||
|
||||
enum aio_pump_mode_t {
|
||||
QUIET,
|
||||
BALANCED,
|
||||
EXTREME
|
||||
EXTREME,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
enum aio_color_mode_t : uint8_t {
|
||||
@@ -44,9 +55,7 @@ struct aio_t {
|
||||
libusb_device_handle* handle;
|
||||
enum aio_pump_mode_t pump_mode;
|
||||
float water_temp;
|
||||
float t_quiet;
|
||||
float t_balanced;
|
||||
float t_extreme;
|
||||
uint16_t pump_counter;
|
||||
uint16_t pump_speed;
|
||||
uint16_t fan_speed[2];
|
||||
};
|
||||
|
||||
165
src/aio.c
165
src/aio.c
@@ -1,75 +1,128 @@
|
||||
#include "aio.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static constexpr uint16_t usb_timeout = 250;
|
||||
static constexpr uint8_t buf_size = 32; // needs 32 bc of overflow issue on these aios?
|
||||
|
||||
static constexpr uint16_t max_fan_speed = 1200;
|
||||
static constexpr uint8_t max_curve_points = 7;
|
||||
|
||||
static struct aio_params_t params = {
|
||||
.fans = {
|
||||
.setpoint = 38.5f,
|
||||
.min_duty = 20,
|
||||
.max_duty = 100,
|
||||
.fan_stop = true,
|
||||
.ctrl = {
|
||||
.Ki = 0.1f,
|
||||
.Kd = 10.0f
|
||||
},
|
||||
}};
|
||||
|
||||
// posts cmd to the aio, puts n bytes of response inside resp buffer
|
||||
#define POST_AND_GET_RESP(cmd, n) uint8_t req = cmd; uint8_t resp[buf_size]; aio_post(dev, &req, 1, resp, n)
|
||||
#define POST_AND_GET_RESP(cmd, n) \
|
||||
uint8_t req = cmd; \
|
||||
uint8_t resp[buf_size]; \
|
||||
aio_post(dev, &req, 1, resp, n)
|
||||
|
||||
// posts 2 bytes of cmd to aio, puts n bytes of response inside resp buffer
|
||||
#define POST_AND_GET_RESP2(cmd1, cmd2, n) uint8_t req[2] = {cmd1, cmd2}; uint8_t resp[buf_size]; aio_post(dev, req, 2, resp, n)
|
||||
#define POST_AND_GET_RESP2(cmd1, cmd2, n) \
|
||||
uint8_t req[2] = {cmd1, cmd2}; \
|
||||
uint8_t resp[buf_size]; \
|
||||
aio_post(dev, req, 2, resp, n)
|
||||
|
||||
static void aio_usb_claim(libusb_device_handle* dev) {
|
||||
// set the duty of both fans at once
|
||||
#define SET_FANS_DUTY(dev, duty) \
|
||||
const uint8_t d = {duty}; \
|
||||
aio_set_fan_duty(dev, &d)
|
||||
|
||||
static inline float clamp(const float value, const float min, const float max) {
|
||||
return fmaxf(fminf(value, max), min);
|
||||
}
|
||||
|
||||
|
||||
static void aio_usb_claim(libusb_device_handle *dev) {
|
||||
int ret = libusb_claim_interface(dev, 0);
|
||||
if (ret != 0) {
|
||||
applog(LOG_ERR, "failed to claim AIO device: %s", libusb_error_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = libusb_control_transfer(dev,(LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE),0x02,0x02,0, nullptr, 0, usb_timeout);
|
||||
ret = libusb_control_transfer(dev, (LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE), 0x02, 0x02, 0, nullptr, 0, usb_timeout);
|
||||
if (ret < 0)
|
||||
applog(LOG_ERR, "AIO clear-to-send failed: %s", libusb_error_name(ret));
|
||||
}
|
||||
|
||||
static void aio_usb_release(libusb_device_handle* dev) {
|
||||
static void aio_usb_release(libusb_device_handle *dev) {
|
||||
libusb_release_interface(dev, 0);
|
||||
}
|
||||
|
||||
static void aio_post(libusb_device_handle *dev, uint8_t *req, const int req_len, uint8_t *resp, const int resp_len) {
|
||||
// applog(LOG_DBG, "writing to aio, %02x", req[0]);
|
||||
libusb_bulk_transfer(dev, 0x01, req, req_len, nullptr, usb_timeout);
|
||||
libusb_bulk_transfer(dev, 0x81, resp, resp_len, nullptr, usb_timeout);
|
||||
}
|
||||
|
||||
static float aio_get_water_temp(libusb_device_handle* dev) {
|
||||
static float aio_get_water_temp(libusb_device_handle *dev) {
|
||||
static float last_read;
|
||||
POST_AND_GET_RESP(0xa9, 6);
|
||||
return (float)resp[3] + (float)resp[4] / 10.0f;
|
||||
const float temp = (float)resp[3] + (float)resp[4] / 10.0f;
|
||||
if (temp > 10.0f && temp < 65.0f)
|
||||
last_read = temp;
|
||||
return last_read;
|
||||
}
|
||||
|
||||
static uint16_t aio_get_pump_speed(libusb_device_handle* dev) {
|
||||
static uint16_t aio_get_pump_speed(libusb_device_handle *dev) {
|
||||
POST_AND_GET_RESP(0x31, 5);
|
||||
return resp[3] << 8 | resp[4];
|
||||
}
|
||||
|
||||
static enum aio_pump_mode_t aio_get_pump_mode(libusb_device_handle* dev) {
|
||||
static enum aio_pump_mode_t aio_get_pump_mode(libusb_device_handle *dev) {
|
||||
POST_AND_GET_RESP(0x33, 4);
|
||||
return (enum aio_pump_mode_t)resp[3];
|
||||
}
|
||||
|
||||
static void aio_get_fan_speed(libusb_device_handle* dev, uint16_t *speeds) {
|
||||
static void aio_get_fan_speed(libusb_device_handle *dev, uint16_t *speeds) {
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
POST_AND_GET_RESP2(0x41, i, 6);
|
||||
speeds[i] = resp[4] << 8 | resp[5];
|
||||
}
|
||||
}
|
||||
|
||||
static char* aio_get_firmware_version(libusb_device_handle* dev, char* buf) {
|
||||
static void aio_set_fan_duty(libusb_device_handle *dev, const uint8_t *duty) {
|
||||
static uint8_t last_duty = 255;
|
||||
if (duty[0] == last_duty)
|
||||
return;
|
||||
uint8_t req[3] = {0x42, 0, 0};
|
||||
uint8_t resp[buf_size];
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
req[1] = i;
|
||||
req[2] = MIN(MAX(duty[i], 0), 100);
|
||||
if (params.fans.fan_stop && req[2] <= params.fans.min_duty)
|
||||
req[2] = 0;
|
||||
aio_post(dev, req, sizeof(req), resp, buf_size);
|
||||
last_duty = req[2];
|
||||
}
|
||||
}
|
||||
|
||||
static char *aio_get_firmware_version(libusb_device_handle *dev, char *buf) {
|
||||
POST_AND_GET_RESP(0xaa, buf_size);
|
||||
sprintf(buf, "%d.%d.%d.%d", resp[3], resp[4], resp[5], resp[6]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void aio_set_pump_mode(libusb_device_handle* dev, const enum aio_pump_mode_t mode) {
|
||||
static void aio_set_pump_mode(libusb_device_handle *dev, const enum aio_pump_mode_t mode) {
|
||||
static enum aio_pump_mode_t last_mode = UNKNOWN;
|
||||
if (mode == last_mode)
|
||||
return;
|
||||
applog(LOG_DBG, "setting pump to %u", mode);
|
||||
POST_AND_GET_RESP2(0x32, mode, 6);
|
||||
last_mode = mode;
|
||||
}
|
||||
|
||||
static void aio_set_color(libusb_device_handle* dev, const enum aio_color_mode_t mode, const uint8_t speed, const struct rgb_t* colors, const uint8_t color_count) {
|
||||
static void aio_set_color(libusb_device_handle *dev, const enum aio_color_mode_t mode, const uint8_t speed, const struct rgb_t *colors, const uint8_t color_count) {
|
||||
if (colors == nullptr) // NOLINT no its not
|
||||
return;
|
||||
|
||||
@@ -102,7 +155,10 @@ static void aio_set_color(libusb_device_handle* dev, const enum aio_color_mode_t
|
||||
aio_post(dev, req, 2, resp, buf_size);
|
||||
}
|
||||
|
||||
static void aio_set_fan_curve(libusb_device_handle* dev, const struct curve_point_t *curve, const uint8_t count) {
|
||||
static void aio_set_fan_curve(libusb_device_handle *dev, const struct curve_point_t *curve, const uint8_t count) {
|
||||
if (curve == nullptr)
|
||||
return;
|
||||
|
||||
if (count < 1 || count > max_curve_points) {
|
||||
applog(LOG_ERR, "aio: curve must have 1-%d points", max_curve_points);
|
||||
return;
|
||||
@@ -137,27 +193,16 @@ void aio_detect(struct aio_t *aio) {
|
||||
return;
|
||||
}
|
||||
|
||||
aio->t_quiet = 36.5f;
|
||||
aio->t_balanced = 38.5f;
|
||||
aio->t_extreme = 41.0f;
|
||||
|
||||
const struct curve_point_t fan_curve[] = {
|
||||
{ 34, SPD2DTY(0)},
|
||||
{ 35, SPD2DTY(360) },
|
||||
{ 39, SPD2DTY(750) },
|
||||
{ 46, SPD2DTY(1000) },
|
||||
{ 50, SPD2DTY(max_fan_speed) }
|
||||
};
|
||||
|
||||
/* detaches other software that might take the device? */
|
||||
if (libusb_kernel_driver_active(aio->handle, 0))
|
||||
libusb_detach_kernel_driver(aio->handle, 0);
|
||||
|
||||
aio_usb_claim(aio->handle);
|
||||
applog(LOG_INF, "found Corsair AIO device at %04x:%04x, fw ver: %s", AIO_USB_VID, AIO_USB_PID, aio_get_firmware_version(aio->handle, (char[16]){0}));
|
||||
aio_set_pump_mode(aio->handle, QUIET); // needed for init ig?
|
||||
applog(LOG_INF, "found Corsair AIO device at %04x:%04x, fw ver: %s. Set point is %.1fC, fan stop %s", AIO_USB_VID, AIO_USB_PID, aio_get_firmware_version(aio->handle, (char[16]){0}), (double)params.fans.setpoint, params.fans.fan_stop ? "ENABLED" : "DISABLED");
|
||||
aio_set_pump_mode(aio->handle, QUIET); // needed for init ig?
|
||||
aio_set_color(aio->handle, AIO_COLOR_FIXED, 0, nullptr, 0); // so it doenst complain the func is unused
|
||||
aio_set_fan_curve(aio->handle, fan_curve, sizeof(fan_curve) / sizeof(fan_curve[0]));
|
||||
// aio_set_fan_curve(aio->handle, fan_curve, sizeof(fan_curve) / sizeof(fan_curve[0]));
|
||||
aio_set_fan_curve(aio->handle, nullptr, 0);
|
||||
aio_usb_release(aio->handle);
|
||||
}
|
||||
|
||||
@@ -172,21 +217,63 @@ void aio_update(struct aio_t *aio) {
|
||||
aio->pump_mode = aio_get_pump_mode(aio->handle);
|
||||
aio_get_fan_speed(aio->handle, aio->fan_speed);
|
||||
|
||||
if ((aio->pump_mode == QUIET && aio->water_temp > aio->t_balanced) || (aio->pump_mode == EXTREME && aio->water_temp < aio->t_balanced))
|
||||
aio_set_pump_mode(aio->handle, BALANCED);
|
||||
/* fan logic */
|
||||
static float fan_duty = 0.0f;
|
||||
static float prev_temp = 36.0f; // 20 avoids huge jump of slope at boot
|
||||
const float error = aio->water_temp - params.fans.setpoint;
|
||||
const float slope = aio->water_temp - prev_temp;
|
||||
prev_temp = aio->water_temp;
|
||||
|
||||
if (aio->pump_mode == BALANCED) {
|
||||
if (aio->water_temp > aio->t_extreme)
|
||||
aio_set_pump_mode(aio->handle, EXTREME);
|
||||
if (aio->water_temp < aio->t_quiet)
|
||||
aio_set_pump_mode(aio->handle, QUIET);
|
||||
fan_duty += error * params.fans.ctrl.Ki + slope * params.fans.ctrl.Kd;
|
||||
fan_duty = clamp(fan_duty, params.fans.min_duty, params.fans.max_duty);
|
||||
|
||||
applog(LOG_DBG, "Fan duty %.3f, error %.4f, slope %.4f", (double)fan_duty, (double)error * (double)params.fans.ctrl.Ki, (double)slope * (double)params.fans.ctrl.Kd);
|
||||
SET_FANS_DUTY(aio->handle, fan_duty);
|
||||
|
||||
/* pump logic */
|
||||
switch (aio->pump_mode) {
|
||||
case QUIET:
|
||||
if (fan_duty > 40)
|
||||
aio->pump_counter++;
|
||||
else
|
||||
aio->pump_counter = 0;
|
||||
|
||||
if (aio->pump_counter > 60)
|
||||
aio_set_pump_mode(aio->handle, BALANCED);
|
||||
break;
|
||||
|
||||
case BALANCED:
|
||||
if (fan_duty > 65)
|
||||
aio->pump_counter++;
|
||||
else if (fan_duty < 35)
|
||||
aio->pump_counter--;
|
||||
|
||||
if (aio->pump_counter > 120)
|
||||
aio_set_pump_mode(aio->handle, EXTREME);
|
||||
else if (aio->pump_counter == 0)
|
||||
aio_set_pump_mode(aio->handle, QUIET);
|
||||
break;
|
||||
|
||||
case EXTREME:
|
||||
if (fan_duty < 60)
|
||||
aio->pump_counter--;
|
||||
|
||||
if (aio->pump_counter < 60)
|
||||
aio_set_pump_mode(aio->handle, BALANCED);
|
||||
break;
|
||||
|
||||
case UNKNOWN:
|
||||
default:
|
||||
aio_set_pump_mode(aio->handle, BALANCED);
|
||||
break;
|
||||
}
|
||||
applog(LOG_DBG, "aio->pump_counter is %d", aio->pump_counter);
|
||||
|
||||
aio_usb_release(aio->handle);
|
||||
}
|
||||
|
||||
void aio_close(const struct aio_t *aio) {
|
||||
libusb_attach_kernel_driver(aio->handle, 0); // reattach so liquidctl still works
|
||||
libusb_attach_kernel_driver(aio->handle, 0); // reattach so liquidctl still works
|
||||
libusb_close(aio->handle);
|
||||
libusb_exit(nullptr);
|
||||
applog(LOG_INF, "AIO device closed");
|
||||
|
||||
Reference in New Issue
Block a user