Skip to content

Instantly share code, notes, and snippets.

@zougloub
Last active November 12, 2016 07:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zougloub/8ec5409e614a31548d4f712124e228b2 to your computer and use it in GitHub Desktop.
Save zougloub/8ec5409e614a31548d4f712124e228b2 to your computer and use it in GitHub Desktop.
Simple H100i GTX controller script - puzzle du jour
#!/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