ca_file or the system's CAs (MOSQ_OPT_TLS_USE_OS_CERTS), client certificate, broker certificate and host name verified, settings errors reported before connecting. The client thread blocks SIGPIPE: with TLS, a write to a connection the broker closed killed the process. The reason of a failed handshake (libmosquitto's log) is appended to the error. tst_mqtt covers TLS with test certificates in tests/data (D44). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
170 lines
8.8 KiB
Markdown
170 lines
8.8 KiB
Markdown
# FrameAssistant
|
||
|
||
**Steam Frame companion for Home Assistant.**
|
||
|
||
FrameAssistant runs on the Steam Frame (and on x86_64 Linux PCs with SteamVR). It connects
|
||
the headset to Home Assistant in both directions:
|
||
|
||
- **Sensors:** it reports the state of the headset and its surroundings to Home Assistant.
|
||
- **Services:** Home Assistant can call it, e.g. to show notifications in the headset.
|
||
- **Dashboard:** a SteamVR dashboard tab to view and control Home Assistant from inside VR.
|
||
|
||
> **Status:** early development. The overlay platform works on the Frame. The Home Assistant
|
||
> parts are being built: so far the settings file and the MQTT connection (the device appears
|
||
> in Home Assistant with a test sensor, "Uptime"; the tab shows the connection status). See [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md),
|
||
> [docs/DESIGN.md](docs/DESIGN.md) and [docs/PROGRESS.md](docs/PROGRESS.md).
|
||
|
||
### Platform
|
||
|
||
FrameAssistant grew out of a SteamVR dashboard-overlay template, which it still builds on:
|
||
|
||
- **Plain QML UI:** rendered off-screen and shown as a SteamVR dashboard tab. Users can float or
|
||
pin the tab anywhere, and it stays visible in games.
|
||
- **Input:** SteamVR's laser (as mouse), thumbstick scrolling, the SteamVR keyboard (opens
|
||
for text fields), and D-pad/button navigation via rebindable SteamVR Input actions.
|
||
- **Vulkan** by default, with **OpenGL** as an automatic fallback. On the Frame, Vulkan is
|
||
about 1.9× faster, since OpenGL runs through zink there.
|
||
- **Several views per app** ("surfaces"), e.g. several dashboard tabs sharing one QML engine
|
||
and its data.
|
||
- **Develop without a headset:** desktop preview window, offscreen snapshots, benchmark.
|
||
- **Frame packages** of about 3 MB, using SteamOS's own Qt; a self-contained variant with Qt
|
||
bundled is also available. Packages come with `install.sh`/`uninstall.sh` and a deploy
|
||
script.
|
||
|
||
## Quick start
|
||
|
||
Requirements:
|
||
- CMake ≥ 3.22, Ninja, a C++20 compiler, and Qt ≥ 6.5 (Quick, QuickControls2, OpenGL;
|
||
Test for the tests)
|
||
- Vulkan headers (optional; without them the build is OpenGL only)
|
||
- Docker or Podman, for the Frame builds
|
||
|
||
The OpenVR SDK files are downloaded automatically when CMake configures the build
|
||
(`-DOPENVR_ROOT=<sdk>` to build offline).
|
||
|
||
```sh
|
||
cmake --preset native-debug && cmake --build --preset native-debug
|
||
ctest --preset native-debug
|
||
|
||
build/native-debug/bin/frame-assistant --desktop # UI in windows, no SteamVR
|
||
build/native-debug/bin/frame-assistant --desktop-novr # same; never uses SteamVR
|
||
build/native-debug/bin/frame-assistant --snapshot out.png # one frame through the VR path
|
||
build/native-debug/bin/frame-assistant # as dashboard tab (SteamVR running)
|
||
build/native-debug/bin/frame-assistant --install-manifest # register + auto-start with SteamVR
|
||
```
|
||
|
||
Overlay and both desktop modes need a settings file (below) and connect to the MQTT broker;
|
||
`--snapshot` and `--benchmark` don't.
|
||
|
||
On the Steam Frame:
|
||
|
||
```sh
|
||
scripts/package-release.sh # → dist/frame-assistant-0.1.0-linux-arm64.tar.gz
|
||
scripts/deploy-to-frame.sh <user>@<frame> # copy over SSH and install (SteamVR must run)
|
||
```
|
||
|
||
Details are in [docs/STEAM-FRAME.md](docs/STEAM-FRAME.md).
|
||
|
||
## Settings
|
||
|
||
FrameAssistant reads its settings from `~/.config/frameassistant/config.json`
|
||
(`$XDG_CONFIG_HOME` is respected; `--config <path>` picks another file). Start from
|
||
[`resources/config.example.json`](resources/config.example.json), which packages ship as
|
||
`share/frameassistant/config.example.json`:
|
||
|
||
```json
|
||
{
|
||
"device": { "name": "Steam Frame" },
|
||
"mqtt": { "host": "homeassistant.local", "username": "frameassistant", "password": "…" },
|
||
"homeassistant": { "url": "http://homeassistant.local:8123" }
|
||
}
|
||
```
|
||
|
||
| Key | Required | Default | Meaning |
|
||
|-----|----------|---------|---------|
|
||
| `device.name` | yes | | Name of this headset in Home Assistant |
|
||
| `device.ha_unique_id` | no | derived from the name | Fixed id, so the name can change without creating a new device |
|
||
| `mqtt.host` | yes | | MQTT broker |
|
||
| `mqtt.port` | no | 1883, 8883 with TLS | |
|
||
| `mqtt.username`, `mqtt.password` | no | no login | |
|
||
| `mqtt.tls.enabled` | no | `false` | |
|
||
| `mqtt.tls.ca_file` | no | system certificates | CA of the broker's certificate |
|
||
| `mqtt.tls.cert_file`, `mqtt.tls.key_file` | no | | Client certificate (needs `ca_file`) |
|
||
| `homeassistant.discovery_prefix` | no | `homeassistant` | |
|
||
| `homeassistant.url` | no | | Home Assistant's address; reserved for the dashboard, not used yet |
|
||
| `notifications.timeout_s` | no | 10 | How long notifications stay |
|
||
|
||
Invalid or missing settings stop the app with an error message and exit code 1. Unknown keys
|
||
only produce a warning. Keep the file private (`chmod 600`) when it holds a password.
|
||
|
||
## Development
|
||
|
||
- **Host build dependencies:** Qt 6 (≥ 6.5) and libmosquitto with its pkg-config file
|
||
(Arch: `mosquitto`, Debian: `libmosquitto-dev`); the `mosquitto` broker for the MQTT
|
||
integration tests. The containers (`docker/`) have everything.
|
||
- **App identity** (app key `net.matzman.frameassistant`, tab name, executable name
|
||
`frame-assistant`, texture size) is set in the root `CMakeLists.txt`. The executable must not
|
||
be named like a SteamVR process such as `vrdashboard` (CMake checks this).
|
||
- **UI:** `src/qml/`, with `Main.qml` as the root. List new QML files under `QML_FILES` in
|
||
`vrdash_add_dashboard()` in `src/CMakeLists.txt`. Iterate with `--desktop` and `--snapshot`.
|
||
- **C++ logic:** the app class `src/Dashboard.h/.cpp` derives from `DashboardApplication`.
|
||
It adds views in its constructor (`addSurface()`) and its own command-line options,
|
||
keeps its objects as members, loads the settings in `prepare()` and wires things up in
|
||
the `loading()`/`started()` overrides. Logic without SteamVR or rendering goes into the
|
||
static library `frameassistant_lib` (e.g. `src/config/`), which the tests link.
|
||
- **Controller buttons:** actions and default bindings are in `resources/input/`.
|
||
- **Ship it:** `scripts/package-release.sh` and `scripts/deploy-to-frame.sh`, see
|
||
[docs/STEAM-FRAME.md](docs/STEAM-FRAME.md).
|
||
|
||
How the pieces fit together: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
||
|
||
## Command line
|
||
|
||
`frame-assistant` understands:
|
||
|
||
| Option | Meaning |
|
||
|--------|---------|
|
||
| *(none)* | Run as SteamVR dashboard overlay (SteamVR must be running) |
|
||
| `--desktop` | Show the UI in desktop windows, one per surface; no SteamVR (from iteration 3: uses SteamVR if it runs) |
|
||
| `--desktop-novr` | Like `--desktop`, but never connects to SteamVR (e.g. on a PC, with its own `--config`) |
|
||
| `--config <path>` | Settings file (default `~/.config/frameassistant/config.json`) |
|
||
| `--snapshot <png>` | Render one frame per surface off-screen and save it (`<name>-<id>.png` with several surfaces) |
|
||
| `--benchmark <frames>` | Render frames back to back with a full GPU wait each and print frame times |
|
||
| `--graphics-api vulkan\|opengl` | Render backend. Default: Vulkan, falling back to OpenGL if Vulkan doesn't start; an explicit choice never falls back |
|
||
| `--app-key <key>`, `--app-name <name>` | Override app key and name, e.g. to run a second instance |
|
||
| `--max-fps <fps>` | Frame-rate cap (default 60); rendering only happens on change |
|
||
| `--install-manifest [--launch]` | Register with SteamVR (auto-start) and optionally start it now; works over SSH |
|
||
| `--uninstall-manifest` | Unregister |
|
||
| `--manifest <path>` | Manifest for the two options above (default: the app's own) |
|
||
| `--log-file <path>` | Also log to a file (relative paths: relative to the install folder) |
|
||
|
||
## Testing
|
||
|
||
```sh
|
||
ctest --preset native-debug # unit, MQTT and render tests (GPU tests skip without a GPU
|
||
# context, MQTT tests without the mosquitto broker)
|
||
scripts/container-test.sh # all tests in Debian incl. Vulkan with validation layers
|
||
VK_DRIVER=lvp scripts/container-test.sh # same on the lavapipe software driver
|
||
```
|
||
|
||
## Repository layout
|
||
|
||
| Path | Content |
|
||
|------|---------|
|
||
| `src/main.cpp`, `src/Dashboard.*`, `src/qml/` | **The app** |
|
||
| `src/config/`, `src/mqtt/`, `src/ha/` | Settings file, MQTT client, HA device and entities (`frameassistant_lib`) |
|
||
| `src/app/` | `DashboardApplication` (app base class), `Surface` (one view) |
|
||
| `src/render/` | Off-screen Qt Quick renderer; Vulkan and OpenGL backends |
|
||
| `src/vr/` | SteamVR connection, overlays (dashboard tab) |
|
||
| `src/input/` | Laser/scroll/keyboard → Qt events; SteamVR Input navigation |
|
||
| `resources/` | SteamVR manifest template, input actions and bindings, example settings |
|
||
| `tests/` | Unit and render tests |
|
||
| `cmake/` | `vrdash_add_dashboard()`, OpenVR download, Qt bundling, toolchain |
|
||
| `docker/`, `scripts/` | Build containers; build, package, deploy and test scripts |
|
||
| `docs/` | Requirements, design, progress, architecture, Steam Frame notes |
|
||
|
||
## License
|
||
|
||
MIT, see [LICENSE](LICENSE). Third-party components (OpenVR SDK, Qt; in bundled builds also
|
||
their dependencies) are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|