Skip to content

Instantly share code, notes, and snippets.

@cliffrowley
Last active February 20, 2024 06:24
  • Star 50 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save cliffrowley/d18a9c4569537b195f2b1eb6c68469e0 to your computer and use it in GitHub Desktop.
Notes on the Stream Deck HID protocol

Stream Deck Protocol

How to interface with a Stream Deck device.

Synopsis

The device uses the HID protocol to communicate with its software.

Configuration

The number of keys can be determined from the HID device descriptors. There is currently no known provision to determine their layout or size. The buttons on the current version of the device (1.0) are arranged in a grid and numbered from top right through to bottom left as follows:

 ------------------------
| 05 | 04 | 03 | 02 | 01 |
|----|----|----|----|----|
| 10 | 09 | 08 | 07 | 06 |
|----|----|----|----|----|
| 15 | 14 | 13 | 12 | 11 |
 ------------------------

Note: the Stream Deck Mini was recently announced, with six buttons. I'll incorporate information about this device as and when I am able.

Host to Device

Feature reports

0x0B RESET

0B 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00

0x05 SET BRIGHTNESS

The brightness on the device is controlled with PWM. This results in a non-linear correlation between set percentage and perceived brightness, and lower values have the most effect.

PC = PERCENTAGE (0-100)

05 55 aa d1 01 PC 00 00 00 00 00 00 00 00 00 00
00

Output reports

0x02 SET KEY IMAGE

Images are 72x72 pixels (5184 pixels total) arranged in a BGR format with 3 bytes per pixel (15552 bytes total).

Images are sent to the device in two packets, the first containing 2583 pixels (7749 bytes) and the second containing the remaining 2601 pixels (7803 bytes). Each packet is a total of 8191 bytes.

Each packet comprises a header and a chunk of image data. The header contains:

  • The index of the key being set (zero based)
  • The packet sequence number (one-based)
  • The previous packet's sequence number (zero if there is no previous packet).

The first packet also contains some extra information that the second does not, the purpose of which is currently unknown.

Including headers, each image packet is exactly 8191 bytes, padded with zeros where appropriate. This leaves up to 8121 bytes available for image data in the first packet and 8175 bytes in the second, but for some reason the official software only sends a maximum of 7749 in the first packet and whatever is left in the second.

I'm assuming that either the device assumes the first packet will contain this amount of data, or that one of the values in the header specifies how long it will be. Experimentation needed.

Header
SE = PACKET SEQUENCE NUMBER
PR = PREVOUS PACKET SEQUENCE NUMBER
KI = KEY INDEX

02 01 SE 00 PR KI 00 00 00 00 00 00 00 00 00 00
First packet

Includes 54 bytes of "extra" information.

.. HEADER
42 4d f6 3c 00 00 00 00 00 00 36 00 00 00 28 00
00 00 48 00 00 00 48 00 00 00 01 00 18 00 00 00
00 00 c0 3c 00 00 13 0e 00 00 13 0e 00 00 00 00
00 00 00 00 00 00
.. 7749 BYTES OF IMAGE
.. ZERO PADDING UP TO 8191 BYTES
Subsequent packets
.. HEADER
.. REMAINING BYTES OF IMAGE
.. ZERO PADDING UP TO 8191 BYTES

Device to Host

Feature reports

0x03 SERIAL

BYTE 6 ONWARDS CONTAINS SERIAL

03 55 aa d3 03 41 4c 31 32 48 31 41 30 37 38 31
36

0x04 VERSION

BYTE 6 ONWARDS CONTAINS STRING VERSION

04 55 aa d4 04 31 2e 30 2e 31 37 30 31 33 33 00
00

Input reports

0x01 KEY STATE CHANGE

When any keys are pressed or released, the device sends an interrupt containing a bit map of the all the current key states. Presumably this means that multiple buttons can be pressed at once, though I haven't yet tested this.

Key 0B pressed (11th)
01 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00
00
Key released
01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00

Snooping

Communication between the device and host can be snooped using Wireshark. Please read this page for information on seting up USB capture.

When snooping USB you'll find that there's a lot of chatter from your hubs and other devices, and you'll need to filter it all out to isolate the StreamDeck. To do this, begin by filtering for usb.idVendor == 0x0fd9 && usb.idProduct == 0x0060, which will give you information about the device. Then simply filter "USB device index" to show the relevant packets for your device.

@WitoTV
Copy link

WitoTV commented Jan 6, 2020

@kinsi55 My device was seen as generic hid interface (no more info was given by the system). I have a friend with streamdeck who found vendor / product ID for me, but they seem to match with the ones provided at the end of this gist.

@WitoTV
Copy link

WitoTV commented Jan 6, 2020

@kinsi55 if you somehow succeed send me some more details.

@kinsi55
Copy link

kinsi55 commented Jan 6, 2020

@WitoTV Sure, if I get around to trying this and have any success I'll let you know. Thanks for now!

@kibix
Copy link

kibix commented Dec 28, 2021

There are some findings i would like to share ..
#1, there is a V1 and V2 of the original Streamdeck. They do not use the same protocol
#2, i received 'V1' style hardware but it has V2 electronics .. so it will need to speak the V2 protocol.
---> easy to detect. if you check the USB capabilities (lsusb -v on linux) the V1 uses 8191 Bytes transfer size out, the V2 uses 1024 Bytes
#3, this 54 bytes 'extra header' is actually well understandable. it is the BMP header of a BMP button file (described for example on wikipedia).
#4, with V2 devices, and the changed protocol, the device will still accept BMP icons but also jpeg icons
#5, i think the number of buttons is returned in the button event message in byte 3 (contains 15 for the original, to be seen on other devices)

I have implemented basic interfacing in lua, using /dev/hidraw0, no special libraries needed for that (but there is no way to send feature reports that way at least not that i am aware of).

@kinsi55
Copy link

kinsi55 commented Dec 28, 2021

Building your own Hardware / Firmware that communicates with the HID protocol of the StreamDeck definitely is possible :P https://twitter.com/Kinsi55/status/1296859058314645505

@ryantheleach
Copy link

@kinsi55 @DDRBoxman Any hints / updates to this?

@kinsi55
Copy link

kinsi55 commented Jul 21, 2022

@ryantheleach Nope not really, I got it to a fully implemented state as seen in the Tweet and thats where its been since

@ryantheleach
Copy link

@kinsi55 I meant, since you have a full implementation, that maybe you had some insight to whether this spec was complete or what to add.

@kinsi55
Copy link

kinsi55 commented Jul 29, 2022

@ryantheleach This is complete yes. For newer streamdeck revisions, the key images are transmitted as jpeg instead of raw rgb but thats it

@kibix
Copy link

kibix commented Oct 11, 2022 via email

@teras
Copy link

teras commented Oct 27, 2023

Hello.
I was trying to communicate with Elgato using the hidapi library, so in theory it should be easy when following these notes. The strange thing is that Elgato doesn't seem to be recognized either by the C library by doing a regular hid_open, not even is listed by the hidapitester application. It is not possible to open the device.

I am using Linux and I am worried if there is something more I need to do. It isn't a permission issue, since I am using streamdeck-ui as a user already, and I also tried running hidapi apps/lib by sudo.

I also tried linking with libhidapi-hidraw instead of libhidapi-libusb, although according to the documentation of streamdeck-ui, the libhidapi-libusb was used.

Any hints?

EDIT
Found the issue, and it was silly. When using the library, I 've left streamdeck-ui running. This app seems to have captured the USB device and thus I couldn't.

Sorry for the noise 😞

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