Files
FrameAssistant/docs/DESIGN.md
T
matzmanandClaude Opus 5.5 3ec3029af6 Design: start-up hook, modes, entity types; add UI-1
D20: requirement UI-1 (status in the main tab). D21: failing start-up
hook prepare() in the platform. D22: modes incl. --desktop with optional
SteamVR, --desktop-novr, --config. D23: one class per HA entity type,
native types preferred, own JSON service only as fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:05:33 +02:00

8.3 KiB
Raw Blame History

Design

Status: in progress, iteration 1, phase 2 (planning and design). High-level architecture first, then the details for the MVP. Requirements and their decisions (D1–D15): REQUIREMENTS.md. Design decisions start at D16.

Process and threads

  • One process started by SteamVR does everything: HA connection, sensors, services, all overlays (D16).
  • Several threads inside it for concurrent work. Which work runs in which thread is still open.

Layers

Three layers; dependencies only go downward (D17).

  Presentation      QML (view and controller)
       ▼
  Business logic    entities, services, managers
       ▼
  I/O               HA connection (MQTT), SteamVR, system (network, audio)
  • Qt Quick already enforces separating presentation from logic.
  • 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 <path> 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 other through Qt signals and slots (Qt's observer pattern). Senders don't know their receivers, 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: 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. ✓
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 (Q2). ✗
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.
  • OpenVR is not documented as thread-safe (relevant for the thread decision).

Open

  1. High-level points 3–7 (see PROGRESS.md, "Next steps").

Decisions

# Date Decision Reason
D16 2026-10-05 One process started by SteamVR does everything; several threads inside it for concurrent work Meets NFR-2 without extra work, no IPC; the platform already supports several surfaces per process
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 <path> 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