diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 9dbc56d..299309d 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -26,6 +26,29 @@ Three layers; dependencies only go downward (D17). - The overlay platform (`vrdashboard_core`) spans two layers: overlays, rendering and input are presentation infrastructure; SteamVR access for sensors and actions is I/O. +## Start-up and modes + +- **Start-up hook (D21):** the platform (`DashboardApplication` in `vrdashboard_core`) gets a + third hook, e.g. `virtual bool prepare(QString* error)`. `exec()` calls it once mode and app + key are known, before the QML engine, the surfaces and `VR_Init`. On `false`, `exec()` ends + with exit code 1 and reports the error through the existing path (log and stderr). The + default returns `true`, so other apps built on the template don't change. FrameAssistant + loads its configuration there and decides from `mode()` whether it needs it (CFG-2). +- **Modes (D22):** + + | Mode | Configuration + HA connection | SteamVR | + |---|---|---| + | overlay (default) | yes | required (overlay app) | + | `--desktop` | yes | connects as background app if SteamVR runs; otherwise VR entities are unavailable (SEN-15) | + | `--desktop-novr` | yes | never | + | `--snapshot`, `--benchmark` | no | no | + + - `--desktop` on the Frame over RDP shows the live UI with real data without wearing the + headset. Stop the SteamVR-started instance by hand first: two instances with the same + device name would knock each other off the broker. + - `--config ` loads another configuration file instead of + `~/.config/frameassistant/config.json`, e.g. on a PC with its own device name (CON-5). + ## Object classes The app is HA-centric, so its building blocks follow HA's concepts (D18). Objects talk to each @@ -34,8 +57,8 @@ and signals across threads are delivered through the receiver's event loop. | Class | Role | MVP | |---|---|---| -| **HA entity** | One HA entity, both directions. Entity → HA: state and JSON attributes. HA → entity: basic commands. Observes value signals of other objects and publishes them; turns MQTT commands into signals other objects act on. Covers sensors, simple actions (`button`), settings (`switch`, `number`), feedback (`event`). | ✓ | -| **HA service** | An action HA calls with parameters: a JSON command on an MQTT command topic (HA side: the standard `mqtt.publish` action). May be more complex than an entity command. Results go back asynchronously, e.g. through an `event` entity. How the user wraps it in HA (scripts, automations, pyscript) is their choice; FrameAssistant only requires stock HA with MQTT (NFR-6). | ✓ (notification) | +| **HA entity** | One HA entity, both directions. Entity → HA: state and JSON attributes. HA → entity: commands; HA's MQTT integration turns its own services into them (e.g. `button.press`, `switch.turn_on`, `number.set_value`, `notify.send_message`). Observes value signals of other objects and publishes them; turns MQTT commands into signals other objects act on. **Native entity types are preferred** wherever their meaning fits (D23). | ✓ | +| **HA service** | **Fallback** where no entity type fits (D23): a JSON command on an own MQTT command topic (HA side: the standard `mqtt.publish` action), e.g. rich notifications or vibration with parameters. Results go back asynchronously, e.g. through an `event` entity. How the user wraps it in HA (scripts, automations, pyscript) is their choice; FrameAssistant only requires stock HA with MQTT (NFR-6). | depends on the NOT-1 choice | | **HA device** | Owns the MQTT connection, discovery and availability, and owns all entity and service objects. One per headset (CON-5). | ✓ | | **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. | ✓ | @@ -44,6 +67,32 @@ and signals across threads are delivered through the receiver's event loop. | **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. | ✓ | +### Entities and services (D23) + +- **One class per HA entity type** on a common base. The base does the discovery part, topics, + availability and throttling, so SEN-14, SEN-15 and NFR-7 are handled in one place. +- A concrete entity is an **instance with a description** (id, name, device class, unit, + throttle interval), not a class of its own. +- Our actions map to native types where the meaning fits: + + | Action | Type | HA service | + |---|---|---| + | Sensors (SEN-*) | `sensor`, `binary_sensor` | — | + | Notification (NOT-1) | `notify` | `notify.send_message` | + | Screenshot, standby, quit app, show boundary (SRV-1/2/3) | `button` | `button.press` | + | Do-not-disturb (SRV-5) | `switch` | `switch.turn_on/off` | + | Volume (SRV-3) | `number` | `number.set_value` | + | Launch app by key (SRV-2) | `text` | `text.set_value` | + | Attention signal with duration/volume (SRV-1), to check | `siren` | `siren.turn_on` | + | Feedback (NOT-5, NOT-10) | `event` | — | + +- **Wiring:** the app wires simple entities (I/O wrapper signal ↔ entity). Managers get their + entities and services from the HA device and wire them internally. The HA device owns them + all (D18). +- **MQTT `notify` drops the title:** HA's `async_send_message()` renders only `message` into + the payload (`homeassistant/components/mqtt/notify.py`). NOT-1 needs title and text; options + (MVP detail): first line is the title, JSON in the message, or an own service. + Notes for later: - OpenVR delivers all events through one queue; one dispatcher (today `VrRuntime`) hands them to the wrappers. @@ -61,3 +110,6 @@ Notes for later: | D17 | 2026-10-05 | **Three layers:** presentation (QML), business logic, I/O. The overlay platform spans presentation (overlays, rendering, input) and I/O (SteamVR access) | Simple and enough for the start; Qt Quick already enforces separating presentation from logic | | D18 | 2026-10-05 | **HA-centric object classes:** HA entity, HA service, HA device, I/O wrappers, overlays, managers, HA web UI, configuration, app; communication through signals and slots | The app is HA-centric; signals/slots decouple objects and work across threads | | D19 | 2026-10-05 | **Configuration is injected:** the app loads it once as an immutable value and passes each component the part it needs; no singleton | Explicit dependencies, trivial tests with own configs, thread-safe; the app creates all components anyway | +| D21 | 2026-10-05 | Start-up hook in the platform that may fail (`prepare()`), before the engine, surfaces and `VR_Init`; failure → exit code 1 | CFG-2 needs a clean abort; `loading()`/`started()` can't fail; backward compatible | +| D22 | 2026-10-05 | Overlay and both desktop modes load the configuration and connect to HA; `--desktop` also connects to SteamVR as a background app if it runs; `--desktop-novr` never does; `--snapshot`/`--benchmark` stay without configuration; new option `--config ` | Live development over RDP on the Frame with real data; PC development without SteamVR under its own device name | +| D23 | 2026-10-05 | **One class per HA entity type** on a common base; concrete entities are described instances. Native entity types (and the HA services the MQTT integration provides for them) are preferred; an own JSON command service only as fallback | Mirrors HA's model; discovery, availability and throttling in one place; actions are visible and selectable in HA without `mqtt.publish` | diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index cd2de16..99b5867 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -15,8 +15,8 @@ questions, in this order: 1. ~~Process model~~: one process, several threads (D16). 2. ~~Layers~~: presentation, business logic, I/O (D17). -3. How features plug in: object classes (D18) and configuration injection (D19) settled; - open: how the classes are wired. +3. ~~How features plug in~~: object classes (D18), configuration injection (D19), one class + per HA entity type with native types preferred (D23). 4. HA channels: MQTT only, or prepare for a second one for the dashboard (Q2/Q3)? 5. Overlay kinds: dashboard tabs and world overlays (platform extension). 6. Runtime model: which work runs in which thread (D16: several threads). @@ -40,6 +40,12 @@ configuration, packaging of libmosquitto. present (relevant for Q2/Q3). - 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. +- Entities: one class per HA entity type, native types preferred, own service only as fallback + (D23). Found: MQTT `notify` drops the title. +- Start-up hook `prepare()` in the platform (D21); modes, `--desktop-novr` and `--config` + (D22). +- New requirement UI-1: status display in the main tab (D20). +- The user installed `mosquitto` on the dev host, so native builds can use libmosquitto. - Started docs/DESIGN.md: one process (D16), three layers (D17), HA-centric object classes (D18). HA side: FrameAssistant needs only stock HA; how the user wraps commands (scripts, pyscript) is up to them (NFR-6 unchanged). diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 6c68905..442b344 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -37,6 +37,12 @@ FrameAssistant. | CON-4 | When the broker is unreachable, reconnect with backoff; after reconnecting, republish discovery and current state. Nothing is buffered. | M | ✓ | Broker restart → entities return to correct states without restarting the app. | | CON-5 | Several headsets work side by side with one HA, each as its own device, identified by its configured device name. | M | ✓ | Two devices with different names → two HA devices, no shared entities. | +### Main tab (UI) + +| ID | Requirement | Prio | MVP | Acceptance criteria | +|----|-------------|------|-----|---------------------| +| UI-1 | The main dashboard tab shows the connection status: device name, broker, connected or not, last error. | M | ✓ | Broker stopped → tab shows "not connected" and the error; broker back → "connected". | + ### Configuration (CFG) | ID | Requirement | Prio | MVP | Acceptance criteria | @@ -154,6 +160,7 @@ FrameAssistant. | D13 | 2026-10-05 | Requirements approved as drafted, priorities included (MVP = Must, other wanted features = Should, SRV-7 and NOT-10 = Could) | End of phase 1 | | D14 | 2026-10-05 | MQTT client: **libmosquitto** from Debian's package in the build container, shipped in the release package like `libopenvr_api.so` | Not on the Frame; Qt MQTT would need the exact Qt version (Appendix B) | | D15 | 2026-10-05 | HA MQTT discovery: **own small layer on Qt's JSON classes**, using device-based discovery (one `/device//config` topic with all entities under `cmps`) | No suitable library (Appendix B); HA recommends device-based discovery for multi-entity devices (NFR-7) | +| D20 | 2026-10-05 | New requirement UI-1 (status display in the main tab), MVP | Helps testing on the device; little effort | Design decisions (D16 onward) are in [DESIGN.md](DESIGN.md).