diff --git a/include/aio.h b/include/aio.h index 711a073..380bede 100644 --- a/include/aio.h +++ b/include/aio.h @@ -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]; }; diff --git a/src/aio.c b/src/aio.c index 50d19c9..7acc1a2 100644 --- a/src/aio.c +++ b/src/aio.c @@ -1,75 +1,128 @@ #include "aio.h" #include "log.h" +#include #include #include -#include 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");