Last active
November 12, 2016 07:59
-
-
Save zougloub/8ec5409e614a31548d4f712124e228b2 to your computer and use it in GitHub Desktop.
Simple H100i GTX controller script - puzzle du jour
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 vi:noet | |
# Corsair H100i GTX driver | |
import struct, time, binascii, sys, io | |
import usb | |
import scipy.interpolate | |
VIDPIDS = [ | |
(0x1b1c, 0x0c03), # H100i GTX | |
] | |
# LUT obtained by looking at the data while observing the Corsair Link app | |
# FIXME wrap-around happens at cold temperatures | |
lut = ( | |
(0xe8, 38.4), | |
(0xe7, 38.5), | |
(0xe6, 38.7), | |
(0xe0, 39.5), | |
(0xdf, 39.7), | |
(0xda, 40.4), | |
(0xd9, 40.5), | |
(0xd6, 41.0), | |
(0xd5, 41.1), | |
(0xd4, 41.2), | |
(0xd2, 41.5), | |
(0xd0, 41.8), | |
(0xcd, 42.3), | |
(0xca, 42.8), | |
(0xc4, 43.5), | |
(0xbc, 45.0), | |
(0xb4, 46.6), | |
(0xa6, 48.8), | |
(0x9e, 50.4), | |
(0x96, 52.0), | |
(0x8d, 54.0), | |
(0x89, 54.8), | |
(0x86, 55.5), | |
(0x84, 56.0), | |
(0x83, 56.25), | |
(0x82, 56.50), | |
(0x81, 56.75), | |
(0x80, 57.0), | |
(0x7f, 57.25), | |
(0x7e, 57.5), | |
(0x7d, 57.75), | |
(0x7c, 58.0), | |
(0x7b, 58.25), | |
(0x7a, 58.5), | |
(0x79, 58.75), | |
(0x78, 59.0), | |
(0x77, 59.25), | |
(0x76, 59.5), | |
(0x75, 59.75), | |
(0x74, 60.0), | |
) | |
xs = [x[0] for x in lut] | |
ys = [x[1] for x in lut] | |
temp_lut = scipy.interpolate.interp1d(xs, ys, | |
kind="linear", | |
fill_value="extrapolate", | |
) | |
class H100iGTXDevice(object): | |
def __init__(self, device): | |
self._device = device | |
self._bus = device.bus | |
if self._device.is_kernel_driver_active: | |
for interface in [0, 1]: | |
try: | |
self._device.detach_kernel_driver(interface) | |
except usb.core.USBError: | |
pass | |
self._device.reset() | |
time.sleep(1.0) | |
self._device.set_configuration(1) | |
self._ctr = 0 | |
self._status = None | |
self._mode = "quiet" | |
def process(self): | |
self._ctr += 1 | |
""" | |
try: | |
self._device.ctrl_transfer( | |
bmRequestType=0x40, | |
bRequest=0x00, | |
wValue=0xffff, | |
wIndex=0x00, | |
data_or_wLength=b"", | |
) | |
except: | |
pass | |
""" | |
self._device.ctrl_transfer( | |
bmRequestType=0x40, | |
bRequest=0x02, | |
wValue=0x0002, | |
wIndex=0x00, | |
data_or_wLength=b"", | |
) | |
""" | |
try: | |
res = self._device.ctrl_transfer( | |
bmRequestType=0xc0, | |
bRequest=0xff, | |
wValue=0x370b, | |
wIndex=0x00, | |
data_or_wLength=1, | |
) | |
print(res) | |
except: | |
pass | |
try: | |
self._device.ctrl_transfer( | |
bmRequestType=0x40, | |
bRequest=0x00, | |
wValue=0x0000, | |
wIndex=0x00, | |
data_or_wLength=b"", | |
) | |
except: | |
pass | |
""" | |
self._device.ctrl_transfer( | |
bmRequestType=0x40, | |
bRequest=0x02, | |
wValue=0x0001, | |
wIndex=0x00, | |
data_or_wLength=b"", | |
) | |
self._device.ctrl_transfer( | |
bmRequestType=0x40, | |
bRequest=0x02, | |
wValue=0x0001, | |
wIndex=0x00, | |
data_or_wLength=b"", | |
) | |
if self._ctr % 3 == 0: | |
# Perform fan control | |
# usb.endpoint_number.direction == 0 && usb.capdata && usb.data_len == 14 | |
# 1100 006400000000 282800000000 | |
# | |
# WIP | |
setpoints = { | |
0: 100, | |
100: 100, | |
} | |
if self._mode != "aggressive": | |
setpoints = { | |
0: 10, | |
30: 20, | |
40: 60, | |
50: 100, | |
} | |
cmd = bytearray([0x11, 0x00] + ([0,0] * 6)) | |
vals = sorted(setpoints.items()) | |
for idx_setpoint, (temp, pct) in enumerate(vals): | |
cmd[2+0+idx_setpoint] = int(round(temp)) | |
cmd[2+6+idx_setpoint] = int(round(pct)) | |
self._device.write(0x02, cmd) | |
elif self._ctr % 3 == 1: | |
# Perform pump speed control | |
# usb.endpoint_number.direction == 0 && usb.capdata && usb.capdata[0] == 13 | |
data = bytearray([ | |
0x13, | |
0x42 if self._mode == "aggressive" else 0x28, # 0x28: quiet, 0x42: aggressive | |
]) | |
self._device.write(0x02, data) | |
if 1:#else: | |
# Perform pump light control | |
# usb.endpoint_number == 0x02 && usb.capdata && usb.data_len == 19 | |
# 10 ffffff00ffffff00002d0a05010000010001 | |
data = bytearray([ | |
0x10, | |
0x0f, # R 0x00-0xff | |
0xff, # G | |
0x0f, # B | |
0x00, | |
0xff, | |
0xff, | |
0x8d, # alarm R | |
0x8d, # alarm G | |
0x8d, # alarm B | |
0x2d, # alarm value degC | |
0x0a, | |
0x05, | |
0x01, # lighting enabled | |
0x00, | |
0x00, | |
0x01, # warning enabled | |
0x00, | |
0x01, | |
]) | |
self._device.write(0x02, data) | |
l = 32 | |
try: | |
data = self._device.read(0x82, l, timeout=100) | |
except usb.core.USBError as e: | |
raise | |
# Note: There's a detection "dead zone" at ~ 2000 rpm | |
fan_rpm = (data[0] << 8) | data[1] | |
pump_rpm = (data[8] << 8) | data[9] | |
temp = temp_lut(data[3]) | |
temp_degC = data[10] + (data[14] - 0) * 0.1 | |
fw_ver = data[23:27] | |
if temp > 40.5 and self._mode == "quiet": | |
self._mode = "aggressive" | |
elif temp < 39.5 and self._mode == "aggressive": | |
self._mode = "quiet" | |
out = [] | |
for idx_b, b in enumerate(data): | |
if idx_b in (0,1, 3, 8,9, 23,24,25,26): # known | |
out.append("\x1B[32m") | |
elif idx_b in (10,14,): # known | |
out.append("\x1B[36m") | |
elif self._status is not None and b != self._status[idx_b]: | |
out.append("\x1B[33m") | |
out.append("%02x\x1B[0m " % b) | |
self._status = data | |
try: | |
with io.open("/sys/class/hwmon/hwmon0/temp1_input", "rb") as f: | |
cputemp = "cputemp: %6.2f" % (1e-3 * int(f.read().decode().rstrip())) | |
except: | |
cputemp = "" | |
print("%s Fan: %4d pump: %4d temp: %.2f/%.2f %s %s" \ | |
% ("".join(out), fan_rpm, pump_rpm, temp, temp_degC, cputemp, self._mode)) | |
res = self._device.ctrl_transfer( | |
bmRequestType=0x40, | |
bRequest=0x02, | |
wValue=0x0004, | |
wIndex=0x00, | |
data_or_wLength=0, | |
) | |
class H100iGTXHandler(object): | |
def __init__(self): | |
self._devices = [] | |
for vid, pid in VIDPIDS: | |
self._devices += [H100iGTXDevice(device) for device in \ | |
usb.core.find(find_all=True, idVendor=vid, idProduct=pid)] | |
def get_devices(self): | |
""" | |
Get a list of all devices attached to this handler | |
""" | |
return self._devices | |
if __name__ == '__main__': | |
import sys, io, time, datetime | |
m = H100iGTXHandler() | |
coolers = m.get_devices() | |
assert len(coolers) == 1, tempers | |
cooler = coolers[0] | |
while True: | |
t = cooler.process() | |
time.sleep(1.0) | |
""" | |
Notes: | |
ID 1b1c:0c03 Corsair | |
Device Descriptor: | |
bLength 18 | |
bDescriptorType 1 | |
bcdUSB 1.10 | |
bDeviceClass 0 | |
bDeviceSubClass 0 | |
bDeviceProtocol 0 | |
bMaxPacketSize0 64 | |
idVendor 0x1b1c Corsair | |
idProduct 0x0c03 | |
bcdDevice 1.00 | |
iManufacturer 1 Corsair Components, Inc. | |
iProduct 2 H100iGTX Cooler | |
iSerial 3 7289_1.0 | |
bNumConfigurations 1 | |
Configuration Descriptor: | |
bLength 9 | |
bDescriptorType 2 | |
wTotalLength 32 | |
bNumInterfaces 1 | |
bConfigurationValue 1 | |
iConfiguration 0 | |
bmAttributes 0x80 | |
(Bus Powered)CtrlAltDelBurstAction | |
MaxPower 50mA | |
Interface Descriptor: | |
bLength 9 | |
bDescriptorType 4 | |
bInterfaceNumber 0 | |
bAlternateSetting 0 | |
bNumEndpoints 2 | |
bInterfaceClass 255 Vendor Specific Class | |
bInterfaceSubClass 0 | |
bInterfaceProtocol 0 | |
iInterface 0 | |
Endpoint Descriptor: | |
bLength 7 | |
bDescriptorType 5 | |
bEndpointAddress 0x02 EP 2 OUT | |
bmAttributes 2 | |
Transfer Type Bulk | |
Synch Type None | |
Usage Type Data | |
wMaxPacketSize 0x0040 1x 64 bytes | |
bInterval 0 | |
Endpoint Descriptor: | |
bLength 7 | |
bDescriptorType 5 | |
bEndpointAddress 0x82 EP 2 IN | |
bmAttributes 2 | |
Transfer Type Bulk | |
Synch Type None | |
Usage Type Data | |
wMaxPacketSize 0x0040 1x 64 bytes | |
bInterval 0 | |
Device Status: 0x0000 | |
(Bus Powered) | |
Communication: | |
- Commands: | |
- 0x11 : fan control | |
- 0x10 : pump control | |
- 0x13 + (0x28 or 0x42) : pump quiet or aggressive | |
- 0x14 + 3 times 0x00 : unknown | |
- Status: bulk message of length 32 is the only incoming message | |
usb.endpoint_number.direction == 1 && usb.capdata | |
:: | |
047400da887e18fb05462811af100421df88ffbe27ff840203000000011d9600 | |
xxxx xx xxxx xxxxxxxx | |
Behaviour: | |
- Note that speed is altered by the motherboard control. | |
""" | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment