Open roblabla opened 4 years ago
Alright small update: I managed to identify the code in the steam client that interacts with the Steam Controller. And fortunately, a lot of symbols and assert messages are left over, allowing me to find the official name for more or less every single packet, along with a whole host of packets that seem to be used for other valve hardware - The valve index and steam controller share a lot of code.
I'll update the table above with this newfound information as I go.
Hello, I am trying to analyze the protocol of Steam Controller BLE HID, I updated it here, hope it can help you. There are some data that I haven’t had time to update, because English is not my native language, I write very slowly😂. Your document is very helpful to me, looking forward to your update😄
Hey @tiehichi thanks for this, it's pretty useful! I've been focusing on this weird proprietary protocol and haven't looked at the actual input reports at all so far :). The HID layer is identical both for BLE, USB and Enhanced ShockBurst (the protocol used to talk to the dongle), so a lot of your findings should apply everywhere.
I'll make a PR later today with my latest findings, in a couple of proper markdowns :). I've figured out a couple of interesting things - it seems there's still some hidden features in the Steam Controller firmware that are going to be fun to play around with ^^.
@tiehichi I have previously categorized some of the meaning behind the bytes in valve mode.
I think that the second byte is a mode indicator. I haven't dug into what the meaning of the bytes are when the byte is 0x05 but I have the meaning of the bytes when the mode is 0x04.
The steam controller seems to have 5 distinct modes; The modes are on the second byte of the packet.
Mode | Byte Value | Description |
---|---|---|
Idle | 0x05 | The controller is in an idle state |
Active | 0x04 | The controller is in an active state (Or waiting for input before switching back to idle) |
Buttons | 0x14 | A button is being pressed/released |
Triggers | 0x24 | A trigger is being depressed |
Joysticks | 0x84 | The joystick is being used |
When using more than one mode (aka, buttons and joysticks) these modes are or'ed together.
The touch pads modes are on the third byte of the packet. When the touch pads are in use the second byte is set to active and the following mode conditions are set.
Touchpad-Mode | Byte Value | Description |
---|---|---|
Left pad | 0x01 | Left touch pad |
Right pad | 0x02 | Right touch pad |
Similarly when both pads are in use the bytes are or'ed together for a value of 0x03.
Then the inputs always start on the forth byte. When all controls are being touched the controls are on the packet in the order: buttons->triggers->joystick->touchpads. All the input cannot fit on one packet so the right touch pad seems to be left off.
When buttons are active they take up 3 bytes of space: | Button | Byte | Value |
---|---|---|---|
A | 4 | 0x80 | |
B | 4 | 0x20 | |
X | 4 | 0x40 | |
Y | 4 | 0x10 | |
Left Shoulder | 4 | 0x08 | |
Right Shoulder | 4 | 0x04 | |
Left Trigger Press | 4 | 0x02 | |
Right Trigger Press | 4 | 0x01 | |
Home | 5 | 0x20 | |
Select | 5 | 0x10 | |
Play | 5 | 0x40 | |
Back Left | 5 | 0x80 | |
Back Right | 6 | 0x01 | |
Joystick Press | 6 | 0x40 | |
Left Touch pad press | 6 | 0x02 | |
Right Touch Press | 6 | 0x04 |
When the trigger is depressed it takes up 2 bytes of space. | Trigger | Byte |
---|---|---|
Left | 4 | |
Right | 5 |
When the joystick is active it takes up 4 bytes of space. this can be converted into a short and then divided by 32767 for a float between -1 to +1.
Joystick | Byte | Description |
---|---|---|
4 & 5 | The Y value | |
6 & 7 | The X value |
Finally the touch pads can appear in the packet independently. Each touch pad takes up 4 bytes of space and when both are used the bytes the right touch pad uses are shifted so they appear as left touch pad->right touch pad, in the packet.
touch pad | Byte | Description |
---|---|---|
Left | (4, 5) & (6, 7) | The X/Y value |
Right | (4, 5) & (6, 7) | The X/Y value |
Hey @roblabla , looking forward to your update, these features must be very cool! Hello @nrc4867 , thank you very much for these information! I am trying to connect SteamController to nintendo switch using ESP32, these data are very useful for me! And if I have any new discoveries, I will share them in time😁
Dead?
This is an issue to gather information about the HID Controller Interface protocol, as described here.
Look at #25 for my current progress.
Old information
The HID Controller Interface is interfaced either via Firmware USB EP3 , Bootloader USB EP1, or via some currently unknown wireless mechanism. It works by transferring buffers of 0x40 bytes. The first byte is a packet id, while the second byte is the length of the additional data. Here is a packetid table containing both bootloader and firmware packets as of 57bf5c10. Host->Controller: Packet ID | Name | Controller Bootloader | Controller Firmware | Dongle | Description ----------|--------|---------------------|------------------|------------|---------- 0x80 | | ⛔️ | ✅ | | 0x81 | ExitLizardMode | ⛔️ | ✅ | ❓ | Exits the "lizard mode" - Tells the steam controller to acting like a dumb keyboard/mouse combo. Might need to be sent periodically? 0x82 | | ⛔️ | ✅ | | 0x83 | [ControllerInfoRequest](#ControllerInfoRequest) | ✅ | ✅ | | Asks the controller to send its ControllerInfo. 0x85 | | ⛔️ | ✅ | | 0x87 | [SetSettings](#SetSettings) | ⛔️ | ✅ | ❓| Sets the controller settings 0x89 | | ⛔️ | ✅ | | 0x8d | | ⛔️ | ✅ | | 0x8e | | ⛔️ | ✅ | | 0x8f | TriggerHapticPulse | ⛔️ | ✅ | | 0x90 | ReinvokeISP | ✅ | ✅ | ❓ | Reinvokes the LPC ISP Firmware (ISP being what happens when you boot while holding the right trigger). Seems to also work on the dongle? 0x91 | EraseLPCFirmware | ✅ | ⛔️ | | Erases the LPC firmware. 0x92 | FlashLPCFirmware | ✅ | ⛔️ | | Flashes a chunk of LPC firmware. 0x93 | VerifyLPCFirmware | ✅ | ⛔️ | | Finishes flashing the LPC firmware, and validates it against a checksum. 0x95 | ResetSOC | ✅ | ✅ | | Restarts both LPC and NRF CPUs. 0x96 | SetPrngEntropy | ⛔️ | ⛔️ | ✅ | Sends a block of 0x10 random bytes to the device. 0x97 | EraseNRFFirmware | ✅ | ✅ | | (Supposedly, no RE) Erases the NRF Firmware 0x98 | FlashNRFFirmware | ✅ | ✅ | | (Supposedly, no RE) Flashes a chunk of NRF Firmware 0x99 | VerifyNRFFirmware | ✅ | ✅ | | (Supposedly, no RE) Finishes flashing the NRF firmware, and validates it against a checksum. 0x9a | | ⛔️ | ✅ | | Sends data to the nRF chip, wrapped in a packet ']'. 0x9f | TurnOffController | ⛔️ | ✅ | ✅ | 0xa7 | CalibrateTrackpads | ⛔️ | ✅ | | 0xa9 | | ⛔️ | ✅ | | 0xa0 | SetHardwareVersion | ✅ | ⛔️ | | THIS COMMAND IS EXTREMELY DANGEROUS 0xaa | | ⛔️ | ✅ | | 0xab | | ⛔️ | ✅ | | 0xac | | ⛔️ | ✅ | | 0xad | SetDonglePairingMode | ⛔️ | ⛔️ | ✅ | 2 byte args 0xae | ControllerInfoRequest | ⛔️ | ✅ | | 0xb1 | SetControllerKeyboardMouseInputState | ⛔️ | ⛔️ | ✅ | 0xb2 | PairingFailed | ⛔️ | ⛔️ | ✅ | 0xb3 | PairingSuccess | ⛔️ | ⛔️ | ✅ | 0xb4 | GetControllerInfo | ⛔️ | ⛔️ | ✅ | 0xb5 | CalibrateIMU | ⛔️ | ✅ | | 0xb6 | PlayAudio | ⛔️ | ✅ | | Plays the selected jingle 0xb7 | StartFlashJingle | ⛔️ | ✅ | | Does something weird with the jingle_data_ptr :eyes: 0xb8 | FlashJingle | ⛔️ | ✅ | | Writes data to the jingle flash buffer 0xb9 | EndFlashJingle | ⛔️ | ✅ | | Finishes flashing a jingle, writing it to eeprom after making sure it looks somewhat valid. 0xba | GetChipID | ⛔️ | ✅ | | 0xbb | ReadUID | ⛔️ | ✅ | | Returns the result of ISP ReadUID. 0xbf | CalibrateJoystick | ⛔️ | ✅ | | Sets eeprom field 0x34 with some computed data 0xc1 | SetAudioMapping | ⛔️ | ✅ | | Sets the jingle to play for various events 0xc5 | SetUserLedColor | ⛔️ | ⛔️ | | 0xc6 | SendIRCode | ⛔️ | ⛔️ | | 0xc7 | StopIR | ⛔️ | ⛔️ | | # ControllerInfoRequest/ControllerInfoResponse When sent from Host to Controller, takes no data and asks the controller to send its ControllerInfo. When sent from Controller to Host: The additional data is an array of HardwareInfo, where HardwareInfo is a structure of one byte (Type) and 4 byte (Data). Here are the different types byte observed: Type ID | Name | Bootloader | Description ----------|--------|-----------|--------- 1 | USB PID | ✅ | The same as the USB PID. Likely more useful for wireless transmission. 2 | Unknown | ⛔️ | Firmware always returns 3 4 | Firmware Version | ✅ | Version/timestamp of the firmware running on the LPC side (e.g. 57bf5c10). 5 | NRF Firmware Version | ⛔️ | Version/timestamp of the firmware running on the NRF side 9 | Hardware Version | ✅ | Version of the controller hardware (as stored in EEPROM) 10 | Unknown | ⛔️ | Firmware returns the data stored at DAT_10000078 # SetSettings When sent from Host to Controller: Sets the controller settings. The additional data is an array of ControllerSetting, where ControllerSetting is a structure of one byte (type) and 2 bytes (value). Here are some observed type bytes: Type ID | Name | Description -------|----------|------------- 0x3 | ? | 0x8 | ? | Lizard mode related? Data is 7 when sent to dongle. 0x2d | ? | Sent periodically # SetDonglePairingMode Arguments: - Don't pair: 0x00 0x00 - Start pairing: 0x2 0x3c