04

Steam Controller Driver for Actual Motors

lorem ipsum

React
Node.js
WebSocket
MongoDB
Redis

Backstory

I was cleaning out a closet and I discovered an old Steam Controller I bought about a decade ago. To my surprise, it still turned on. Way to go, Duracell. I remembered it being a pretty niche thing that had very little adoption and was quickly deprecated by Valve. Boy was I wrong. I turned it on and immediately remembered how amazing the haptic feedback was on the touchpads. How could I forget the grip inputs? Even a gyroscope! The design and hardware were top notch, but adoption was the problem.

The haptic feedback touchpads made Games like Civ and XCOM playable on the couch. But I had my eyes on something much bigger target that could use a controller with fourteen buttons, six directional axes, two analog triggers, and a gryoscope: Flight Simulator. With traditional controllers, you had to have a plethora of different button configurations to make it work. Joysticks make it hard to sense and control your aircraft. Touchpads allow for refined movement with feedback to help you ramp up and down with ease.

Flight Simulator

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eget tortor massa. Sed sed nulla vitae enim accumsan pulvinar fermentum nec nunc. Phasellus scelerisque sem vel massa tempus, id lobortis arcu mattis. Donec sed lectus cursus libero auctor facilisis. Donec mattis luctus congue. Maecenas et nunc odio. Sed eu diam lectus. Phasellus sit amet mi vitae lacus rhoncus elementum. Nullam dictum justo sed dignissim placerat. Pellentesque porta velit vel luctus efficitur.

WebSocket Server

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eget tortor massa. Sed sed nulla vitae enim accumsan pulvinar fermentum nec nunc. Phasellus scelerisque sem vel massa tempus, id lobortis arcu mattis. Donec sed lectus cursus libero auctor facilisis. Donec mattis luctus congue. Maecenas et nunc odio. Sed eu diam lectus. Phasellus sit amet mi vitae lacus rhoncus elementum. Nullam dictum justo sed dignissim placerat. Pellentesque porta velit vel luctus efficitur.

Physical Applications

Parsing the Byte Stream

If I want to make a driver for this controller, I need to see what the output is. Controllers are just like a keyboard: character devices. When you type an "a" character into your keyboard, the keyboard sends data for an "a" key to the computer. When you press an "a" button on your controller, it does just about the same thing. It sends data, which in this case is 41 in hex or 01000010 in binary, to the computer and it is handled by the OS, which monitors for any special inputs, before sending it to the application you're using. This happens so quickly, we don't even think about it. It's a little more complicated than that, but I'll explain later. What I need to do is reverse engineer what data the inputs on my controller output and write a driver to turn those inputs into actions.

Knowing my controller is a character device, I can "read" it just like a file, but I have to jump through a couple hoops first. I don't know what that actual file is yet. I'm going to have to check around. When you plug a device into a computer running an OS, it has to decide what to do with it. Devices that already have their firmware configured will tell the OS what to do with it, usually through the Human Interface Device (HID) standard. So I need to ask the OS where it put the file.

$ lsusb
Bus 003 Device 022: ID 28de:1102 Valve Software Wired Controller

This gives us the Bus number, Device number, and both the Device ID and Vendor ID. The bus and device number are abstractions for userspace created by udev. In this instance they seem very redundant and abstract away from what I'm trying to accomplish. What I am trying to do is take the raw input from the device at a kernel level, so I need to ask udev where it's getting this device from.

$ udevadm info -q path -n /dev/bus/usb/003/022
/sys/devices/pci0000:00/0000:00:14.0/usb3/3-7
This shows us where the kernel has put the device. We can go into this series of directories and find it. Problem solved! Almost.

In this case, with the steam controller, it actually has 3 subdevices, each with their own hidraw file. As it turns out, the controller can also be used in place of a mouse and keyboard. One could poke around the files some more to differentiate them, but I found a better solution.

sudo usbhid-dump -m 28de:1102 -ed > descriptor.txt
grep -A 10 "DESCRIPTOR" descriptor.txt | grep -v "DESCRIPTOR" | grep -v "STREAM" | tr -d ' \n' > descriptor_hex.txt
rexx rd.rex -d --hex "$(cat descriptor_hex.txt)"
=== /dev/hidraw6 ===
Name:             Valve Software Wired Controller
Vendor:Product:   0x28de:0x1102
Bus type:         3
Physical:         usb-0000:00:14.0-7/input0
Descriptor size:  65 bytes
Descriptor (first 32 bytes): 05 01 09 06 95 01 A1 01 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 95 01 75 08 81 01 95 05 
Type hints:       [KEYBOARD] 

=== /dev/hidraw7 ===
Name:             Valve Software Wired Controller
Vendor:Product:   0x28de:0x1102
Bus type:         3
Physical:         usb-0000:00:14.0-7/input1
Descriptor size:  52 bytes
Descriptor (first 32 bytes): 05 01 09 02 A1 01 09 01 A1 00 05 09 19 01 29 05 15 00 25 01 95 05 75 01 81 02 95 01 75 03 81 01 
Type hints:       [MOUSE] 

=== /dev/hidraw8 ===
Name:             Valve Software Wired Controller
Vendor:Product:   0x28de:0x1102
Bus type:         3
Physical:         usb-0000:00:14.0-7/input2
Descriptor size:  33 bytes
Descriptor (first 32 bytes): 06 00 FF 09 01 A1 01 15 00 26 FF 00 75 08 95 40 09 01 81 02 95 40 09 01 91 02 95 40 09 01 B1 02 

I found a tool called RDD! HID Report Descriptor Decoder on github by abend0c1. It takes the data from usbhid-dump and performs lookups on all the devices given. It's a very comprehensive tool that gave much more output than I listed.