Document iteration 1: settings file, HA Dashboard tab, new hooks

README: settings file, --config, the new tab. ARCHITECTURE.md and
CLAUDE.md: prepare() hook, app command-line options, app code layout,
WebEngine notes, the moc raw-string pitfall. THIRD_PARTY_NOTICES: Qt
WebEngine. Ship config.example.json; the package README explains the
set-up. PROGRESS.md and DESIGN.md: phase 3 results and deviations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 22:21:34 +02:00
co-authored by Claude Opus 5.5
parent 0676998033
commit 179316db52
9 changed files with 175 additions and 37 deletions
+34 -8
View File
@@ -29,7 +29,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
**FrameAssistant**, "Steam Frame companion for Home Assistant" (in the spirit of HASS.Agent): **FrameAssistant**, "Steam Frame companion for Home Assistant" (in the spirit of HASS.Agent):
it reports headset/environment sensors to Home Assistant, offers services Home Assistant can it reports headset/environment sensors to Home Assistant, offers services Home Assistant can
call (e.g. notifications in the headset), and shows a Home Assistant dashboard as a SteamVR call (e.g. notifications in the headset), and shows a Home Assistant dashboard as a SteamVR
dashboard tab. Status: the overlay platform works; the Home Assistant parts are being planned. dashboard tab. Status: the overlay platform works; the Home Assistant parts are being built
(implementation iteration 1: settings file and the "HA Dashboard" web UI tab).
**Start every session by reading `docs/PROGRESS.md`** (current state, next steps, log). **Start every session by reading `docs/PROGRESS.md`** (current state, next steps, log).
@@ -60,7 +61,7 @@ cmake --preset native-debug && cmake --build --preset native-debug # system Qt
ctest --preset native-debug # all tests ctest --preset native-debug # all tests
build/native-debug/bin/tst_inputbridge clickUsesGlCoordinates # one QtTest function build/native-debug/bin/tst_inputbridge clickUsesGlCoordinates # one QtTest function
build/native-debug/bin/frame-assistant --desktop # UI preview in a window, no SteamVR build/native-debug/bin/frame-assistant --desktop # UI preview in a window, no SteamVR
build/native-debug/bin/frame-assistant --snapshot x.png # one frame via the real offscreen path build/native-debug/bin/frame-assistant --snapshot x.png # x-0.png (main tab), x-dashboard.png
scripts/container-build.sh cross # x86_64 → aarch64 against the Frame's system Qt (default) scripts/container-build.sh cross # x86_64 → aarch64 against the Frame's system Qt (default)
scripts/container-build.sh native # same for the host arch (use on the Frame) scripts/container-build.sh native # same for the host arch (use on the Frame)
scripts/container-build.sh cross-bundle # fallback: self-contained, Qt bundled (also native-bundle) scripts/container-build.sh cross-bundle # fallback: self-contained, Qt bundled (also native-bundle)
@@ -70,7 +71,11 @@ VK_DRIVER=lvp scripts/container-test.sh # same on lavapipe (software Vulkan)
build/native-debug/bin/frame-assistant --graphics-api vulkan --benchmark 600 # frame ms incl. GPU build/native-debug/bin/frame-assistant --graphics-api vulkan --benchmark 600 # frame ms incl. GPU
``` ```
- Use `--snapshot` and then read the PNG to check UI or rendering changes without a headset. - Use `--snapshot` and then read the PNGs to check UI or rendering changes without a headset.
With several surfaces it writes `<name>-<id>.png` per surface (`-0` for the main tab's
empty id). Snapshot and benchmark don't load the settings, so the "HA Dashboard" tab shows
its "no URL" hint there.
- Overlay and desktop mode need a settings file; pass a test one with `--config <path>`.
- `tst_render` runs once per backend (`VRDASH_TEST_API`, separate CTest entries, because the - `tst_render` runs once per backend (`VRDASH_TEST_API`, separate CTest entries, because the
Qt graphics API is process-global). It QSKIPs without a GPU context or when Vulkan isn't Qt graphics API is process-global). It QSKIPs without a GPU context or when Vulkan isn't
compiled in. compiled in.
@@ -106,11 +111,14 @@ into Qt events.
modes (overlay, `--desktop`, `--snapshot`, `--benchmark`, manifest install). modes (overlay, `--desktop`, `--snapshot`, `--benchmark`, manifest install).
- **Inheritance for the app, components for the surfaces** (the user's decision, after - **Inheritance for the app, components for the surfaces** (the user's decision, after
trying composition): the app derives from `DashboardApplication`. In its constructor it trying composition): the app derives from `DashboardApplication`. In its constructor it
configures (`addSurface`, optional `setAppKey`/`setDisplayName`) and it configures (`addSurface`, optional `setAppKey`/`setDisplayName`) and passes its own
overrides the hooks `loading()` (backends + shared engine exist, QML not loaded) and command-line options (`QCommandLineOption` list) to the base constructor. It overrides
`started()` (everything runs). In the hooks, protected accessors give the running state: the hooks `prepare(QString*)` (first; mode and app key known, no engine/surfaces/SteamVR
`mode()`, `appKey()`, `appName()`, `engine()`, `surfaces()`, `surface()` (primary), yet; returning false ends `exec()` with exit code 1), `loading()` (backends + shared
`surface(id)`, `vrRuntime()`, `navigation()`. The app is `src/Dashboard.h/.cpp`. engine exist, QML not loaded) and `started()` (everything runs). In the hooks, protected
accessors give the running state: `mode()`, `appKey()`, `appName()`, `isOptionSet()`,
`optionValue()`, `engine()`, `surfaces()`, `surface()` (primary), `surface(id)`,
`vrRuntime()`, `navigation()`. The app is `src/Dashboard.h/.cpp`.
- The base constructor creates the `QGuiApplication` (`QCoreApplication` for manifest - The base constructor creates the `QGuiApplication` (`QCoreApplication` for manifest
commands), so derived members can be QObjects with timers. commands), so derived members can be QObjects with timers.
- All QML and surfaces are torn down **at the end of `exec()`**, while the derived object - All QML and surfaces are torn down **at the end of `exec()`**, while the derived object
@@ -188,6 +196,22 @@ into Qt events.
- **QML singletons owned by the app:** keep them as members of the app class. A singleton - **QML singletons owned by the app:** keep them as members of the app class. A singleton
must not be default-constructible, or QML builds its own instance instead of calling must not be default-constructible, or QML builds its own instance instead of calling
`create()` (e.g. give the constructor a required `QObject* parent` and init it `{nullptr}`). `create()` (e.g. give the constructor a required `QObject* parent` and init it `{nullptr}`).
- **App code layout:** logic without SteamVR or rendering goes into the static library
`frameassistant_lib` (`src/config/`, …), which the app and its tests link. QML types
(`QML_ELEMENT`, e.g. `src/ha/HaWebUi`) go into `QML_SOURCES` of `vrdash_add_dashboard()`:
they must be part of the QML module. The generated type registration includes their
headers by file name only, so their directory is on the app's include path.
- **Settings** (`src/config/Config`) are loaded once in `Dashboard::prepare()` (overlay and
desktop mode) and passed on as an immutable value (DESIGN.md, D19).
- **Qt WebEngine** ("HA Dashboard" tab): `QtWebEngineQuick::initialize()` runs in `main()`
before the app object, because it must precede the `QGuiApplication`. `HaWebUi` creates a
persistent profile under `~/.local/share/frameassistant/webengine` with the Qt 6.8 API
(setters on `QQuickWebEngineProfile`). Qt ≥ 6.9 logs a hint to use
`WebEngineProfilePrototype` instead, which the Frame's Qt 6.8 doesn't have.
- **moc and raw string literals:** a raw string containing `//` (e.g. a URL) makes moc treat
the rest as a comment; it then misses `Q_OBJECT` classes in that file (link error "vtable
for …"). In moc'ed files, write such text as normal string literals (`tst_config` uses
JSON with `'` quotes and converts them).
- Publish per-frame values (frame stats etc.) to QML at most about once per second. Binding - Publish per-frame values (frame stats etc.) to QML at most about once per second. Binding
per-frame values in QML re-triggers the on-demand renderer forever. per-frame values in QML re-triggers the on-demand renderer forever.
- **The executable is `frame-assistant` (`VRDASH_EXECUTABLE_NAME`), never `vrdashboard`.** - **The executable is `frame-assistant` (`VRDASH_EXECUTABLE_NAME`), never `vrdashboard`.**
@@ -307,3 +331,5 @@ Check these when testing on the device, and update this list:
- The restructured app code (base class `DashboardApplication`, `Surface`, `Overlay` base, - The restructured app code (base class `DashboardApplication`, `Surface`, `Overlay` base,
active-surface navigation) has only been tested off the device so far. active-surface navigation) has only been tested off the device so far.
- Several dashboard tabs from one process (works in `tst_surfaces`, not tried in SteamVR). - Several dashboard tabs from one process (works in `tst_surfaces`, not tried in SteamVR).
- Qt WebEngine in the off-screen renderer (Vulkan and OpenGL), the SteamVR keyboard in web
page input fields, and the persistent login ("HA Dashboard" tab, DSH-1).
+56 -12
View File
@@ -9,8 +9,9 @@ the headset to Home Assistant in both directions:
- **Services:** Home Assistant can call it, e.g. to show notifications in the headset. - **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. - **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 > **Status:** early development. The overlay platform works on the Frame. The Home Assistant
> parts are being planned; see [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md), > parts are being built: so far a settings file and an "HA Dashboard" tab with Home
> Assistant's web UI. See [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md),
> [docs/DESIGN.md](docs/DESIGN.md) and [docs/PROGRESS.md](docs/PROGRESS.md). > [docs/DESIGN.md](docs/DESIGN.md) and [docs/PROGRESS.md](docs/PROGRESS.md).
### Platform ### Platform
@@ -33,8 +34,8 @@ FrameAssistant grew out of a SteamVR dashboard-overlay template, which it still
## Quick start ## Quick start
Requirements: Requirements:
- CMake ≥ 3.22, Ninja, a C++20 compiler, and Qt ≥ 6.5 (Quick, QuickControls2, OpenGL; - CMake ≥ 3.22, Ninja, a C++20 compiler, and Qt ≥ 6.5 (Quick, QuickControls2, OpenGL,
Test for the tests) WebEngineQuick; Test for the tests)
- Vulkan headers (optional; without them the build is OpenGL only) - Vulkan headers (optional; without them the build is OpenGL only)
- Docker or Podman, for the Frame builds - Docker or Podman, for the Frame builds
@@ -45,12 +46,14 @@ The OpenVR SDK files are downloaded automatically when CMake configures the buil
cmake --preset native-debug && cmake --build --preset native-debug cmake --preset native-debug && cmake --build --preset native-debug
ctest --preset native-debug ctest --preset native-debug
build/native-debug/bin/frame-assistant --desktop # UI in a window, no SteamVR build/native-debug/bin/frame-assistant --desktop # UI in windows, no SteamVR
build/native-debug/bin/frame-assistant --snapshot out.png # one frame through the VR path build/native-debug/bin/frame-assistant --snapshot out.png # one frame per tab through the VR path
build/native-debug/bin/frame-assistant # as dashboard tab (SteamVR running) build/native-debug/bin/frame-assistant # as dashboard tab (SteamVR running)
build/native-debug/bin/frame-assistant --install-manifest # register + auto-start with SteamVR build/native-debug/bin/frame-assistant --install-manifest # register + auto-start with SteamVR
``` ```
Overlay and desktop mode need a settings file (below); `--snapshot` and `--benchmark` don't.
On the Steam Frame: On the Steam Frame:
```sh ```sh
@@ -60,6 +63,41 @@ scripts/deploy-to-frame.sh <user>@<frame> # copy over SSH and install (SteamVR
Details are in [docs/STEAM-FRAME.md](docs/STEAM-FRAME.md). 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 |
| `homeassistant.discovery_prefix` | no | `homeassistant` | |
| `homeassistant.url` | no | | Home Assistant web UI for the "HA Dashboard" tab |
| `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.
The "HA Dashboard" tab keeps the Home Assistant login in
`~/.local/share/frameassistant/webengine`.
## Development ## Development
- **App identity** (app key `net.matzman.frameassistant`, tab name, executable name - **App identity** (app key `net.matzman.frameassistant`, tab name, executable name
@@ -68,8 +106,10 @@ Details are in [docs/STEAM-FRAME.md](docs/STEAM-FRAME.md).
- **UI:** `src/qml/`, with `Main.qml` as the root. List new QML files under `QML_FILES` in - **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`. `vrdash_add_dashboard()` in `src/CMakeLists.txt`. Iterate with `--desktop` and `--snapshot`.
- **C++ logic:** the app class `src/Dashboard.h/.cpp` derives from `DashboardApplication`. - **C++ logic:** the app class `src/Dashboard.h/.cpp` derives from `DashboardApplication`.
It adds views in its constructor (`addSurface()`), keeps its objects as members and wires It adds views in its constructor (`addSurface()`) and its own command-line options,
them up in the `loading()`/`started()` overrides. 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/`. - **Controller buttons:** actions and default bindings are in `resources/input/`.
- **Ship it:** `scripts/package-release.sh` and `scripts/deploy-to-frame.sh`, see - **Ship it:** `scripts/package-release.sh` and `scripts/deploy-to-frame.sh`, see
[docs/STEAM-FRAME.md](docs/STEAM-FRAME.md). [docs/STEAM-FRAME.md](docs/STEAM-FRAME.md).
@@ -84,6 +124,7 @@ How the pieces fit together: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|--------|---------| |--------|---------|
| *(none)* | Run as SteamVR dashboard overlay (SteamVR must be running) | | *(none)* | Run as SteamVR dashboard overlay (SteamVR must be running) |
| `--desktop` | Show the UI in desktop windows, one per surface; no SteamVR | | `--desktop` | Show the UI in desktop windows, one per surface; no SteamVR |
| `--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) | | `--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 | | `--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 | | `--graphics-api vulkan\|opengl` | Render backend. Default: Vulkan, falling back to OpenGL if Vulkan doesn't start; an explicit choice never falls back |
@@ -107,17 +148,20 @@ VK_DRIVER=lvp scripts/container-test.sh # same on the lavapipe software driver
| Path | Content | | Path | Content |
|------|---------| |------|---------|
| `src/main.cpp`, `src/Dashboard.*`, `src/qml/` | **The app** | | `src/main.cpp`, `src/Dashboard.*`, `src/qml/` | **The app** |
| `src/config/` | Settings file (`frameassistant_lib`) |
| `src/ha/` | Home Assistant parts: `HaWebUi` (web UI tab) |
| `src/app/` | `DashboardApplication` (app base class), `Surface` (one view) | | `src/app/` | `DashboardApplication` (app base class), `Surface` (one view) |
| `src/render/` | Off-screen Qt Quick renderer; Vulkan and OpenGL backends | | `src/render/` | Off-screen Qt Quick renderer; Vulkan and OpenGL backends |
| `src/vr/` | SteamVR connection, overlays (dashboard tab) | | `src/vr/` | SteamVR connection, overlays (dashboard tab) |
| `src/input/` | Laser/scroll/keyboard → Qt events; SteamVR Input navigation | | `src/input/` | Laser/scroll/keyboard → Qt events; SteamVR Input navigation |
| `resources/` | SteamVR manifest template, input actions and bindings | | `resources/` | SteamVR manifest template, input actions and bindings, example settings |
| `tests/` | Unit and render tests | | `tests/` | Unit and render tests |
| `cmake/` | `vrdash_add_dashboard()`, OpenVR download, Qt bundling, toolchain | | `cmake/` | `vrdash_add_dashboard()`, OpenVR download, Qt bundling, toolchain |
| `docker/`, `scripts/` | Build containers; build, package, deploy and test scripts | | `docker/`, `scripts/` | Build containers; build, package, deploy and test scripts |
| `docs/` | Requirements, progress, architecture, Steam Frame notes | | `docs/` | Requirements, design, progress, architecture, Steam Frame notes |
## License ## License
MIT, see [LICENSE](LICENSE). Third-party components (OpenVR SDK, Qt; in bundled builds also MIT, see [LICENSE](LICENSE). Third-party components (OpenVR SDK, Qt incl. Qt WebEngine; in
their dependencies) are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). bundled builds also their dependencies) are listed in
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
+1
View File
@@ -7,6 +7,7 @@ This project is MIT-licensed (see `LICENSE`). `<project>` below is the CMake pro
|-----------|---------|----------------| |-----------|---------|----------------|
| [OpenVR SDK](https://github.com/ValveSoftware/openvr) (`libopenvr_api.so`, `openvr.h`) | BSD-3-Clause | Shipped in `lib/`; license in `share/doc/<project>/third-party/openvr.LICENSE` | | [OpenVR SDK](https://github.com/ValveSoftware/openvr) (`libopenvr_api.so`, `openvr.h`) | BSD-3-Clause | Shipped in `lib/`; license in `share/doc/<project>/third-party/openvr.LICENSE` |
| [Qt 6](https://www.qt.io/) (Core, Gui, Qml, Quick, Quick Controls, Wayland/XCB plugins) | LGPL-3.0 (Qt open-source license) | Linked dynamically. Default builds use the system's Qt (not shipped). Bundled builds ship Qt's shared libraries unmodified in `lib/`, `plugins/`, `qml/`: replace them to use another compatible Qt build | | [Qt 6](https://www.qt.io/) (Core, Gui, Qml, Quick, Quick Controls, Wayland/XCB plugins) | LGPL-3.0 (Qt open-source license) | Linked dynamically. Default builds use the system's Qt (not shipped). Bundled builds ship Qt's shared libraries unmodified in `lib/`, `plugins/`, `qml/`: replace them to use another compatible Qt build |
| [Qt WebEngine](https://doc.qt.io/qt-6/qtwebengine-index.html) (Quick), for the "HA Dashboard" tab | LGPL-3.0; contains Chromium (BSD-3-Clause and further licenses, listed by Qt WebEngine) | Linked dynamically, from the system's Qt; not shipped. Bundled builds don't support it yet |
**Bundled builds** (`--bundle-qt`) also ship further shared libraries that Qt depends on, **Bundled builds** (`--bundle-qt`) also ship further shared libraries that Qt depends on,
taken unmodified from Debian. Their copyright and license files are in taken unmodified from Debian. Their copyright and license files are in
+23 -10
View File
@@ -37,13 +37,19 @@ the order in which all of that is created and destroyed.
Your app derives from it: Your app derives from it:
- **Constructor:** configuration (`addSurface()`, optionally `setAppKey()`, - **Constructor:** configuration (`addSurface()`, optionally `setAppKey()`,
`setDisplayName()`, `setQuickStyle()`, `setQmlModule()`). `setDisplayName()`, `setQuickStyle()`, `setQmlModule()`). The app's own command-line
options go to the base constructor as a list of `QCommandLineOption`s; they are parsed and
listed in `--help` with the built-in ones, and read with `isOptionSet()`/`optionValue()`.
- **Hooks:** - **Hooks:**
- `prepare(QString* error)`: first, once mode and app key are known, before the QML
engine, the surfaces and SteamVR exist (not for manifest commands). Returning `false`
ends `exec()` with the error and exit code 1, e.g. for invalid settings.
- `loading()`: the shared QML engine, the surfaces and their render backends exist; QML - `loading()`: the shared QML engine, the surfaces and their render backends exist; QML
isn't loaded yet. Set up what the QML needs here. isn't loaded yet. Set up what the QML needs here.
- `started()`: everything runs. Connect to surfaces and input here. - `started()`: everything runs. Connect to surfaces and input here.
- **Accessors:** `mode()`, `appKey()`, `appName()`, `engine()`, `surfaces()`, `surface()` - **Accessors:** `mode()`, `appKey()`, `appName()`, `isOptionSet()`, `optionValue()`,
(the first), `surface(id)`, `vrRuntime()`, `navigation()`. `engine()`, `surfaces()`, `surface()` (the first), `surface(id)`, `vrRuntime()`,
`navigation()`.
- **Lifetime:** the base constructor creates the `QGuiApplication`, so your members can be - **Lifetime:** the base constructor creates the `QGuiApplication`, so your members can be
QObjects with timers. All QML and surfaces are torn down at the end of `exec()`, while your QObjects with timers. All QML and surfaces are torn down at the end of `exec()`, while your
object and its members still exist. object and its members still exist.
@@ -121,15 +127,16 @@ sequenceDiagram
## Start and shutdown (overlay mode) ## Start and shutdown (overlay mode)
1. **Start:** 1. **Start:**
1. Connect to SteamVR (`VR_Init`). 1. Call `prepare()`; stop with exit code 1 if it fails.
2. Create one render backend per surface. The first surface decides the API, including a 2. Connect to SteamVR (`VR_Init`).
3. Create one render backend per surface. The first surface decides the API, including a
fallback to OpenGL; the others use the same API, because Qt's graphics API is fallback to OpenGL; the others use the same API, because Qt's graphics API is
process-wide. process-wide.
3. Call `loading()`. 4. Call `loading()`.
4. Load each surface's QML. 5. Load each surface's QML.
5. Create the overlays. 6. Create the overlays.
6. Set up controller navigation. 7. Set up controller navigation.
7. Call `started()`, then run the event loop. 8. Call `started()`, then run the event loop.
2. **Shutdown runs in reverse**, with one rule found on the Frame: **overlays first, then 2. **Shutdown runs in reverse**, with one rule found on the Frame: **overlays first, then
SteamVR, then the graphics devices.** OpenVR requires shutting down before the devices it SteamVR, then the graphics devices.** OpenVR requires shutting down before the devices it
got textures from are destroyed. Otherwise its client library frees objects on a dead got textures from are destroyed. Otherwise its client library frees objects on a dead
@@ -160,3 +167,9 @@ For e.g. a notification app: a `WorldOverlay` (another `Overlay` subclass, creat
(`VROverlayFlags_MakeOverlaysInteractiveIfVisible`). (`VROverlayFlags_MakeOverlaysInteractiveIfVisible`).
The surface structure is ready for it: `SurfaceConfig` would gain a kind and a placement. The surface structure is ready for it: `SurfaceConfig` would gain a kind and a placement.
## FrameAssistant on top
The app itself (`src/Dashboard.*`, `src/config/`, `src/ha/`, `src/qml/`) builds on this
platform; its design (Home Assistant connection, entities, overlays, threads) is in
[DESIGN.md](DESIGN.md).
+6 -4
View File
@@ -150,7 +150,7 @@ and signals across threads are delivered through the receiver's event loop.
| **I/O wrappers** | Data sources and actuators; know nothing about HA. One wrapper object per OpenVR interface used (e.g. `IVRSystem`, `IVRApplications`, `IVRChaperone`, `IVRSettings`). System wrappers (network, audio) later. | OpenVR ✓, system ✗ | | **I/O wrappers** | Data sources and actuators; know nothing about HA. One wrapper object per OpenVR interface used (e.g. `IVRSystem`, `IVRApplications`, `IVRChaperone`, `IVRSettings`). System wrappers (network, audio) later. | OpenVR ✓, system ✗ |
| **Overlays** | View and controller of MVC (QML), as dashboard tabs or world overlays. Get their data (the model) from other objects. | ✓ | | **Overlays** | View and controller of MVC (QML), as dashboard tabs or world overlays. Get their data (the model) from other objects. | ✓ |
| **Managers** | One object per stateful feature that manages and coordinates it, using entities, services and overlays. Notification manager (MVP); status card manager (SRV-6/7) later. | notifications ✓ | | **Managers** | One object per stateful feature that manages and coordinates it, using entities, services and overlays. Notification manager (MVP); status card manager (SRV-6/7) later. | notifications ✓ |
| **HA web UI** | Encapsulates Qt WebEngine for the dashboard tab (DSH-1; Q2 for DSH-2). | ✓ (DSH-1) | | **HA web UI** | Encapsulates Qt WebEngine for the dashboard tab (DSH-1; Q2 for DSH-2). `src/ha/HaWebUi`, a QML singleton in the app itself (not `frameassistant_lib`: QML types must be part of the QML module). | ✓ (DSH-1) |
| **Configuration** | Settings from the JSON file, loaded once by the app as an immutable value. Each component gets the part it needs through its constructor (D19). | ✓ | | **Configuration** | Settings from the JSON file, loaded once by the app as an immutable value. Each component gets the part it needs through its constructor (D19). | ✓ |
| **App** | The managing object: owns all of the above as components and wires them together. | ✓ | | **App** | The managing object: owns all of the above as components and wires them together. | ✓ |
@@ -314,10 +314,12 @@ Layout follows common practice (e.g. Zigbee2MQTT's `…/<device>/set`); one topi
code ≠ 0 (CFG-2, via the start-up hook D21). code ≠ 0 (CFG-2, via the start-up hook D21).
- Unknown keys: a warning in the log, no abort (catches typos without breaking across - Unknown keys: a warning in the log, no abort (catches typos without breaking across
versions). versions).
- Password in plain text: warn if the file is readable by others; otherwise file permissions - Password in plain text: warn if the file holds a password and is readable by others;
(`chmod 600`). otherwise file permissions (`chmod 600`). (Implemented this way: a readable file without a
password is harmless.)
- Throttle intervals (SEN-14) are fixed per entity in the code for the MVP. - Throttle intervals (SEN-14) are fixed per entity in the code for the MVP.
- A `config.example.json` ships in the package. - A `config.example.json` ships in the package (`share/frameassistant/`); the package's
README.txt explains how to set it up.
- To check on the Frame when TLS is implemented: where the system certificates are - To check on the Frame when TLS is implemented: where the system certificates are
(`/etc/ssl/certs`?), for the `ca_file` default. (`/etc/ssl/certs`?), for the `ca_file` default.
+23 -3
View File
@@ -5,9 +5,12 @@ CLAUDE.md ("Working rules").
## Current state ## Current state
- **Implementation iteration 1 (DSH-1 and configuration), phase 3 (implementation).** - **Implementation iteration 1 (DSH-1 and configuration), phase 3 (implementation) done,
Requirements (D13) and design ([DESIGN.md](DESIGN.md)) are approved; the plan below was waiting for the user's approval.** All plan steps are implemented and committed; next is
approved on 2026-10-05. phase 4, testing on the Frame (plan item 10).
- **Open point found:** started by SteamVR, the app writes no log file (the manifest passes no
`--log-file`, stderr goes to SteamVR). CFG-2 wants the settings error "in the log file and
on stderr". To be decided.
## Next steps ## Next steps
@@ -77,6 +80,23 @@ Known limitation: the bundled-Qt package does not support WebEngine in this iter
present (relevant for Q2/Q3). present (relevant for Q2/Q3).
- libmosquitto ABI check: Debian's 2.0.21 needs at most `OPENSSL_3.0.0`/`GLIBC_2.34`; the - libmosquitto ABI check: Debian's 2.0.21 needs at most `OPENSSL_3.0.0`/`GLIBC_2.34`; the
Frame has OpenSSL 3.2.1 and glibc 2.39. No libcjson dependency. D14 holds. Frame has OpenSSL 3.2.1 and glibc 2.39. No libcjson dependency. D14 holds.
- Phase 3 of implementation iteration 1:
- Platform: `prepare()` hook and app-specific command-line options, tested in
`tst_surfaces` (incl. `surfaces_prepare_fail`).
- Settings file (`src/config/`, new library `frameassistant_lib`), `--config`; `tst_config`
(37 cases). `config.example.json` in the package; README.txt explains the set-up.
- "HA Dashboard" tab: `WebEngineView`, `HaWebUi` with a persistent profile, WebEngine
initialisation in `main()`. Host: desktop mode loads a local test page, profile data
lands in `~/.local/share/frameassistant/webengine`; snapshot renders both tabs.
- Build containers with Qt WebEngine. Cross build: the aarch64 binary needs no private Qt
symbols, at most `Qt_6.8` and `GLIBC_2.34`. Container tests 8/8 (incl. Vulkan with
validation layers, no messages).
- Snags: moc misses classes in files with raw strings containing `//` (CLAUDE.md);
generated QML type registration includes headers by file name; Qt ≥ 6.9 hints at
`WebEngineProfilePrototype` (not in the Frame's 6.8). Deviations: `HaWebUi` lives in the
app, not the library; the password warning needs a password in the file.
- Not verified yet: WebEngine with a URL in the off-screen pipeline (Frame, phase 4); the
bundled package (not supported for WebEngine in this iteration).
- Plan for implementation iteration 1 approved; phase 3 started. - Plan for implementation iteration 1 approved; phase 3 started.
- Plan for implementation iteration 1 written; new tab "HA Dashboard" (D38, D39). - Plan for implementation iteration 1 written; new tab "HA Dashboard" (D38, D39).
- MVP implemented in four iterations; DSH-1 first, at the user's wish (D37). - MVP implemented in four iterations; DSH-1 first, at the user's wish (D37).
+4
View File
@@ -20,3 +20,7 @@ endforeach()
install(FILES ${_share}/manifest.vrmanifest DESTINATION ${VRDASH_SHARE_SUBDIR}) install(FILES ${_share}/manifest.vrmanifest DESTINATION ${VRDASH_SHARE_SUBDIR})
install(FILES ${_input_files} DESTINATION ${VRDASH_SHARE_SUBDIR}/input) install(FILES ${_input_files} DESTINATION ${VRDASH_SHARE_SUBDIR}/input)
# Example settings file (copy to ~/.config/frameassistant/config.json and edit).
configure_file(config.example.json ${_share}/config.example.json COPYONLY)
install(FILES config.example.json DESTINATION ${VRDASH_SHARE_SUBDIR})
+20
View File
@@ -0,0 +1,20 @@
{
"device": {
"name": "Steam Frame"
},
"mqtt": {
"host": "homeassistant.local",
"port": 1883,
"username": "frameassistant",
"password": "change-me",
"tls": {
"enabled": false
}
},
"homeassistant": {
"url": "http://homeassistant.local:8123"
},
"notifications": {
"timeout_s": 10
}
}
+8
View File
@@ -81,6 +81,14 @@ chmod +x "$out/install.sh" "$out/uninstall.sh"
echo "SteamOS update, use the -bundled package instead." echo "SteamOS update, use the -bundled package instead."
fi fi
echo echo
echo "Settings (required before the first start):"
echo " mkdir -p ~/.config/$project"
echo " cp share/$project/config.example.json ~/.config/$project/config.json"
echo " chmod 600 ~/.config/$project/config.json"
echo " then edit it (device name, MQTT broker, Home Assistant URL)."
echo " Without valid settings the app exits; to see why, run"
echo " ./bin/$exe --desktop in a terminal."
echo
echo "Install / update: ./install.sh (SteamVR must be running)" echo "Install / update: ./install.sh (SteamVR must be running)"
echo " SteamVR remembers this folder's location: after moving it," echo " SteamVR remembers this folder's location: after moving it,"
echo " run ./install.sh again." echo " run ./install.sh again."