Files
audi-app/companion-app
Paul Nothaft 0103508cea Phase 6: Datenanbindung, Ersteinrichtung und Offline-Anzeige
Die App spricht jetzt echt mit Home Assistant. Ein Datenkontext buendelt die
vier Datentoepfe, haengt sich an den WebSocket-Ereignisstrom und reicht
Verbindungszustand und Warteschlange nach aussen; die Screens kennen weder
REST noch WebSocket. Ersteinrichtung prueft die Zugangsdaten, bevor sie sie
speichert, und unterscheidet in der Fehlermeldung zwischen abgelehntem Token
und nicht erreichbarem Server - zwei Faelle, die voellig verschiedene
Reaktionen verlangen.

Zwei echte Fehler, die erst der Test gegen die laufende Instanz zutage
brachte:

1. Die Typen der Datenschicht deklarierten tank_prozent, sicher_abgestellt
   und sicherheit. Das Backend schreibt tankprozent, gesichert und
   sicherheitscheck - die Oberflaeche haette still ueberall undefined
   gelesen, ohne dass irgendetwas fehlgeschlagen waere.
2. Der Profil-Adapter reichte lebende Verweise ins Rohprofil durch. Ein
   Formular haette damit den Rohstand mitveraendert und die Zusage gebrochen,
   den vom Backend fortgeschriebenen Reifen-Kilometerstand nie zu
   ueberschreiben. Abschnitte werden jetzt kopiert.

Ausserdem zwei Parameter-Eigenschaften in der Datenschicht aufgeloest, weil
Node sie im Strip-Modus nicht uebersetzt und genau darueber die Rauchtests
laufen.

Belegt durch neun Pruefungen gegen die laufende Instanz (Lesen, Dienstaufruf,
vollstaendiger Warteschlangenumlauf, WebSocket-Anmeldung) und 21 Unit-Tests.
2026-08-11 11:02:44 +02:00
..

DataMetric360 — companion app

Private vehicle app for one car. Ships three ways from one codebase: native iOS and Android via Capacitor, and embedded as a plain iframe in the Home Assistant dashboard. Architecture and the decisions behind it: ../COMPANION_APP_ARCHITECTURE.md.

Status

Data layer only. No UI yet — the screens come from the Claude Design draft (../DESIGN_BRIEF_DATAMETRIC360.md), and get implemented on top of design-system/'s React components once that draft settles. This package was written first on purpose: how the app talks to Home Assistant doesn't depend on what the screens look like, so it survives every design iteration untouched.

Not yet added (deliberately, they'd be guesses today): React/Vite, Capacitor, the native secure-storage adapter, and the Audi brand assets (fonts/rings/badges — those come from homeassistant/www/ at implementation time, never into design-system/, see the licence note in the architecture doc).

Layout

src/api/
├── types.ts           HA state shapes + domain types (Fahrt, Tankvorgang, …) + the entity-ID table
├── umgebung.ts        runtime detection (capacitor/iframe/browser), credential storage, URL helpers
├── rest.ts            REST client — replaces hass.states / hass.callService
├── live.ts            WebSocket client — push updates, auto-reconnect with backoff
├── warteschlange.ts   offline queue for writes made without a connection
└── index.ts           DataMetricApi — ties the three together, exposes the domain operations
scripts/smoke.ts       verification against a running HA instance

Comments are in German, matching the rest of the project.

What replaces what

The old panel got a hass object injected by panel_custom. That object only exists inside the HA frontend, which is exactly why the old panel can't run as a standalone app. The mapping:

Old panel Here
hass.states[id].attributes.daten rest.datenLesen(id) / DataMetricApi.profilLesen() etc.
hass.callService(...) warteschlange.einreihen(...) via the DataMetricApi methods
automatic re-render on state push live.aufZustand(...)

Same entities, same pyscript services, same backend files written — only the transport changes.

Every write goes through the queue rather than straight to REST. That's what makes offline edits behave the same as online ones, just delayed: a receipt photographed in a dead zone is persisted and sent when the connection returns, surviving an app restart in between.

Commands

Node is installed at C:\Program Files\nodejs but is not on PATH — prefix it:

$env:Path = "C:\Program Files\nodejs;" + $env:Path
npm run typecheck
npm run smoke

smoke targets http://localhost:18123 (the audi_ha_test Docker container) by default; pass a different base URL as the first argument.

Verification status (2026-08-10)

tsc --noEmit clean under strict plus noUncheckedIndexedAccess and exactOptionalPropertyTypes. All 7 smoke checks pass against the running test container, covering URL normalisation, the http→ws/https→wss mapping, that GET /api/ exists and rejects an unauthenticated request with 401, and that the WebSocket opens with auth_required — which is the message live.ts's whole auth flow is built around.

Not yet verified — needs a token: authenticated reads, service calls, and the queue's real round-trip. Those need a Long-Lived Access Token, which is a deliberate manual step (see the LLAT provisioning decision in the architecture doc). To do it: create a token in the HA profile page, then extend scripts/smoke.ts with authenticated cases.