"""liquidctl driver for the ASUS Ryuo liquid coolers. Copyright Bloodhundur and contributors SPDX-License-Identifier: GPL-3.0-or-later """ import logging from typing import List from liquidctl.driver.usb import UsbHidDriver from liquidctl.error import ExpectationNotMet from liquidctl.util import clamp, rpadlist _LOGGER = logging.getLogger(__name__) _REPORT_LENGTH = 65 _PREFIX = 0xEC _CMD_SET_FAN_SPEED = 0x2A _REQUEST_GET_FIRMWARE = (0x82, 0x02) _STATUS_FIRMWARE = "Firmware version" class AsusRyuo(UsbHidDriver): """Driver for ASUS Ryuo I 240 (fan control only).""" _MATCHES = [ (0x0B05, 0x1887, "ASUS Ryuo I 240", {}), ] def initialize(self, **kwargs): msg = self._request(*_REQUEST_GET_FIRMWARE) fw_string = bytes(msg[2:]).split(b"\x00")[0].decode("ascii", errors="ignore") return [ (_STATUS_FIRMWARE, fw_string, ""), ] def get_status(self, **kwargs): _LOGGER.info("No status available") def set_fixed_speed(self, channel, duty, **kwargs): if channel not in ("fans", "fan"): raise ValueError("Only 'fans' channel is supported") duty = clamp(duty, 0, 100) _LOGGER.info(f"Setting fan speed to {duty}%") self._write([_PREFIX, _CMD_SET_FAN_SPEED, duty]) def _request(self, request_header: int, response_header: int) -> List[int]: self.device.clear_enqueued_reports() self._write([_PREFIX, request_header]) return self._read(response_header) def _read(self, expected_header=None) -> List[int]: msg = self.device.read(_REPORT_LENGTH) if msg[0] != _PREFIX: raise ExpectationNotMet("Unexpected report prefix") if expected_header is not None and msg[1] != expected_header: raise ExpectationNotMet("Unexpected report header") return msg def _write(self, data: List[int]): self.device.write(rpadlist(data, _REPORT_LENGTH, 0))