Skip to content

Instantly share code, notes, and snippets.

@andreldm
Last active August 3, 2020 23:52
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 andreldm/367f0dba38da9c59e9f169fe0dd570fd to your computer and use it in GitHub Desktop.
Save andreldm/367f0dba38da9c59e9f169fe0dd570fd to your computer and use it in GitHub Desktop.
Corsair Harpoon RGB Wireless basic protocol debugging, see https://github.com/andreldm/harpoond
# Setup:
# python -m venv env
# source env/bin/activate
# pip install pyusb
#
# Execute with: sudo python test.py
# See udev rule present in (2) if you want to avoid running as root.
#
# References:
# 1. https://wiki.wireshark.org/CaptureSetup/USB
# 2. https://slomkowski.eu/tutorials/eavesdropping-usb-and-writing-driver-in-python
# 3. https://mvidner.blogspot.com/2017/01/usb-communication-with-python-and-pyusb.html
# 4. https://github.com/Benzhaomin/pyrmi/blob/master/main.py
import sys
import usb.core
import usb.util
import sched, time
VENDOR_ID = 0x1b1c
WIRED_DEVICE_ID = 0x1b5e
DONGLE_DEVICE_ID = 0x1b65
WIRED = True
COMMAND_PREFIX = 0x08
dev = usb.core.find(idVendor=VENDOR_ID, idProduct=WIRED_DEVICE_ID)
if dev is None:
print("Wired default not found, looking for dongle...")
WIRED = False
COMMAND_PREFIX = 0x09
dev = usb.core.find(idVendor=VENDOR_ID, idProduct=DONGLE_DEVICE_ID)
assert dev
interface = 1 # INTERFACE 1: Human Interface Device
reader = dev[0][(interface, 0)][0] # ENDPOINT 0x84: Interrupt IN
writer = dev[0][(interface, 0)][1] # ENDPOINT 0x04: Interrupt OUT
def write(data):
padding = [0x0]*(64 - len(data))
writer.write(data + padding, timeout=100)
def read():
data = reader.read(reader.bEndpointAddress, reader.wMaxPacketSize)
return bytes(data).hex()
def send(data):
write(data)
print(read())
def grab_device():
if dev.is_kernel_driver_active(interface):
try:
dev.detach_kernel_driver(interface)
usb.util.claim_interface(dev, interface)
except usb.core.USBError as e:
sys.exit("Could not detach kernel driver: %s" % str(e))
def ungrab_device():
usb.util.release_interface(dev, interface)
dev.attach_kernel_driver(interface)
grab_device()
# Init
send([0x08, 0x01, 0x03, 0x00, 0x02])
if WIRED:
send([0x08, 0x0d, 0x00, 0x01])
else:
send([0x09, 0x01, 0x03, 0x00, 0x02])
send([0x09, 0x0d, 0x00, 0x01])
# Set my configuration
send([COMMAND_PREFIX, 0x06, # Command to manipulate LEDs ?
0x00, 0x06, # ?
0x00, 0x00, 0x00, # ?
0x00, # Indicator LED's red
0x00, # Main LED's red
0xff, # Indicator LED's green
0x00, # Main LED's green
0x00, # Indicator LED's blue
0x00 # Main LED's blue
])
#send([COMMAND_PREFIX, 0x01, 0x20, 0x00, 0xe8, 0x03]) # Set DPI to 1000 (3e8 = 1000)
#send([COMMAND_PREFIX, 0x01, 0x20, 0x00, 0xb8, 0x0b]) # Set DPI to 3000 (b8b = 2955)
send([COMMAND_PREFIX, 0x01, 0x20, 0x00, 0x08, 0x70]) # Set DPI to 1800 (708 = 1800)
ungrab_device()
s = sched.scheduler(time.time, time.sleep)
def keep_alive(sc):
grab_device()
send([COMMAND_PREFIX, 0x12]) # Keep alive, has to be sent every minute or mouse will reset to defaults
ungrab_device()
s.enter(55, 1, keep_alive, (sc,))
s.enter(55, 1, keep_alive, (s,))
s.run()
# Packets sent when iCue is initialized (captured with Wireshark)
#
# WIRED
#
# send([0x08, 0x02, 0x03]) # ?
# send([0x08, 0x01, 0x03, 0x00, 0x02]) # REQUIRED, if executed alone, Main LED freezes
# send([0x08, 0x02, 0x03]) # ?
# send([0x08, 0x02, 0x5f]) # ?
# send([0x08, 0x02, 0x01]) # ?
# send([0x08, 0x02, 0x03]) # ?
# send([0x08, 0x02, 0x13]) # ?
# send([0x08, 0x0d, 0x00, 0x05]) # ?
# send([0x08, 0x09]) # ?
# send([0x08, 0x08]) # ?
# send([0x08, 0x05, 0x01]) # ?
# send([0x08, 0x0d, 0x00, 0x27]) # ?
# send([0x08, 0x01, 0x03, 0x00, 0x02]) # ?
# send([0x08, 0x0d, 0x00, 0x05]) # ?
# send([0x08, 0x09]) # ?
# send([0x08, 0x08]) # ?
# send([0x08, 0x05, 0x01]) # ?
# send([0x08, 0x02, 0x0f]) # ?
# send([0x08, 0x02, 0x10]) # ?
# send([0x08, 0x02, 0x0b]) # ?
# send([0x08, 0x02, 0x0d]) # ?
# send([0x08, 0x02, 0x0e]) # ?
# send([0x08, 0x01, 0x02, 0x00, 0xe8, 0x03]) # ?
# send([0x08, 0x02, 0x15]) # ?
# send([0x08, 0x01, 0x20, 0x00, 0x90, 0x01]) # ?
# send([0x08, 0x0d, 0x00, 0x02]) # ?
# send([0x08, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01]) # ?
# send([0x08, 0x05, 0x01]) # ?
# send([0x08, 0x0d, 0x00, 0x02]) # ?
# send([0x08, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01]) # ?
# send([0x08, 0x05, 0x01]) # ?
# send([0x08, 0x01, 0x07]) # ?
# send([0x08, 0x0d, 0x00, 0x01]) # REQUIRED, ?
# send([0x08, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0xff, 0x13, 0x00, 0x00, 0x00, 0xc5]) # Sets DPI Indicator LED to red
# send([0x08, 0x02, 0x15]) # ?
# Makes DPI Indicator LED oscillate between green and off twice
# for x in [
# 0x04, 0x08, 0x0c, 0x10, 0x19, 0x1d, 0x21, 0x2a, 0x2e, 0x33, 0x3b, 0x3f,
# 0x44, 0x48, 0x4c, 0x51, 0x55, 0x59, 0x5e, 0x62, 0x66, 0x6a, 0x6e, 0x73,
# 0x77, 0x7b, 0x80, 0x7e, 0x7a, 0x76, 0x71, 0x6d, 0x69, 0x64, 0x60, 0x5c,
# 0x57, 0x53, 0x4f, 0x4b, 0x46, 0x42, 0x3e, 0x39, 0x35, 0x31, 0x2d, 0x29,
# 0x24, 0x1f, 0x1b, 0x17, 0x13, 0x0e, 0x0a, 0x06, 0x01, 0x00, 0x04, 0x08,
# 0x0d, 0x11, 0x15, 0x1a, 0x1e, 0x22, 0x27, 0x2b, 0x2f, 0x33, 0x38, 0x3c,
# 0x40, 0x44, 0x49, 0x4d, 0x51, 0x56, 0x5a, 0x5f, 0x63, 0x67, 0x6b, 0x70,
# 0x74, 0x78, 0x7c, 0x80, 0x7d, 0x79, 0x74, 0x70, 0x6c, 0x67, 0x63, 0x5f,
# 0x5a, 0x56, 0x52, 0x4e, 0x49, 0x45, 0x41, 0x3b, 0x37, 0x32, 0x2e, 0x2a,
# 0x26, 0x21, 0x1d, 0x19, 0x14, 0x10, 0x0c, 0x08, 0x03, 0x00]:
# send([0x08, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x13, x, 0x00, 0x00, 0xc5])
# send([0x08, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0xff, 0x13, 0x00, 0x00, 0x00, 0xc5]) # Sets DPI Indicator LED to red
#
# DONGLE
#
# send([0x08, 0x02, 0x03]) # ?
# send([0x08, 0x01, 0x03, 0x00, 0x02]) # REQUIRED, if executed alone, Main LED freezes
# send([0x08, 0x02, 0x03]) # ?
# send([0x08, 0x02, 0x5f]) # ?
# send([0x08, 0x02, 0x01]) # ?
# send([0x08, 0x02, 0x03]) # ?
# send([0x08, 0x02, 0x13]) # ?
# send([0x08, 0x02, 0x14]) # ?, this is different from the sequence of Wired initialization
# send([0x08, 0x0d, 0x00, 0x05]) # ?
# send([0x08, 0x09]) # ?
# send([0x08, 0x08]) # ?
# send([0x08, 0x05, 0x01]) # ?
# send([0x08, 0x0d, 0x00, 0x27]) # ?
# send([0x08, 0x01, 0x03, 0x00, 0x02]) # ?
# send([0x08, 0x0d, 0x00, 0x05]) # ?
# send([0x08, 0x09]) # ?
# send([0x08, 0x08]) # ?
# send([0x08, 0x05, 0x01]) # ?
# send([0x08, 0x02, 0x36]) # ?, at this point the sequence diverges from Wired initialization
# send([0x09, 0x02, 0x11]) # ?
# send([0x09, 0x02, 0x12]) # ?
# send([0x09, 0x02, 0x03]) # ?
# send([0x09, 0x01, 0x03, 0x00, 0x02]) # REQUIRED, ?
# send([0x09, 0x02, 0x03]) # ?
# send([0x09, 0x02, 0x5f]) # ?
# send([0x09, 0x02, 0x01]) # ?
# send([0x09, 0x02, 0x03]) # ?
# send([0x09, 0x02, 0x13]) # ?
# send([0x09, 0x02, 0x14]) # ?
# send([0x09, 0x0d, 0x00, 0x05]) # ?
# send([0x09, 0x09]) # ?
# send([0x09, 0x08]) # ?
# send([0x09, 0x05, 0x01]) # ?
# send([0x09, 0x0d, 0x00, 0x27]) # ?
# send([0x09, 0x0d, 0x00, 0x05]) # ?
# send([0x09, 0x09]) # ?
# send([0x09, 0x08]) # ?
# send([0x09, 0x05, 0x01]) # ?
# send([0x09, 0x02, 0x15]) # ?
# send([0x09, 0x02, 0x0f]) # ?
# send([0x09, 0x02, 0x10]) # ?
# send([0x09, 0x02, 0x0b]) # ?
# send([0x09, 0x02, 0x0d]) # ?
# send([0x09, 0x02, 0x0e]) # ?
# send([0x09, 0x01, 0x02, 0x00, 0xe8, 0x03]) # ?
# send([0x09, 0x02, 0x15]) # ?
# send([0x09, 0x0d, 0x00, 0x02]) # ?
# send([0x09, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01]) # ?
# send([0x09, 0x05, 0x01]) # ?
# send([0x09, 0x05, 0x07]) # ?
# send([0x09, 0x0d, 0x00, 0x02]) # ?
# send([0x09, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01]) # ?
# send([0x09, 0x05, 0x01]) # ?
# send([0x09, 0x02, 0x0f]) # ?
# send([0x09, 0x02, 0x10]) # ?
# send([0x09, 0x01, 0x02, 0x00, 0xe8, 0x03]) # ?
# send([0x09, 0x02, 0x15]) # ?
# send([0x09, 0x0d, 0x00, 0x02]) # ?
# send([0x09, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01]) # ?
# send([0x09, 0x05, 0x01]) # ?
# send([0x09, 0x01, 0x07]) # ?
# send([0x09, 0x0d, 0x00, 0x02]) # ?
# send([0x09, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01]) # ?
# send([0x09, 0x05, 0x01]) # ?
# send([0x09, 0x01, 0x20, 0x00, 0x40, 0x06]) # ?
# send([0x09, 0x02, 0x40]) # ?
# send([0x09, 0x02, 0x0b]) # ?
# send([0x09, 0x02, 0x0d]) # ?
# send([0x09, 0x02, 0x0e]) # ?
# send([0x09, 0x0d, 0x00, 0x01]) # REQUIRED, ?
@hockeymikey
Copy link

So how does one control the main led besides freezing? What's the pattern with the oscillation of the indicator led? Wanna try with other colors.

@andreldm
Copy link
Author

So how does one control the main led besides freezing?

I'm personally only interested in static colors, I didn't check yet how patterns are defined.

What's the pattern with the oscillation of the indicator led? Wanna try with other colors.

I think iCue does that either as a silly effect or as a way to check if responses are correct.

@hockeymikey
Copy link

hockeymikey commented Jul 21, 2020

I'm personally only interested in static colors, I didn't check yet how patterns are defined.

I'm fine keeping it static, just not seeing how in the script minus the freezing which I would want to define a color.

I think iCue does that either as a silly effect or as a way to check if responses are correct.

Interesting, so the array is from them. Didn't know if you made it and it had a pattern or something I wasn't seeing. I tried with red but it went red to yellow. Very strange.

@andreldm
Copy link
Author

I'm fine keeping it static, just not seeing how in the script minus the freezing which I would want to define a color.

You can ignore that comment and interpret this packet as part of the initialization (which I think it's very likely)

Interesting, so the array is from them. Didn't know if you made it and it had a pattern or something I wasn't seeing. I tried with red but it went red to yellow. Very strange.

Up to # Custom packets crafted by me all packets were sent by iCue, I kept them verbatim, commenting out the ones that don't seem necessary. Try tweaking values in send of line 103.

@andreldm
Copy link
Author

@hockeymikey script updated to support dongle and stays running to send the keep alive packet every 55 seconds.

@hockeymikey
Copy link

@hockeymikey script updated to support dongle and stays running to send the keep alive packet every 55 seconds.

Great work, you're amazing. Now if only I can figure out how to listen for the screen being dimmed on kde and I could incorporate that haha. Did you use W10 in a VM? I'm curious to figuring out the other unknown parts if I get the time.

@andreldm
Copy link
Author

Great work, you're amazing. Now if only I can figure out how to listen for the screen being dimmed on kde and I could incorporate that haha. Did you use W10 in a VM? I'm curious to figuring out the other unknown parts if I get the time.

Thanks, yes I used a Windows 10 VM (see references #1 and #2). As for preventing screen dimming, try caffeine.

By the way, I turned this gist into a project, so you won't updates here anymore.

@hockeymikey
Copy link

Solid work

As for preventing screen dimming, try caffeine.

I wanna detect screen dimming with Python so I can dim the mouse with it and other things. Haven't been able to find anything either kde focused or in general.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment