# HMDUtility (BeyondHID) — protocol & firmware study

Source: `C:\Users\decid\Documents\projects\HMDUtility` (READ-ONLY, not built).
Git: current branch `main` (HEAD `a7d07d7`, "Merge PR #216 from BigscreenVR/release"),
clean vs `origin/main`. ~60 remote branches (notable: `vxr-firmware`, `synaptics-test`,
`dev-fw-22`, `dev-custom-dfu-util`, `feat-ipd-change`, `dev-lighthouse-serials`,
`et-cams-firmware`, `eyetracking-deployment`, `dev-gen2-fan`, `72hz`). Owner org
`BigscreenVR` → first-party Bigscreen utility.

## 1. Purpose / platform / language / distribution
- Official Bigscreen **Beyond** companion app ("Software utility for controlling the Beyond", `README.md`). Sets display brightness/color/refresh, fan, mic gain, IPD, runs HW self-tests, reads serials/usage timers, pulls crash dumps, **and flashes firmware**. Also manages eye-tracking (Beyond 2E): ONNX model download, ET enrollment, SteamVR ET driver install, alignment overlay.
- **Windows-only** C++17, Dear ImGui + GLFW + OpenGL UI (`src/ui.cpp` ~210KB). Heavy Win32 (SetupAPI device enum, registry, Media Foundation, WinToast). No Linux path despite the sauna brief — would be a full rewrite of the device layer. Deps (`dependencies/`): hidapi, libusb-1.0, onnxruntime+DirectML, OpenCV 4.12 (LFS), cpr (HTTP), SHA, picopng, wintoastlib.
- Build: `build.ps1` → MSBuild VS2022, x64, `src/BeyondHID.sln`. `APP_VERSION` from MSBuild `$(UTILITY_APP_VERSION)`. Single `BeyondHID.exe`; single-instance mutex `com_bigscreen_hmd_utility`; installs a Startup-folder shortcut (`-startupLaunch`), self-elevates; registers `bset://` URI handler (carries ET account token `t=`). `signtool.exe` (repo root) for code signing.
- LFS blobs (`.gitattributes`): `ETEnroll.exe`, `opencv_world4120.dll`, `opencv_world4120d.dll`. (Firmware blobs below are committed normally, not LFS.)

## 2. Host↔headset USB/HID surface
VID/PIDs (all literals in `src/hid_handler.cpp`, `src/firmware_update.h`):
| VID:PID | Role | Transport |
|---|---|---|
| `35BD:0101` | HMD application | HID **feature reports** |
| `35BD:4004` | MCU bootloader | HID **output reports** (`hid_write`) |
| `35BD:1001` | crash handler | HID feature reports |
| `35BD:0282` | DFU device (ET/FPGA) | **USB DFU 1.1** via libusb |
| `35BD:0202` | "Bigeye" ET camera (UVC) | enumerated/uninstalled only |
| `28DE:2300` | Tundra tracking SiP (Valve VID) | read serial; config via SteamVR tool |

Probe order at startup: 0101→4004→1001, else Disconnected; PID 0282 present ⇒ EyetrackingDFU state (`hid_handler.cpp:842-887`). Reports are **64 bytes**, byte[0]=report ID `0x00`, byte[1]=opcode.

**Application opcodes** (`hid_handler.cpp:12-433`; resp code echoes opcode, strings NUL-terminated):
- `0x2A` software/firmware version → resp `0x2A` (`firmware_version`).
- `0x25` PCBA serial; `0x26` **HMD serial** (drives model detection); `0x7E` tracking-flex serial; `0x5E` OLED serial.
- `0x5A` usage timer [type 0/1/2] → u32×10 s (total powered / display-on / longest).
- Mic: `0x53` stereo[0/1], `0x47` integer gain[u16 BE], `0x67` fractional gain[u16 BE Q15].
- Fan: `0x46` immediate[0-100], `0x66` deferred[0-100].
- `0x52` periodic data rate[u16 BE ms]; `0x50` OLED vertical flip; `0x49` **OLED brightness[u16 BE 0-1023]**; `0x74` proximity offset[u16 LE].
- `0x4C` **RGB LED[r,g,b]**; `0x4D` proximity sensor tuning (11 bytes).
- `0x42` restart→bootloader; `0x65 0x42` FPGA switch; `0x40` enter FATP; `0x64` **EDID switch[0=75+90,1=90,2=72]** (= refresh-rate/timing mode); `0x6F` direct-OLED write[which,len,data].
- Config flash: `0x55` read block[0-15]→32B (resp `0x55`), `0x57` write block[blk,32B], `0x56` **save/commit**.
- VXR7200 (Synaptics DP→MIPI bridge): `0x41` program[len,addr,data], `0x44` delete 64k bank[0-7], `0x4B` checksum, `0x54` query tags, `0x59` reset, `0x4E` name. (NB addr bytes masked `&0xF` — looks like a bug, cite as-is `hid_handler.cpp:303-310,346-349`.)
- `0x4A` HW self-test[char]: `H`hub `L`LED `S`USB-C switch `V`VXR7200 `P`prox `O`OLED `F`fan.
- Async telemetry resp `0x23` (`hid_handler.cpp:1259-1296`): fan RPM, proximity, **CC1/CC2 USB-C pin levels**, mainboard temp (float Kelvin→C), left/right display temps (C). `0x24`=success, `0x45`=fail.

**Config region** = 512B TLV in MCU flash, per-entry CRC8 (poly `0x07`, init `0xFF`), terminator `0xFF` (`hid_handler.cpp:690-829`). Tags (`hid_handler.h:111-139`): `01`PCBA serial, `02`RGB color, `03`fan speed, `04`prox-disable, `05`**Linkbox_v1 (deprecated: HPD signal level for old linkboxes)**, `06`prox cal, `07`FATP mode (forces non-DSC), `08`HMD serial, `09`tracking-flex serial, `0A`display brightness(0-1023), `0B`prox threshold, `0C`prox hysteresis, `0D`EDID switch, `0E`prox offset, `0F`VXR sleep enable.

**Bootloader** (`35BD:4004`, output reports, CRC8 init **0**): `0x2A` version, `0x42` run-app, `0x44` write[len,addr-LE,data,crc8], `0x50` program-page[addr], `0x22` erase-app, `0x65` erase-flash[sizecode 0=8kB/1=16kB/2=128kB-sector, addr].
**Crash handler** (`35BD:1001`): R/S/T/G/I/M/C opcodes dump ARM Cortex-M state (R0-R15, PSR/MSP/PSP, S0-S31, FPSCR) + named memory regions (`hid_handler.cpp:531-688,1133-1252`) → STM32-class MCU w/ FPU.

## 3. Firmware update flows & bundled blobs
**A. MCU app** (`firmware_update.cpp`, `firmware_update.h`). Container `*.beyondfw`: `[1B verlen][ver ascii][4B start addr LE][4B blob len LE][blob][64B SHA-512 of all preceding]`. Gating: semver compare (`mcu_update_available`). Flash map: `APP_ADDRESS=0x00404000`, `PAGE=0x200`, chunk 32B, max app `0x1C000+3×0x20000`. Flow: `0x42` reboot→BL → erase (by 8/16kB blocks or full sectors) → write 32B chunks → program page → poll status. **Bundled `src/latest.beyondfw` = v0.3.18, addr 0x00404000, 78976B blob.**
**B. ET/FPGA DFU** (`35BD:0282`, `dfu_utilities.cpp`): standard **USB DFU 1.1** (DNLOAD/UPLOAD/GETSTATUS/CLRSTATUS/ABORT control transfers), discovered by interface class `0xFE`/subclass `0x01` + DFU functional descriptor `0x21`. Container `*.dfu` with 16B DFU suffix (magic `UFD`, **CRC32**). Version = u16 at offset −16 (hex). 32kB sectors, write-then-readback verify, Winbond 2MB flash (1MB app, 1MB BL per `firmware_update.h:19-23`). **Bundled `src/eyetracking_firm.dfu` = version 0x53(83), 855727B.** Header constants call PID 0282 "FPGA"; runtime code calls it "eyetracking dfu" — same DFU device.
**C. VXR7200 (Synaptics)**: programmed via MCU passthrough HID (opcodes `0x41/0x44/0x4B/0x54/0x59/0x4E`); no standalone VXR blob committed on `main` (see remote `vxr-firmware`/`synaptics-test`).
**D. Tundra SiP**: **not** flashed here — only LHR serial read + config edited via SteamVR tool.

## 4. SteamVR / OpenVR interaction
- Beyond runs as a **native SteamVR HMD via the Tundra lighthouse driver** (presence id `35BD.0101`). 6DOF needs base stations.
- Bundled drivers installed with `…/SteamVR/bin/win64/vrpathreg.exe adddriver` (`utils.cpp:658-770`):
  - `src/steamvr/BeyondSteamVR/` — "bridge" driver `driver_BeyondSteamVR.dll` + `openvr_api.dll`; manifest `alwaysActivate:true`, `hmd_presence:["35BD.0101"]`; `default.vrsettings` exposes `ipd_mm`, `display_brightness`.
  - `SteamVRDriver/` (repo root) — **resourceOnly** `bigscreenbeyond` (status icons only).
  - `src/eyetracking/ETDriver/` ("BeyondEyetracking") + `ETEnroll`, `AlignmentHelper/ETCalOverlay.exe` overlay (`steamvr_watch.cpp` launches it while SteamVR runs).
- Uses `vrcmd.exe` for settings, `vrpathreg show` to verify install. No lighthouse/base-station driver install or watchman flashing.

## 5. IMU / tracking / extended-display / linkbox
- **No raw IMU/pose stream exposed by the utility.** Telemetry `0x23` carries thermals/fan/prox/USB-C only. IMU lives in the Tundra flex (`firmware_config.imu_rate=1000`) and is consumed by SteamVR; IMU **calibration** is only read/written inside the lighthouse config JSON (below).
- No Windows "extended/desktop monitor" feature. The Beyond is a **direct-mode DisplayPort sink**: EDID vid `BIG` pid `1234`, **DSC v17, 4 slices, 1920×1920/eye, 75/90Hz** (72Hz code exists but `enable_72hz_option()` returns false). Refresh switch = EDID-timing command `0x64` (`update_refresh_rate`, `utils.cpp:1707`). FATP/non-DSC fallback via `0x40`/config `0x07`.
- **Linkbox**: only deprecated config tag `05` (HPD level for old linkboxes) + USB-C switch self-test `S` + CC1/CC2 telemetry. No richer linkbox protocol.

## 6. Beyond 1 vs 2 (BS2/BS2E)
`Utils::get_device_type()` (`utils.cpp:2042`) keys on **HMD-serial prefix**: `BS1`, `BS2`, or `BS2E` (Beyond 2 + eye-tracking). Differences: ET camera `35BD:0202` "Bigeye" + ONNX models + ET SteamVR driver only on **BS2E** (`ui.cpp:5365 has_eyetracking = BS2E`); **IPD adjust on BS2/BS2E only** (`ipd_supported = BS2E||BS2`); BS1 IPD fixed. USB-tree hub depth differs for lighthouse-serial parent matching (BS1=1 level, BS2*=2, `hid_handler.cpp:1503`). Remote `dev-gen2-fan` ⇒ gen2 fan tuning.

## 7. Distortion / calibration handling  ← key for sauna
Per-unit calibration is **NOT in the Bigscreen MCU**. It lives in the **Tundra/lighthouse tracker flash** and is read/written through SteamVR's **`lighthouse_console.exe`** (`rs.exe_path`) over stdin (`utils.cpp:250 write_to_stdin`): `serial LHR-xxxx` → `downloadconfig file.json` / `uploadconfig file.json`. IPD change = download config, regex-edit `default_mm`/IPD, validate, re-upload (`utils.cpp:1519-1700`). Backups saved as `lighthouse_config_backup_<LHR>.json`.
Sample committed: `src/lighthouse_config_new.json`, `src/lighthouse_config_backup.json` (serial `LHR-A6379A9C`, revision 0.11). Schema (standard Valve lighthouse JSON):
- `tracking_to_eye_transform[2]` (per eye): `distortion{type:"DISTORT_DPOLY3", coeffs[3], center_x, center_y}`, `intrinsics[3×3]`, `eye_to_head[3×3]`, `grow_for_undistort`, `undistort_r2_cutoff`. **← warp/distortion + projection.**
- `imu{acc_bias[3], acc_scale[3], gyro_bias[3], plus_x[3], plus_z[3], position[3]}`. **← IMU intrinsics/extrinsics.**
- `ipd{high_mm, low_mm, minstep_mm}`, `head{plus_x, plus_z, position}`, `display_color_mult[2][3]`, `dsc_version/slices/bppx16`, `direct_mode_edid_vid/pid`, `device{eye 1920×1920, first/last_eye}`.
- `lighthouse_config{modelPoints[22], modelNormals[22], channelMap[22]}` (base-station photodiode constellation).
No cloud distortion store. The only cloud is the ET-model backend: `cpr` GET `{serverApiAddress}/get_user_id|get_user_files|get_model` with `Authorization: Bearer <token>` (`user_data_manager.cpp`), serving `.model` (ONNX) files to `%APPDATA%`. `serverApiAddress_` is configured, not hardcoded here.

---
## Relevance to sauna
- **Reusable protocol/code (Windows host):** the full Beyond control plane is plain 64-byte HID feature reports to `35BD:0101` via hidapi — directly portable. Immediately useful for a sauna host: `0x49` brightness, `0x4C` RGB, `0x46/0x66` fan, `0x64` EDID/refresh, `0x2A` version, `0x26` HMD serial (→model). Config TLV (CRC8 0x07) + bootloader/DFU flows are fully specified above. Cross-platform: hidapi+libusb already used, so a Linux port of *device control* is feasible; the SteamVR/SetupAPI/registry layers are not.
- **Display path is the easy win:** Beyond is a standard DP monitor (DSC, 1920×1920/eye, 75/90Hz, EDID `BIG:1234`). A sauna compositor can render to it **without SteamVR**; the hard parts are (a) applying per-eye distortion and (b) head tracking.
- **Calibration access path (critical):** exact per-unit distortion (`DISTORT_DPOLY3` coeffs + intrinsics + `eye_to_head`), color, and IMU calibration are retrievable as JSON via `lighthouse_console.exe serial …; downloadconfig` — **no base stations required for the read**. This is the cleanest source of warp-mesh/IPD/IMU-cal data for an independent renderer. (`lighthouse_console` ships with SteamVR; protocol is its stdin command set.)
- **3DOF blocker:** the Tundra IMU is **not exposed** over Bigscreen HID; today it is only reachable through SteamVR's lighthouse driver (which expects base stations for fusion). For sauna's no-lighthouse 3DOF goal, IMU access must come from elsewhere — reading the Tundra/`28DE:2300` USB endpoints directly (reverse-engineering needed) or a SteamVR null/IMU-only driver path. The utility provides the **calibration** but not a tracking data feed.
- **No hardware changes needed** for display+control; the open question for sauna is purely the IMU data tap, since calibration and display are already accessible.
