[GH-ISSUE #227] ESB metadata forwarding broken #97

Closed
opened 2026-10-22 05:03:44 +00:00 by roman · 0 comments
Owner

Originally created by @Leseratte10 on GitHub (Aug 9, 2026).
Original GitHub issue: https://github.com/safijari/openpuck/issues/227

The original Puck has four ESB interfaces (corresponding to the four connected controllers) that you can use to send messages directly to a connected Controller. OpenPuck doesn't seem to handle them correctly and instead answers them all with its own hardcoded data from identity.cpp.

To reproduce the error, pair your controller to an OpenPuck, then modify the pairing entry on the Puck (either using scpair or by exporting and importing the config) so that it no longer contains the serial number of the paired controller, so it looks like this:

$ python3 scpair.py list
Puck: FXB1234567890 (OpenPuck)
  slot 0: MyController (key 86d6bc6fba61bbb2)
  slot 1: Empty
  slot 2: Empty
  slot 3: Empty

Now connect the controller to the Puck, and then run "scpair.py list" from my fork in merge request #222

When you do that on an OpenPuck, it'll display the following:

Puck: FXB1234567890 (OpenPuck), firmware 6A18D053
  slot 0: MyController (key 86d6bc6fba61bbb2), connected to controller MyController
  slot 1: Empty                                                        ^^^^^^^^^^^^
  slot 2: Empty                                                     wrong controller name
  slot 3: Empty

Controller (wireless): MyController, firmware 6A18D053   <<<<< this is the wrong firmware and wrong name!
  slot 0: FXB1234567890 (key 86d6bc6fba61bbb2)
  slot 1: Unused

Which means the controller name is wrong, and the controller firmware is wrong. Instead of showing name and firmware from the wirelessly-connected controller, it shows name and firmware from its own data.

When you instead run the same command on a Valve puck (or Steam Machine), it will display the following - so despite the fact that the puck's pairing record contains "MyController" instead of the serial number, it can still read and display the actual serial number of the paired controller:

Puck: FXB1234567890 (Valve Puck), firmware 6A628359
  slot 0: MyController (key 86d6bc6fba61bbb2), connected to controller FXA9876543210
  slot 1: Empty
  slot 2: Empty
  slot 3: Empty

Controller (wireless): FXA9876543210, firmware 6A628345   <<<<< this is the correct firmware and correct name
  slot 0: FXB1234567890 (key 86d6bc6fba61bbb2)
  slot 1: Unused

Can you make OpenPuck behave like the original Puck, in that it should forward these requests to the controller to be answered?

This bug is most likely the root cause of #205 and #208, and the reason why Steam sometimes displays an invalid serial number in the controller settings, doesn't always correctly show the active slot, and doesn't always show "Steam Controller" for the pairing when looking at a Puck's pairings. Also, I would not be surprised if fixing this might also fix #214 and some of the jankiness around the controller haptics.

Looking at the source code in puck_hid.cpp in handleSet, there's a list "localAnswer" that marks the requests that should be answered by the Puck and not forwarded to the controller. 0xAE (read serial) and 0x83 (read attributes) is on that list, so you'll never be able to read the serial number or the attributes of a controller.

The comment for the 0xAE case explains why the behaviour is wrong - because it reads the serial number from the puck's bonding record, which may not always be the serial number of the connected controller. The original puck forwards both 0xAE and 0x83 to the controller if the request ID is set to 1, and only responds with its own data if the request ID is set to 2.

Same goes for 0x83 - the code doesn't actually forward the request to the controller, it just changes the PID from 0x1304 to 0x1302. Which makes all the metadata in there invalid (like, containing the Puck's firmware version instead of the controller's).

Also, request 0xED is supposed to return the controller's bond records, read from the controller over the wireless link. Not return the puck's bonding records. Right now, OpenPuck fakes a response with one slot only, which is the reason Steam doesn't correctly show a connected controller's pairings.

I tried to fix this, but I'm confused by the code. In puck_hid.cpp I can see a bunch of code that sends data to a wirelessly-connected controller by calling relayEnqueue - which for some weird reason seems to live in haptics.cpp - but I cannot find any code where it processes responses sent by the controller to these requests?

In general, most if not all requests sent with rid=1 are supposed to be forwarded to the controller, the controller will respond, and the puck will forward that response over USB. Only requests with rid=2 are supposed to be answered by the Puck itself.

@safijari can you shed some light here?

Originally created by @Leseratte10 on GitHub (Aug 9, 2026). Original GitHub issue: https://github.com/safijari/openpuck/issues/227 The original Puck has four ESB interfaces (corresponding to the four connected controllers) that you can use to send messages directly to a connected Controller. OpenPuck doesn't seem to handle them correctly and instead answers them all with its own hardcoded data from identity.cpp. To reproduce the error, pair your controller to an OpenPuck, then modify the pairing entry on the Puck (either using scpair or by exporting and importing the config) so that it no longer contains the serial number of the paired controller, so it looks like this: ``` $ python3 scpair.py list Puck: FXB1234567890 (OpenPuck) slot 0: MyController (key 86d6bc6fba61bbb2) slot 1: Empty slot 2: Empty slot 3: Empty ``` Now connect the controller to the Puck, and then run "scpair.py list" from my fork in merge request #222 When you do that on an OpenPuck, it'll display the following: ``` Puck: FXB1234567890 (OpenPuck), firmware 6A18D053 slot 0: MyController (key 86d6bc6fba61bbb2), connected to controller MyController slot 1: Empty ^^^^^^^^^^^^ slot 2: Empty wrong controller name slot 3: Empty Controller (wireless): MyController, firmware 6A18D053 <<<<< this is the wrong firmware and wrong name! slot 0: FXB1234567890 (key 86d6bc6fba61bbb2) slot 1: Unused ``` Which means the controller name is wrong, and the controller firmware is wrong. Instead of showing name and firmware from the wirelessly-connected controller, it shows name and firmware from its own data. When you instead run the same command on a Valve puck (or Steam Machine), it will display the following - so despite the fact that the puck's pairing record contains "MyController" instead of the serial number, it can still read and display the actual serial number of the paired controller: ``` Puck: FXB1234567890 (Valve Puck), firmware 6A628359 slot 0: MyController (key 86d6bc6fba61bbb2), connected to controller FXA9876543210 slot 1: Empty slot 2: Empty slot 3: Empty Controller (wireless): FXA9876543210, firmware 6A628345 <<<<< this is the correct firmware and correct name slot 0: FXB1234567890 (key 86d6bc6fba61bbb2) slot 1: Unused ``` Can you make OpenPuck behave like the original Puck, in that it should forward these requests to the controller to be answered? This bug is most likely the root cause of #205 and #208, and the reason why Steam sometimes displays an invalid serial number in the controller settings, doesn't always correctly show the active slot, and doesn't always show "Steam Controller" for the pairing when looking at a Puck's pairings. Also, I would not be surprised if fixing this might also fix #214 and some of the jankiness around the controller haptics. Looking at the source code in puck_hid.cpp in handleSet, there's a list "localAnswer" that marks the requests that should be answered by the Puck and not forwarded to the controller. 0xAE (read serial) and 0x83 (read attributes) is on that list, so you'll never be able to read the serial number or the attributes of a controller. The comment for the 0xAE case explains why the behaviour is wrong - because it reads the serial number from the puck's bonding record, which may not always be the serial number of the connected controller. The original puck forwards both 0xAE and 0x83 to the controller if the request ID is set to 1, and only responds with its own data if the request ID is set to 2. Same goes for 0x83 - the code doesn't actually forward the request to the controller, it just changes the PID from 0x1304 to 0x1302. Which makes all the metadata in there invalid (like, containing the Puck's firmware version instead of the controller's). Also, request 0xED is supposed to return the *controller's* bond records, read from the controller over the wireless link. Not return the puck's bonding records. Right now, OpenPuck fakes a response with one slot only, which is the reason Steam doesn't correctly show a connected controller's pairings. I tried to fix this, but I'm confused by the code. In puck_hid.cpp I can see a bunch of code that sends data to a wirelessly-connected controller by calling relayEnqueue - which for some weird reason seems to live in haptics.cpp - but I cannot find any code where it processes responses sent by the controller to these requests? In general, most if not all requests sent with rid=1 are supposed to be forwarded to the controller, the controller will respond, and the puck will forward that response over USB. Only requests with rid=2 are supposed to be answered by the Puck itself. @safijari can you shed some light here?
roman closed this issue 2026-10-22 05:03:45 +00:00
Sign in to join this conversation.
No labels
pull-request
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
safijari/openpuck#97
No description provided.