Jump to content
Corsair Community

Developers: The Corsair Link USB protocol!


CFSworks

Recommended Posts

  • 2 weeks later...
  • Replies 185
  • Created
  • Last Reply

Top Posters In This Topic

Wow, I actually have people registering on the forum to thank me. Never had that happen before... :)

 

Well, get used to it, bacause it's happening again, may you be blessed with long life!

 

And if you manage to work it out, please inform the Linux community if you could.

 

Good Luck!

Link to comment
Share on other sites

I cant get into much detail...but we are strictly limited as to what we can say.

 

RAM GUY,

 

I used to be a support engineer and have been in your shoes more times than I care to remember, so I understand your position perfectly.

 

The last time I heard from your product manager about this (Corsair George, I believe, is is user-name?), he gave some non-committal answers, misunderstood a question as a complaint, and (almost angrily) closed the thread.

 

That was back in June 2011. It's been two years. I know your constraints have not changed, and I'm not complaining about that.

 

I registered today for two purposes. One, was to thank CFSworks, above, for his work.

 

The second, is to ask you to "intimate" to management, that the money of Linux users is just as green as anyone else's, and many of us are tired of being dismissed as "all 31 of them."

 

Nobody is expecting Corsair to create the Linux-compatible software -- although, if you did, I guarantee you that Corsair would face no one's objection to disclose everything about it, down to the last semicolon in the code.

 

Nobody is expecting Corsair to violate agreements with other parties and divulge proprietary information. Nobody is even expecting "Free software" -- a frequent misrepresentation of Linux users. But we did pay the same price as everyone else, and many of us are sick and tired of this attitude.

 

If Corsair George had told us to go pound sand, at least it would have been honest.

 

Thank you.

Link to comment
Share on other sites

  • Corsair Employee

r_avital

First thanks for adding your thoughts and comments and I cannot speak for George per say. But I can tell you that we would like to have this worked out a long time ago but because of License issues with the Code it was just not possible. So we as a company and we as Enthusiasts with in the company agree with you and want to share it for Open source Not only for Linux users but it would open it up to a wide verity of uses beyond just computers. So it is definitely something we want to do, however it has been more of a brick wall than we would have liked it to be.

And I dont think George meant anything personal when he closed the thread he just did not want it to explode into something else.

So if it came off that way I know that was not his intent and I apologize for that.

I cant make any promises as to what will happen in the future but we would like to be able to open this code up. And as far as I know we have been looking into that for future products but the resources have just not been there to develop our own code which is what will have to happen for us to be able to release an open source product.

Link to comment
Share on other sites

  • 2 weeks later...

I'm glad to see this thread has some activity. I don't want to jump ship to a different solution but if I don't see a new more bug free software or even a kludge thrown together by one of these dedicated enthusiasts, I'm gonna have to abandon all my link gear. It is just getting ridiculous. I'm sorry I'm venting, but I know I'm not alone. I feel the more people speak out, the better the chances that the people at the top will get the point. I used to be a corsair fanboy, but no more. I'm not pointing fingers at the corsair reps that are actually communicating with us. I just wanted to voice my two cents as I'm frustrated. I've given the corsair link system 9 months to win me over, but it's just too buggy. If someone out there can throw together some homebrewed software, please post it here. I've tried multiple OSs, compatibility settings, fresh installs, workarounds posted in the forums; nothing has shone me a product that is worthy of being released to the public.

Sorry for the rant, I just needed to be heard and I want to promote this thread so that a solution can be found.

Link to comment
Share on other sites

Hey all!

Hey CFSworks; great job so far.... :cool:

Some additional information to keep this thread moving in the positive direction.

 

Corsair Link protocol overview

 

As mentioned, the USB device uses a simple 64-byte HID "raw" report; the rawhid source code can be a useful starting point to establish low-level communication if a device isn't "just available" (e.g., on Windows).

 

http://www.pjrc.com/teensy/rawhid.html

 

The ReportID is always zero and is length-prefixed with a number of SMBus-like packets within the HID packet. Each packet has an index (which should be from 20~255), SMBus sub-packet type followed by any write data, if the sub-packet is writing data. Block types also have a length, and it appears the "quick" and "byte" modes are unsupported (only CmdByte, CmdWord and CmdBlock appear to be used).

 

H80i/H100i Register set:

 

[TABLE=head]__Number__|R/W|__Length__| Description

00 | R | 1 byte | Device ID - (H80 0x37, Cooling node 0x38, Lighting node 0x39, H100 0x3A, 80i 0x3B, 100i 0x3c -- this field as well as the version are common on all C-Link devices; but the rest aren't

01 | R | 2 bytes | Firmware Version in BCD (for example 1.0.5 is 0x1005, or 0x05, 0x10 in little endianess)

02 | R | 8 bytes | Product name, zero-terminated - only present on the H80i and H100i

03 | R | 1 | Status, 0 okay, 0xFF bad

04 | RW | 1 byte | Select current LED

05 | R | 1 byte | Number of LEDs

06 | RW | 1 byte | LED mode - 0x00 for static color, 0x40 for 2-color cycle, 0x80 for 4-color, 0xC0 for temperature mode; low nibble defines cycle speed or the temperature channel to use (0 internal sensor, 7 manual)

07 | R | 3 bytes | LED current color, RGB color of the selected LED

08 | RW | 2 bytes | In temperature controlled mode (0xC0) this defines the colour to use to with the below gradients

09 | RW | 6 bytes | LED temperature-mode temperatures: 3 temperatures; used when cycle mode is 0xc0

0a | RW | 9 bytes | LED temperature-mode colors: RGBx3 colors, corresponding to temperatures in register above

0b | RW | 12 bytes | LED cycle colors: RGBx4 colors (only first color used if cycle mode set to 00, first two if 4b, ignored if c0)

0c | RW | 1 byte | Select active temperature sensor

0d | R | 1 byte | Number of temperature sensors

0e | R | 2 bytes | Temperature as measured by selected sensor

0f | RW | 2 bytes | Temperature limit (when the temperature goes over this, status is set to 0xff)

10 | RW | 1 byte | Select current fan; for H100i, 0-3 are the fans, 4 is pump

11 | R | 1 byte | Number of fans

12 | RW | 1 byte | Fan mode; 02=fixed PWM, 04=fixed RPM, 06=default, 08=quiet, 0a=balanced, 0c=performance, 0e=custom; high bit is one when fan is detected, low bit is one when the fan is 4-pin, bits 6~4 define the temperautre channel to use in "curve" modes, 0 internal and 7 manual

13 | RW | 1 byte | Fan fixed PWM, 0-255, only used if fan mode is 02

14 | RW | 2 bytes | Fan fixed RPM; when fan mode is 04, controller will target this RPM

15 | RW | 2 bytes | Report external temperature to fan controller - used for controlling fans via external sensors

16 | R | 2 bytes | Current fan RPM

17 | R | 2 bytes | Maximum RPM recorded since power-on

18 | RW | 2 bytes | Fan under speed threshold

19 | RW | 10 bytes | Fan RPM table, for custom (0e) mode: array of 5 RPMs

1a | RW | 10 bytes | Fan temp table, for custom (0e) mode: array of 5 temperatures

[/TABLE]

 

Note: All data is little-endian.

Temperatures are reported in units of 1/256th of a degree Celsius.

  • Like 1
Link to comment
Share on other sites

  • 3 weeks later...

For the people who are still interested...

 

H80/H100/Cooling Node Register set:

[TABLE=head]Register|Hex|Length|Read|Description

Device ID|00|2|RO|H80 0x37; Cooling node 0x38; H100 0x3A

Firmware Version|01|2|RO|#.#.## that is 0x1011 is version 1.0.17

System Status|02|2|RO|Status 0 okay 0xFF bad

Status Led|04|2||Each bit = led; 0 = Extreme, 1 = Perf, 2 = Quiet, 3 = CLink Dude

Temps|07|8|RW|Current temp for four channels (signed fixed point)

RPM Current|0B|10|RO|Current fan speed

RPM Max|10|10|RO|Measured fan maximum

Input Temp|1A|10|RW|Temperature when in one of the curve modes

Fan Control|20 30 40 50 60|2|RW|Bit 7 Fan detected; Bit 6~4 Temp Input; Bit 3~1 Fan Mode; Bit 0 Fan Pins (1 is 4-Pin)

|(each channel)|||Temp Input: 0 Coolant Temp 1 Temp 0 2 Temp 1 3 Temp 2 4 MCU Temp 7 ManualFan

||||Mode: 0 Off 1 PWM 2 RPM 3 Set Point 4 Quiet 5 Performance 6 Extreme 7 User Curve

Fan Target PWM|21 31 41 51 61|2|RW|Current fan speed as a percentage * 2.55 (e.g. 255 == 100%)

Fan Target RPM|22 32 42 52 62|2|RW|Current RPM target

Fan User Curve RPM|23 33 43 53 63|10|RW|User curve profile

Fan User Curve Temp|28 38 48 58 68|10|RW|User curve profile

[/TABLE]

 

Note: Fields which only expect a single byte (e.g., PWM) must be zero-padded.

 

LED Node Register set:

[TABLE=head]Register|Hex|Length*|Read|Description

Device ID|00|1|RO|0x3A for LED Node

Firmware Version|01|2|RO|BCD 0x0910 is version 1.0.9

SystemStatus|03|1|RO|Good is 0; bad is 0xFF

User Mode|04|2x1|RW|0x10 Pulse Mode; 0x01 through 0x0F Built-In Colours; 0x00 C-Link Mode

RGB Out|06|2x3|RO|Current calculated LED output

Temps|0C|2x2|RW|Input temperatures

Cycle Mode|10 30|1|RW|Bit 6~7: 00 - Static; 01 - Two Cycle; 10 - Four Cycle; 11 - Temperature

||||Bit 0~2 Cycle Speed in powers of 2 x 1/8th Second

Cold Warm Hot|11 31|3x5|RW|Temp (two bytes) and RGB (three bytes) for each threshold

Cycle Colours|20 40|4x4|RW|RGB (with one byte padding) for each cycle step

[/TABLE]

 

Note: Lengths may be shown as number of sets x length of each set. For example, there are three sets (one for cold, warm and hot respectively) with five bytes of data for temperature settings. The hex address will be offset byt the BYTE position being written.

 

LED Nodes, Cooling Nodes and the original H80 and H100 all have seuqential registers, meaning that writing more data than the first register will hold will "spill over" into the next register, allowing large block reads and writes.

Link to comment
Share on other sites

Can some one tell me the USB vendor ID and the USB product ID for the H100i? Like you I am not happy with the Link software and I'd like to write my own. I see all of this great work has been done that explains the protocol and I'd like to take advantage of it.
Link to comment
Share on other sites

Can some one tell me the USB vendor ID and the USB product ID for the H100i? Like you I am not happy with the Link software and I'd like to write my own. I see all of this great work has been done that explains the protocol and I'd like to take advantage of it.

PID is always 0x1B1C

VID is 0x0C04 for H80i and H100i

VID is 0x0C02 for the USB Commander

 

They use the same basic protocol, except the Commander's don't have the extra length byte at the very beginning. Here's what I have working so far (C# code, this depends on HidLibrary which you can get from NuGet):

 

[b]public [/b]List<Int32> ListPorts() {
   [b]int [/b]packetIndex = 1; [color="Green"]// skip the report id field (always zero)[/color]
   [b]byte[/b][] packet = [b]new byte[/b][65]; [color="Green"]// size of the packet plus report id[/color]
   [b]if [/b](newProtocol) packet[packetIndex++] = 2; [color="Green"]// newProtocol = (pid == 0xC04)[/color]
   packet[packetIndex++] = ([b]byte[/b])index; [color="Green"]// write the index[/color]
   [b]if [/b](index < 255) index++; [b]else [/b]index = 20; [color="Green"]// and cycle it[/color]
   packet[packetIndex++] = 0x4F; [color="Green"]// command to get the port status[/color]

   [color="Green"]// perform the io[/color]
   device.Write(packet, 500);
   [b]var [/b]inpacket = device.Read(500);

   [color="Green"]// and collect our list of active channels[/color]
   List<Int32> ports = new List<Int32>();
   [b]for [/b]([b]int [/b]portIndex = 0; portIndex < 8; portIndex++) {
       [color="Green"]// any port set to 0xFF is not present, anything else is 'live'[/color]
       [b]if [/b](inpacket.Data[3 + portIndex] != 0xFF)
           ports.Add(portIndex);
   }

   [b]return [/b]ports;
}

[color="Green"]// reportid, length, index, command, register, len
// 0         1       2      3        4         5   <- block
// 0         1       2      3        4             <- byte/word
//           ^-- omitted with old USB Commander (0xC02)

// reportid, index, command/status, data...
// 0         1      2               3
//           ^-- omitted with USB Commander[/color]
[b]public [/b][b]byte[/b][] ReadData([b]int [/b]reg, [b]int [/b]len = 1) {
   [b]int [/b]i = (newProtocol) ? 2 : 1; [color="Green"]// fill in the length when we're done[/color]
   [b]byte[/b][] packet = [b]new byte[/b][65]; [color="Green"]// our standard packet length[/color]
   [b]int [/b]command = (Port << 4) | 1; [color="Green"]// port fills the high nibble, 1 is 'read'[/color]

   [b]if [/b](len == 1) command |= 6; [color="Green"]// read byte[/color]
   [b]else if [/b](len == 2) command |= 8; [color="Green"]// read short[/color]
   [b]else [/b]command |= 10; [color="Green"]// read any length[/color]

   packet[i++] = ([b]byte[/b])index; [color="Green"]// obligatory index field[/color]
   [b]if [/b](index < 255) index++; [b]else [/b]index = 20; [color="Green"]// and cycle[/color]
   packet[i++] = ([b]byte[/b])command; [color="Green"]// command byte[/color]
   packet[i++] = ([b]byte[/b])reg; [color="Green"]// the register we'd like to read[/color]
   if (len > 2) packet[i++] = ([b]byte[/b])len; [color="Green"]// arbitrary length require the length[/color]

   [b]if [/b](newProtocol) packet[1] = ([b]byte[/b])(index - 1); [color="Green"]// add in our total length[/color]

   [color="Green"]// do the io[/color]
   device.Write(packet, 500);
   [b]var [/b]inpacket = device.Read(500);

   [b]if [/b](inpacket != null && inpacket.Status == HidDeviceData.ReadStatus.Success) {
       [color="Green"]// ignore bad packets (handle the null from the caller side)[/color]
       [b]if [/b](inpacket.Data[1] != packet[(newProtocol) ? 2 : 1]) [b]return [/b][b]null[/b];
       [b]if [/b]((inpacket.Data[2] & 240) != 0) [b]return [/b][b]null[/b];

       [color="Green"]// grab our returned data from within the packet[/color]
       [b]byte[/b][] data = [b]new byte[/b][len];
       Buffer.BlockCopy(inpacket.Data, 3, data, 0, len);
       [b]return [/b]data;
   }
   [b]else [/b][b]return [/b][b]null[/b];
}

[color="Green"]// reportid, length, index, command, register, len, data...
// 0         1       2      3        4         5,   6... <- block
// 0         1       2      3        4              5... <- byte/word
//           ^-- omitted with USB Commander (0xC02)[/color]
[b]public [/b][b]bool [/b]WriteData([b]int [/b]reg, [b]byte[/b][] data) {
   [b]int [/b]i = (newProtocol) ? 2 : 1; [color="Green"]// fill in the length when we're done[/color]
   [b]byte[/b][] packet = [b]new byte[/b][65]; [color="Green"]// our standard packet length[/color]
   [b]int [/b]command = (Port << 4); [color="Green"]// port fills the high nibble, no low bit for write[/color]

   [b]if [/b](data.Length == 1) command |= 6; [color="Green"]// byte[/color]
   [b]else if [/b](data.Length == 2) command |= 8; [color="Green"]// short[/color]
   [b]else [/b]command |= 10; [color="Green"]// arbitrary length[/color]

   packet[i++] = ([b]byte[/b])index; [color="Green"]// index and cycle[/color]
   [b]if [/b](index < 255) index++; [b]else [/b]index = 20;
   packet[i++] = ([b]byte[/b])command; [color="Green"]// command field[/color]
   packet[i++] = ([b]byte[/b])reg; [color="Green"]// register we're writing too[/color]
   [b]if [/b](data.Length > 2) packet[i++] = ([b]byte[/b])(data.Length); [color="Green"]// length, if req'd[/color]
   Buffer.BlockCopy(data, 0, packet, i, data.Length); [color="Green"]// data we're writing[/color]
   i += data.Length; [color="Green"]// position in the packet[/color]

   [b]if [/b](newProtocol) packet[1] = (byte)(index - 1); [color="Green"]// prefix packet length[/color]

   device.Write(packet, 500); [color="Green"]// do the io[/color]
   [b]var [/b]inpacket = device.Read(500);

   [color="Green"]// return true if the write was successful[/color]
   [b]return [/b](inpacket != null && inpacket.Status == HidDeviceData.ReadStatus.Success);
}

From here you need to just enumerate everything and push that info up to some sort of UI. It's important that you not try to talk to the same device using multiple threads, that breaks things, so either stick to single threading, single threading per USB device, or use locks to ensure that the write-read sequence is always unbroken.

 

Apologies for the messy code.

 

The code is very "C" like and should be easy enough to port to Linux (unless mono can be made to work), but that's beyond my realm of expertise...

Link to comment
Share on other sites

This is extremely sweet. I'm doing a Windows implementation as well. In (wait for it) (waaiiiittttt) C# as well. :biggrin:

 

Thanks!!

You're welcome, and good luck!

 

Regrading C#, if it had direct-to-x86 compilation it would be the perfect language to write in, but having to pass through the CLR all the time makes it imperfect for low latency communication. Ultimately, you'd be better off writing a C/C++ layer that handles the basic communication loop and pass that up to higher code.

 

For example, in C# in Windows using HidLibrary, I get around 12ms per packet, whereas rewriting the low level code in ANSI C using direct Win32 calls and plugging it into LUA, I was able to easily hit 1ms per packet (the limit Windows/USB2.0 will allow).

Link to comment
Share on other sites

Your correct. I've done a fair amount of low level C and assembly over the years but I'm starting to play with C sharp. I haven't done much of anything yet with is thing, motivation seems to be a factor and getting a response from the 100i yet seems to be another. I recently did a large project for the army in c# on a hand-held unit that dealt with a couple low level devices so I should be able to figure this out. Unless you want some collaboration on yours.... :)
Link to comment
Share on other sites

  • 1 month later...

Hi All,

 

I modified the hidtest utility within the hidapi package (http://www.signal11.us/oss/hidapi/) to read and write the fan mode and RPM registers within the Corsair H80i (& H100i) coolers under Ubuntu 12.04.

 

/*******************************************************

Windows HID simplification

 

Alan Ott

Signal 11 Software

 

8/22/2009

 

Copyright 2009, All Rights Reserved.

 

This contents of this file may be used by anyone

for any reason without any conditions and may be

used as a starting point for your own applications

which use HIDAPI.

********************************************************/

 

#include <stdio.h>

#include <wchar.h>

#include <string.h>

#include <stdlib.h>

#include "hidapi.h"

 

// Headers needed for sleeping.

#ifdef _WIN32

#include <windows.h>

#else

#include <unistd.h>

#endif

 

int hid_read_wrapper (hid_device *handle, unsigned char *buf)

{

// Read requested state. hid_read() has been set to be

// non-blocking by the call to hid_set_nonblocking() above.

// This loop demonstrates the non-blocking nature of hid_read().

int res = 0;

while (res == 0) {

res = hid_read(handle, buf, sizeof(buf));

if (res == 0)

// printf("waiting...\n");

if (res < 0)

printf("Unable to read()\n");

#ifdef WIN32

Sleep(500);

#else

usleep(500*1000);

#endif

}

 

//printf("Data read:\n ");

//// Print out the returned buffer.

//for (int i = 0; i < res; i++)

// printf("%02hhx ", buf);

//printf("\n");

//return res;

}

 

int main(int argc, char* argv[])

{

int res;

unsigned int commandId = 0x81;

unsigned char buf[256];

#define MAX_STR 255

wchar_t wstr[MAX_STR];

hid_device *handle;

int i;

 

#ifdef WIN32

UNREFERENCED_PARAMETER(argc);

UNREFERENCED_PARAMETER(argv);

#endif

 

struct hid_device_info *devs, *cur_dev;

 

if (hid_init())

return -1;

 

devs = hid_enumerate(0x0, 0x0);

cur_dev = devs;

while (cur_dev) {

printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);

printf("\n");

printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string);

printf(" Product: %ls\n", cur_dev->product_string);

printf(" Release: %hx\n", cur_dev->release_number);

printf(" Interface: %d\n", cur_dev->interface_number);

printf("\n");

cur_dev = cur_dev->next;

}

hid_free_enumeration(devs);

 

// Set up the command buffer.

memset(buf,0x00,sizeof(buf));

buf[0] = 0x01;

buf[1] = 0x81;

 

 

// Open the device using the VID, PID,

// and optionally the Serial number.

// open Corsair H80i or H100i cooler

handle = hid_open(0x1b1c, 0x0c04, NULL);

if (!handle) {

printf("Error: Unable to open Corsair H80i or H100i CPU Cooler\n");

return 1;

}

 

// Read the Manufacturer String

wstr[0] = 0x0000;

res = hid_get_manufacturer_string(handle, wstr, MAX_STR);

if (res < 0)

printf("Unable to read manufacturer string\n");

printf("Manufacturer String: %ls\n", wstr);

 

// Read the Product String

wstr[0] = 0x0000;

res = hid_get_product_string(handle, wstr, MAX_STR);

if (res < 0)

printf("Unable to read product string\n");

printf("Product String: %ls\n", wstr);

 

// Set the hid_read() function to be non-blocking.

hid_set_nonblocking(handle, 1);

 

memset(buf,0,sizeof(buf));

 

// Read Device ID: 0x3b = H80i. 0x3c = H100i

buf[0] = 0x03; // Length

buf[1] = commandId++; // Command ID

buf[2] = 0x07; // Command Opcode

buf[3] = 0x00; // Command data...

buf[4] = 0x00;

 

res = hid_write(handle, buf, 17);

if (res < 0) {

printf("Error: Unable to write() %ls\n", hid_error(handle));

}

 

hid_read_wrapper(handle, buf);

if (res < 0) {

printf("Error: Unable to read() %ls\n", hid_error(handle));

}

printf("Device ID: %02hhx\n", buf[2]);

 

int deviceId = buf[2];

 

if ((deviceId != 0x3b) && (deviceId != 0x3c)) {

printf("Device ID: %02hhx mismatch. Not Corsair H80i or H100i CPU Cooler\n", buf[2]);

hid_close(handle);

hid_exit();

}

 

memset(buf,0x00,sizeof(buf));

// Read number-of-fans

buf[0] = 0x03; // Length

buf[1] = commandId++; // Command ID

buf[2] = 0x07; // Command Opcode

buf[3] = 0x11; // Command data...

buf[4] = 0x00;

 

res = hid_write(handle, buf, 5);

if (res < 0) {

printf("Error: Unable to write() %ls\n", hid_error(handle));

}

 

hid_read_wrapper(handle, buf);

if (res < 0) {

printf("Error: Unable to read() %ls\n", hid_error(handle));

}

 

printf("number-of-fans: %02hhx\n", buf[2]);

 

memset(buf,0x00,sizeof(buf));

// Read number-of-LEDs

buf[0] = 0x03; // Length

buf[1] = commandId++; // Command ID

buf[2] = 0x07; // Command Opcode

buf[3] = 0x05; // Command data...

buf[4] = 0x00;

 

res = hid_write(handle, buf, 5);

if (res < 0) {

printf("Error: Unable to write() %ls\n", hid_error(handle));

}

 

hid_read_wrapper(handle, buf);

if (res < 0) {

printf("Error: Unable to read() %ls\n", hid_error(handle));

}

 

printf("number-of-LEDs: %02hhx\n", buf[2]);

 

int fanMode = 0;

for (int i = 0; i < 5; i++) {

#ifdef WIN32

Sleep(500);

#else

usleep(500*1000);

#endif

 

memset(buf,0x00,sizeof(buf));

// Read fan Mode

buf[0] = 0x07; // Length

buf[1] = commandId++; // Command ID

buf[2] = 0x06; // Command Opcode

buf[3] = 0x10; // Command data...

buf[4] = i; // select fan

buf[5] = commandId++; // Command ID

buf[6] = 0x07; // Command Opcode

buf[7] = 0x12; // Command data...

 

res = hid_write(handle, buf, 11);

if (res < 0) {

printf("Error: Unable to write() %ls\n", hid_error(handle));

}

 

//printf("read fan Mode ");

res = hid_read_wrapper(handle, buf);

if (res < 0) {

printf("Error: Unable to read() %ls\n", hid_error(handle));

}

fanMode = buf[4];

 

memset(buf,0x00,sizeof(buf));

// Read fan RPM

buf[0] = 0x07; // Length

buf[1] = commandId++; // Command ID

buf[2] = 0x06; // Command Opcode

buf[3] = 0x10; // Command data...

buf[4] = i; // select fan

buf[5] = commandId++; // Command ID

buf[6] = 0x09; // Command Opcode

buf[7] = 0x16; // Command data...

 

res = hid_write(handle, buf, 11);

if (res < 0) {

printf("Error: Unable to write() %ls\n", hid_error(handle));

}

 

//printf("read fan RPM ");

res = hid_read_wrapper(handle, buf);

printf("Fan %d mode %02hhx RPM: %02hhx %02hhx\n", i, fanMode, buf[4], buf[5]);

 

if (i < 5) {

#ifdef WIN32

Sleep(500);

#else

usleep(500*1000);

#endif

 

memset(buf,0x00,sizeof(buf));

// setting fan mode to Default (0x06) or Performance (0x0c)

fanMode = 0x0c;

buf[0] = 0x0b; // Length

buf[1] = commandId++; // Command ID

buf[2] = 0x06; // Command Opcode

buf[3] = 0x10; // Command data...

buf[4] = i; // select fan

buf[5] = commandId++; // Command ID

buf[6] = 0x06; // Command Opcode

buf[7] = 0x12; // Command data...

buf[8] = fanMode;

buf[9] = commandId++; // Command ID

buf[10] = 0x07; // Command Opcode

buf[11] = 0x12; // Command data...

 

res = hid_write(handle, buf, 17);

if (res < 0) {

printf("Error: Unable to write() %ls\n", hid_error(handle));

}

//printf("read fan mode to confirm ");

res = hid_read_wrapper(handle, buf);

if (res < 0) {

printf("Error: Unable to read() %ls\n", hid_error(handle));

}

printf("Fan %d mode is %02hhx should be: %02hhx\n", i, buf[6], fanMode);

}

 

}

 

hid_close(handle);

 

/* Free static HIDAPI objects. */

hid_exit();

 

#ifdef WIN32

system("pause");

#endif

 

return 0;

}

  • Confused 1
Link to comment
Share on other sites

There is a great difference between the description of a protocol, and software to implement it.

From my POV Corsair should publish the protocol details, let people write their own code based on this, and maybe other hardware vendors will support it. Would do us all good including Corsair.

  • Confused 1
Link to comment
Share on other sites

  • 3 weeks later...
There is a great difference between the description of a protocol, and software to implement it.

From my POV Corsair should publish the protocol details, let people write their own code based on this, and maybe other hardware vendors will support it. Would do us all good including Corsair.

 

What part of the protocols are we missing? Confirmation from Corsair that we're right? They've already said they won't do that.

Link to comment
Share on other sites

Yes, but there is a difference between a published protocol and a reverse engineered one. If published I would expect for example changes and additions to be published as well. But it seems that won't happen. Pity. They have a chance to create a protocol and communications for lighting, cooling etc. And if anyone connects non Corsair LEDs to their lighting node, so what. Corsair has decided not to sell them.
Link to comment
Share on other sites

Wow guys, what you do here is awesome.

 

I'm tired of the buggy and ugly Corsair Link. Every time I want to change my H100i settings or monitor power usage on my AX760i, I have to detach and reattach the USB connector. Moreover, the product is ugly as hell (why all hardware manufacturers make super ugly GUI in their software? Perhaps, the only exception is AMD, but recent Catalyst's got more ugly; I'd rather have no GUI at all and control everything through CLI and\or configuration files then seeing THIS)

 

If I understand correctly, the CFSworks wants to make some Linux software for Corsair Link, but you guys Thatualle1970 and bobpombrio are doing something for Windows, right?

 

Unfortunately I have no idea in developing drivers and only a very (very) little knowledge of c++ and WinAPI so can't help developing.

 

If there is something else I can help otherwise (i.e. test something?), please PM me. I live in GMT +0300 and available (and hopefully will respond quickly) every weekend

I've got AX760i and H100i devices (AX760i connected to H100i, and H100i goes to the USB; I can also connect AX760i to its USB dongle; if something working will come from this thread, I'll be eager to buy the Link device also...). I am also a very experienced Windows software user and know a lot of stuff about it.

Link to comment
Share on other sites

  • 2 months later...

Hi,

 

I have switched one year ago to Linux and I found that it lacks the control over my cooler (H100i).

Thanks dwmccauley's example I have created a working basic tool that allows the following:

  • View fan and pump information
  • Change fan mode
  • Set fan RPM (if fixed RPM mode is selected)

 

The code is attached in the file and you can use the makefile in the Debug folder.

 

I apologize in advance if I break any "Linux developing rules", but I am a .NET developer (yes I use Linux and develop on MS products) and I just got into C++.

 

V.

 

P.S. Let me know if you find bugs or issues with my code.

OpenCorsairLink.zip

Link to comment
Share on other sites

  • 1 month later...
Hi,

 

I have switched one year ago to Linux and I found that it lacks the control over my cooler (H100i).

Thanks dwmccauley's example I have created a working basic tool that allows the following:

  • View fan and pump information
  • Change fan mode
  • Set fan RPM (if fixed RPM mode is selected)

 

The code is attached in the file and you can use the makefile in the Debug folder.

 

I apologize in advance if I break any "Linux developing rules", but I am a .NET developer (yes I use Linux and develop on MS products) and I just got into C++.

 

V.

 

P.S. Let me know if you find bugs or issues with my code.

 

Thanks, this seems to work well. I hope that someone converts this into a kernel module.

Link to comment
Share on other sites

  • 2 weeks later...

Hi all

 

This is my first post on this forum. I have been following this thread

for sometime. I have a h80i and a Command module with a cooling node.

I am a software engineer (I do operating systems). I have had the need

for support for the hardware under Linux (on my personal home system).

So I decided to take all the great information supplied by other users and

develop a driver, which I did. Since I did this off of research and the hard

work of the other users. I am giving this work back to you all.

 

 

The attached tar-ball has a readme file that should help get things going.

Remember this is not a polished thing. While it has been working well (for

two weeks), it is new code. The install processes is ruff at best...

 

Also I think that if there is enough interest, it should get cleaned up some

and ultimately get put into appropriate community projects... At this point

I am getting good use out of it and I hope others can also... :biggrin:

 

Barry

corsairdriver.zip

Edited by barryha
Link to comment
Share on other sites

Hi,

 

I have switched one year ago to Linux and I found that it lacks the control over my cooler (H100i).

Thanks dwmccauley's example I have created a working basic tool that allows the following:

  • View fan and pump information
  • Change fan mode
  • Set fan RPM (if fixed RPM mode is selected)

 

The code is attached in the file and you can use the makefile in the Debug folder.

 

I apologize in advance if I break any "Linux developing rules", but I am a .NET developer (yes I use Linux and develop on MS products) and I just got into C++.

 

V.

 

P.S. Let me know if you find bugs or issues with my code.

 

This worked for me with a little bit of hacking. There was a hard path to the source code I had to change and I had to change the library it uses from libhidapi-hidraw to libhidapi-libusb. This is probably because I'm using CentOS6, which has an older kernel. Also, for some reason the program segmentation faults when it finishes.

 

Thanks for the code.

Link to comment
Share on other sites

Hey CFSworks; great job so far.... :cool:

Some additional information to keep this thread moving in the positive direction.

 

Corsair Link protocol overview

 

As mentioned, the USB device uses a simple 64-byte HID "raw" report; the rawhid source code can be a useful starting point to establish low-level communication if a device isn't "just available" (e.g., on Windows).

 

http://www.pjrc.com/teensy/rawhid.html

 

The ReportID is always zero and is length-prefixed with a number of SMBus-like packets within the HID packet. Each packet has an index (which should be from 20~255), SMBus sub-packet type followed by any write data, if the sub-packet is writing data. Block types also have a length, and it appears the "quick" and "byte" modes are unsupported (only CmdByte, CmdWord and CmdBlock appear to be used).

 

H80i/H100i Register set:

 

[TABLE=head]__Number__|R/W|__Length__| Description

00 | R | 1 byte | Device ID - (H80 0x37, Cooling node 0x38, Lighting node 0x39, H100 0x3A, 80i 0x3B, 100i 0x3c -- this field as well as the version are common on all C-Link devices; but the rest aren't

01 | R | 2 bytes | Firmware Version in BCD (for example 1.0.5 is 0x1005, or 0x05, 0x10 in little endianess)

02 | R | 8 bytes | Product name, zero-terminated - only present on the H80i and H100i

03 | R | 1 | Status, 0 okay, 0xFF bad

04 | RW | 1 byte | Select current LED

05 | R | 1 byte | Number of LEDs

06 | RW | 1 byte | LED mode - 0x00 for static color, 0x40 for 2-color cycle, 0x80 for 4-color, 0xC0 for temperature mode; low nibble defines cycle speed or the temperature channel to use (0 internal sensor, 7 manual)

07 | R | 3 bytes | LED current color, RGB color of the selected LED

08 | RW | 2 bytes | In temperature controlled mode (0xC0) this defines the colour to use to with the below gradients

09 | RW | 6 bytes | LED temperature-mode temperatures: 3 temperatures; used when cycle mode is 0xc0

0a | RW | 9 bytes | LED temperature-mode colors: RGBx3 colors, corresponding to temperatures in register above

0b | RW | 12 bytes | LED cycle colors: RGBx4 colors (only first color used if cycle mode set to 00, first two if 4b, ignored if c0)

0c | RW | 1 byte | Select active temperature sensor

0d | R | 1 byte | Number of temperature sensors

0e | R | 2 bytes | Temperature as measured by selected sensor

0f | RW | 2 bytes | Temperature limit (when the temperature goes over this, status is set to 0xff)

10 | RW | 1 byte | Select current fan; for H100i, 0-3 are the fans, 4 is pump

11 | R | 1 byte | Number of fans

12 | RW | 1 byte | Fan mode; 02=fixed PWM, 04=fixed RPM, 06=default, 08=quiet, 0a=balanced, 0c=performance, 0e=custom; high bit is one when fan is detected, low bit is one when the fan is 4-pin, bits 6~4 define the temperautre channel to use in "curve" modes, 0 internal and 7 manual

13 | RW | 1 byte | Fan fixed PWM, 0-255, only used if fan mode is 02

14 | RW | 2 bytes | Fan fixed RPM; when fan mode is 04, controller will target this RPM

15 | RW | 2 bytes | Report external temperature to fan controller - used for controlling fans via external sensors

16 | R | 2 bytes | Current fan RPM

17 | R | 2 bytes | Maximum RPM recorded since power-on

18 | RW | 2 bytes | Fan under speed threshold

19 | RW | 10 bytes | Fan RPM table, for custom (0e) mode: array of 5 RPMs

1a | RW | 10 bytes | Fan temp table, for custom (0e) mode: array of 5 temperatures

[/TABLE]

 

Note: All data is little-endian.

Temperatures are reported in units of 1/256th of a degree Celsius.

 

I'm adding some LED control and status to the C version of OpenCorsairLink. Can you give any more information about how register 08 works? Does it map colours to temperature ranges? How is the data encoded in this register?

Link to comment
Share on other sites

  • 1 month later...
I'm adding some LED control and status to the C version of OpenCorsairLink. Can you give any more information about how register 08 works? Does it map colours to temperature ranges? How is the data encoded in this register?

Yes. In a nutshell, there are three colours, cool, warm and hot. The temperature thresholds are defined in 09 and are 8.8 signed fixed-point. The colours are simple RGB values for each point and reside in register 0A. To set the temperature colour points, you'd do something like:

for(int i=0; i<3; i++) {
   temp[i * 2] = (unsigned char)(temp.value[i] * 256.0f);
   temp[i * 2 + 1] = (unsigned char)temp_value[i];
}

When you write the temperature to 08 while under temperature controlled mode, the H80i will interpolate that temperature to within the cool-warm-hot range and determine the RGB value automatically.

Link to comment
Share on other sites


×
×
  • Create New...