Commit Graph
5 Commits
Author SHA1 Message Date
345bf15ca3 Fix puck activation after firmware updates (#44)
The receiver stopped working after a controller firmware update. Original
diagnosis and fix by @danbosscher; extended here for Bluetooth, Steam
handoff, and several defects found during QA on the hardware.

The puck publishes a controller interface per slot plus a management
endpoint, and on current firmware an empty slot rejects the lizard-mode
command outright.

- Enumerate only the controller collection (FF00:0001), excluding the
  puck's management endpoint at FF00:0002
- Wait for a live 0x42/0x45 state report before sending feature commands,
  and skip slots that never produce one
- Prefer an exclusive claim but accept a shared handle when Windows keeps
  a compatible write handle open — only while Steam is absent, since a
  handle held with Steam running is probably Steam's, and driving the
  controller alongside it is worse than refusing. Refusing is what
  escalates to the device cycle that takes the handle back.
- Add the Nereid receiver PID (0x1305)

Defects found by QA and fixed here:

- The battery payload is [chargeState, percent] on every transport, not
  just Bluetooth. A 92% battery was reported, and fed to the DS4 battery
  level, as 1%.
- Game mode could not tell an idle receiver from a contested one, so a
  controller that was merely switched off triggered three device cycles,
  restarting the receiver's devnodes for nothing.
- A receiver raises no device event when a slot becomes occupied, so
  waking a controller went unnoticed for as long as it was left. Poll for
  it instead.
- Overlapping cycles undid successful claims: a cycle is asynchronous, and
  the arrivals it generated re-entered the acquire path and fired another
  on top of it, ripping the device back out a second after game mode came
  up. Cycles are now spaced.
- Failure was announced while the last cycle was still landing — the alert
  fired one second before success. The verdict now waits out a grace
  period.

Verified on the puck, Bluetooth, and wired: handoff to Steam on game
launch, reclaim afterwards, and no duplicate input.

Reclaiming takes around nine seconds on a four-slot receiver, dominated by
SetupDiCallClassInstaller tearing down each interface in turn. Three
attempts to shorten it (batching the cycle, a shorter settle, cycling only
the contested interface) all regressed the reclaim and were reverted; it
needs to be worked out against hardware rather than reasoned about.

Co-Authored-By: Dan Bosscher <danbosscher@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 23:34:08 -07:00
Dylan DeverillandClaude Fable 5 50a8ae246d Add Bluetooth LE support for the Steam Controller
The controller now works wired, through the dongle, or paired over
Bluetooth, with the full feature set: game mode, rumble, trackpad haptics,
battery, PlayStation-mode gyro, and taking the controller back from a
running Steam.

Windows exposes the entire vendor protocol over HID-over-GATT under PID
0x1303 — feature reports, output reports, and the 0x45 state report (54
bytes, ~157 Hz) all work through the existing HidDevice stack, so support
is a thin layer rather than a new transport backend.

- Add SC2026_BT_PID to enumeration and a per-slot Transport tag, detected
  from the interface path (HOGP GUID / bthenum markers win over PID
  tokens) and logged on CONNECT/DISCONNECT
- Swap the battery report 0x43 payload bytes on Bluetooth slots — the
  firmware sends [chargeState, percent], reversed from USB/dongle
- Bluetooth-specific disconnect alert copy, and a notification when
  acquisition gives up rather than a silently dead pad

Device cycling had to be reworked for reclaiming the controller from
Steam. DIF_PROPERTYCHANGE reports success while leaving the device and
Steam's handle untouched; CM_Query_And_Remove_SubTree is vetoed by open
handles; pnputil /restart-device works once, then leaves the devnode
reboot-pending and refuses thereafter. Disable+enable works repeatedly.

Two traps found against live hardware. SetupDiCallClassInstaller returned
ERROR_INVALID_DATA for a disable Windows had genuinely performed — the app
logged the device removal a tenth of a second before the call reported
failure — so return codes cannot gate the re-enable, or the controller is
stranded disabled and needs Device Manager to recover. CM_Get_DevNode_Status
is now the source of truth: whether the node went down decides if a real
cycle happened, the re-enable always runs and retries until the node
confirms it is enabled, and only a stranded device counts as failure. Some
devices also reject a global scope change, so DICS_FLAG_CONFIGSPECIFIC is
tried as well.

SteamProbe becomes a transport discovery tool: detailed enumeration with
transport detection, interface selection, non-fatal lizard-off with a
raw-dump fallback, an output-report test, report-rate measurement, and
--enum / --imu modes. The windowless cycle helper gains its own cycle.log,
since it cannot share the tray app's events.log (held open _SH_DENYWR) and
was previously impossible to diagnose.

Verified on hardware: game mode, rumble, haptics, battery, IMU streaming,
clean removal on power-off/sleep/range loss with automatic reconnect,
yielding to Steam on game launch, and reclaiming afterwards (~1.2s). Also
confirmed the firmware enforces one host transport at a time, so no
cross-transport deduplication is needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 17:41:43 -07:00
Dylan DeverillandClaude Sonnet 4.6 61454cc6ff Fix compatibility with post-Valve-update firmware (REPORT_STATE 0x42 → 0x45)
A Valve firmware update changed the main STATE report ID from 0x42 to
0x45. REPORT_STATE is updated to match; the now-redundant REPORT_EXTENDED
constant (which previously held 0x45) is removed.

Also adds better probe diagnostics to Open() — distinguishes between an
unexpected report ID (firmware mismatch) and a read timeout (empty slot).

Fixes a stale debug flag in SteamProbe: FORCE_SHOW_BYTES_16_17 reverted
to false (right joystick Y, not X).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:21:40 -07:00
Dylan DeverillandClaude Sonnet 4.6 48b44c34f9 Extend SteamProbe to enumerate all Valve HID interfaces
Added EnumerateAllValveDevices() to SteamProbe which lists every HID
interface with VID=0x28DE at startup — printing PID, usage page, usage,
and product string. This makes it easy to identify the wireless dongle's
PID without needing Device Manager.

Updated HidDevice::Enumerate to treat pid=0 as a wildcard so callers
can enumerate all devices for a given vendor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 20:36:34 -07:00
Dylan Deverill 589ed11e4a Initial release 2026-05-10 19:28:03 -07:00