e1d570992e
Drei zusammengehörige Teile in einem Repository: - homeassistant/ Das fertige, im Einsatz befindliche Home-Assistant-Panel (panel_custom Custom Element + pyscript-Backend). Echte Fahrzeug- und Personendaten (fahrzeugprofil.json, fahrten.jsonl, tankvorgaenge.jsonl, Tankbelege) bleiben per .gitignore außen vor; die anonymisierte Vorlage fahrzeugprofil.example.json ist mit dabei. - design-system/ Eigenständige React-Komponentenbibliothek (@audi-dash/ui), die die visuelle Sprache des Panels nachbildet - ohne Audi-Markenzeichen und ohne die lizenzierte Hausschrift. Dient als Grundlage für Claude Design. War bis hierher ein eigenes Repository und ist in dieses eingeschmolzen worden. - companion-app/ Datenschicht der neuen App DataMetric360 (iOS/Android via Capacitor, zusätzlich als Iframe im HA-Dashboard). Noch ohne Oberfläche: REST- und WebSocket-Zugriff auf Home Assistant plus Warteschlange für Änderungen ohne Netz. Ersetzt das eingespritzte hass-Objekt, das nur innerhalb des HA-Frontends existiert. Dazu die Projektdokumentation: SPECIFICATION.md (Ist-Stand des Panels), COMPANION_APP_ARCHITECTURE.md (Architekturentscheidungen der neuen App), AUDIT_2026-08-10.md, DESIGN_BRIEF_DATAMETRIC360.md und der ursprüngliche Bauauftrag. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
265 lines
18 KiB
Markdown
265 lines
18 KiB
Markdown
# DataMetric360 — Companion App Architecture (design decided, not yet built)
|
|
|
|
**App name: `DataMetric360`** (decided 2026-08-10). Public hostname: a subdomain of
|
|
**`datametric360.app`**,
|
|
a dedicated domain registered at all-inkl solely for this purpose (see §5.4 for why it must be a
|
|
separate domain). Both the app name and the domain are deliberately neutral — they don't advertise
|
|
"Audi", "car", or "GPS tracker" to anyone who sees the hostname or the app icon, which is a small but
|
|
free privacy win for a hostname that will be publicly resolvable.
|
|
|
|
|
|
**Status:** architecture decisions locked in during design discussion on 2026-08-10. **No code for this
|
|
exists yet** — this is a separate, not-yet-started project sitting alongside the finished HA panel
|
|
described in `SPECIFICATION.md`. Implementation is explicitly deferred until the FMM003 hardware is
|
|
installed and wired (owner's own framing: "Inbetriebnahme findet separat statt, wenn die HW vorrätig
|
|
ist"). This file exists so the decisions already made aren't lost before that point.
|
|
|
|
**Relationship to the rest of the repo:**
|
|
- `homeassistant/` — the finished, in-use HA panel (`SPECIFICATION.md` is authoritative for it). Its
|
|
data source today is the HACS integration `TommiG1/HA_VAG-EU-Data-Act` plus the iPhone Companion App's
|
|
WLAN sensor for trip detection.
|
|
- `design-system/` — the React component library (no Audi brand assets) built to feed Claude Design.
|
|
Becomes directly useful here: it's the intended shared UI foundation for the app described below.
|
|
- **This file** — describes a new, third piece: a companion app that will eventually supersede the HA
|
|
panel's role, once built.
|
|
|
|
---
|
|
|
|
## 1. Concept
|
|
|
|
One responsive web app, built with the `design-system/` component library, shipped three ways:
|
|
|
|
1. **Native iOS app** — via Capacitor.
|
|
2. **Native Android app** — via Capacitor.
|
|
3. **Embedded in Home Assistant** — as a plain `panel_iframe` (not `panel_custom`/`hass` injection),
|
|
so the same build just gets more screen space and reflows via CSS breakpoints instead of floating a
|
|
fixed phone-card layout in the middle of a bigger screen.
|
|
|
|
Once this exists, it **supersedes** `homeassistant/www/audi-dashboard-app.js` — the current
|
|
panel_custom implementation is not meant to be maintained forever in parallel; it gets replaced, not
|
|
duplicated. That's an explicit, confirmed decision, not an assumption. **`audi-dashboard-app.js` gets
|
|
archived, not deleted**, once superseded — same safety-net pattern already used in this project
|
|
(`.bak-before-audit-merge` files).
|
|
|
|
**Distribution: sideload only, no App Store / Play Store.** Confirmed decision. This matters beyond
|
|
convenience: the Audi Type font, the four-rings SVG, and the model-badge SVGs are licensed only for
|
|
this one private, non-published installation (`SPECIFICATION.md` §2, `bauauftrag.md` §8/§12) — exactly
|
|
why `design-system/` strips all of that out, since it gets uploaded to a third party (Claude Design).
|
|
A sideloaded app installed directly onto the owner's own two phones, never published to a store, stays
|
|
inside that same private-installation scope — so **the companion app can use the real Audi assets**,
|
|
matching the "same look and feel" decision already made, no generic-fallback treatment needed.
|
|
|
|
**Frontend head start via `design-system/` + Claude Design:** the component-library sync gave Claude
|
|
Design the real building blocks (tokens, ~19 components) to design with — it has **not** yet designed
|
|
this app's actual screens (Home, Trips, Fuel, Settings, reworked around the REST/WebSocket data model
|
|
instead of `hass`). That's a future step, following the same pattern as the original Design-Audit task
|
|
this session started with: design in Claude Design using the real components, then implement the result
|
|
into actual Capacitor/React code.
|
|
|
|
The iPhone stops being a data source entirely — it becomes purely a display device for this app, same
|
|
as Android. All vehicle data comes from the FMM003 (see §2), not from the iPhone Companion App.
|
|
|
|
---
|
|
|
|
## 2. Data source: FMM003 replaces the iPhone + VAG integration
|
|
|
|
**Confirmed, explicit decision** (retiring two existing data sources):
|
|
|
|
- The iPhone Companion App's WLAN sensor (`sensor.iphone_wifi_connection`), used today for trip
|
|
start/end detection, is **fully retired**. No fallback to it.
|
|
- The HACS integration `TommiG1/HA_VAG-EU-Data-Act` is **fully retired** as a data source.
|
|
- **All vehicle data comes from the FMM003** (fully wired to the vehicle's CAN/FMS bus), via Codec8,
|
|
into Home Assistant as one combined, flexibly-mapped entity (no hardcoded field list — see the
|
|
no-hardcoding mapping system discussed separately, not yet written to a file).
|
|
|
|
**Decided (2026-08-10, after web research — see rationale below): Traccar, minimal variant.**
|
|
|
|
**Research question:** is there something more efficient than Traccar for decoding Codec8 from an
|
|
FMM003? Checked and ruled out:
|
|
- HA's official "Teltonika" integration — for Teltonika *routers* (RUT series: signal/modem/network
|
|
monitoring), unrelated to GPS/AVL trackers. Not applicable.
|
|
- A Teltonika-RMS-based HA integration exists (`derliebemarcus/teltonika_rms`) but connects through
|
|
Teltonika's own cloud (RMS) — same objection as the earlier Flespi rejection: vehicle/GPS data would
|
|
transit an external vendor cloud, which is exactly what this whole redesign was meant to avoid.
|
|
- OpenRemote (an alternative fleet platform some Teltonika docs point to) — a full IoT asset-management
|
|
platform with a rule engine and dashboards; heavier than Traccar, not lighter.
|
|
- Minimal standalone Codec8 parser libraries (e.g. `teltonika-go`, small Python GPS-server scripts) —
|
|
genuinely lighter in principle (no bundled platform/Tomcat), but sacrifice exactly what makes Traccar
|
|
the safer choice: even Traccar itself, mature and community-maintained for years, still has open
|
|
issues about firmware-specific Codec8 decoding quirks per device generation. A standalone parser
|
|
would hit the same class of edge cases with none of that accumulated fixing behind it.
|
|
|
|
**Conclusion: no better alternative found — Traccar stays**, but in a genuinely minimal shape:
|
|
|
|
- **Database: H2 (embedded, file-based), not MariaDB.** This was a wrong assumption in the first draft
|
|
of this document — Traccar's own upstream default database is H2, and the HA Community Add-on
|
|
(`hassio-addons/addon-traccar`) ships with H2 pre-configured (`jdbc:h2:/data/traccar`) out of the box.
|
|
MariaDB is only the add-on docs' recommendation for larger/multi-user production deployments, not a
|
|
hard requirement. For one vehicle, one device, one private user, H2 is the right call — **no MariaDB
|
|
add-on needed at all**, one fewer service to run and back up.
|
|
- Known trade-off (from community reports): occasional H2 corruption/data loss across Traccar version
|
|
upgrades. Mitigation: extend the existing daily `backup.py` routine to also copy Traccar's `.mv.db`
|
|
file alongside the other data files — same backup pattern already in place, no new mechanism.
|
|
- **Disable geofencing and reports/notifications** in Traccar's config — none of that applies to a
|
|
single always-known vehicle; keeps the running feature surface to just device ingestion + Position
|
|
Forwarding, which is all this project actually uses.
|
|
- **Position Forwarding → HA webhook** (as already planned) remains the integration path for CAN/FMS
|
|
attributes (fuel, RPM, etc.), since HA's own official `traccar_server` integration is
|
|
device_tracker/geofence-focused and wouldn't surface those extra AVL fields on its own.
|
|
|
|
Hardware confirmed sufficient for this: Dell OptiPlex 3000 TC, Pentium N6005, 16 GB DDR4, 256 GB NVMe —
|
|
comfortably enough for Traccar alone, more so without a MariaDB add-on alongside it.
|
|
|
|
### Trip detection (substitutes the old WLAN-based logic)
|
|
|
|
- **Start / end trigger:** FMM003 ignition state (CAN), not WLAN connectivity.
|
|
- **Pause-tolerance behavior carries over unchanged from the existing design** — this was iterated
|
|
three times during design and is settled:
|
|
- Ignition never drops during engine start-stop (confirmed technical fact), so no separate hold-time
|
|
is needed to filter start-stop out — the `ignition: 1→0` condition already excludes it.
|
|
- The existing user-configurable pause-tolerance setting (`fahrten_pausenzeit_min`, today's default
|
|
15 min, editable in Settings) is a real, intentional product feature and **must be preserved**: if
|
|
the vehicle restarts within that window after a genuine ignition-off, it's one trip, not two.
|
|
- Implementation pattern (unchanged from the current `fahrterkennung.py` approach, just re-triggered
|
|
off the FMM003 ignition entity instead of the WLAN sensor): `task.unique()` cancels a pending
|
|
`task.sleep()` if ignition comes back on within the pause window; the trip's recorded end
|
|
timestamp/odometer is backdated to the actual ignition-off moment, not the timer-expiry moment.
|
|
- `sensor.iphone_wifi_connection` drops out of `pyscript/modules/einstellungen.py` entirely once this
|
|
ships.
|
|
|
|
---
|
|
|
|
## 3. Frontend data access: HA REST/WebSocket API, not `hass` injection
|
|
|
|
The new app is not a `panel_custom` element, so it has no injected `hass` object. Instead:
|
|
|
|
- **Auth:** a Home Assistant Long-Lived Access Token (LLAT), generated once per install in the HA user
|
|
profile. Sufficient for a private single-user install — no need for the full OAuth flow the official
|
|
Companion App uses.
|
|
- **Reads:** `GET /api/states/<entity_id>` per entity, or subscribe over HA's **WebSocket API** for
|
|
push updates instead of polling.
|
|
- **Writes:** `POST /api/services/<domain>/<service>` — calling the **same** `pyscript.audi_dashboard_*`
|
|
services the current panel already calls via `hass.callService()` (e.g.
|
|
`audi_dashboard_profil_schreiben`, `audi_dashboard_beleg_hochladen`). Same backend function, same
|
|
file written server-side — a receipt upload or a FIN/plate edit from the phone is a real round-trip
|
|
to the shared dataset, never a local-only change. The phone only caches for offline viewing and holds
|
|
the token; it is never the source of truth.
|
|
- This same REST/WebSocket client is what makes the iframe-in-HA case (§1.3) work identically to the
|
|
native apps — one data-access layer for all three targets, no `hass`-injection special-casing needed
|
|
for the iframe case.
|
|
|
|
### LLAT provisioning (decided)
|
|
|
|
**Primary: manual paste into a one-time setup screen**, saved to secure on-device storage (iOS Keychain
|
|
/ Android Keystore, via a Capacitor secure-storage plugin). Always available, no permissions, no extra
|
|
infrastructure — and the guaranteed fallback if anything about the QR path misbehaves.
|
|
|
|
**Secondary (preferred by the owner, build if it stays simple): QR code scan.** The one real objection
|
|
to QR — that generating it would mean pasting a long-lived token into some third-party online QR
|
|
generator — is avoidable: generate the QR **locally**, in a small self-hosted page under HA's
|
|
`/local/`, using an offline JS QR library, rendering entirely client-side with no network call. The
|
|
token then never leaves the local network on its way into the QR. On the app side this needs a
|
|
Capacitor barcode/camera plugin and the camera permission. Both halves are small and well-trodden;
|
|
if the implementation turns out to be more than a modest amount of work, ship manual paste alone —
|
|
it's fully sufficient on its own.
|
|
|
|
A URL/deep-link bootstrap was considered and **rejected**: it needs a registered custom URL scheme plus
|
|
some channel to send yourself the link, which exposes the token in transit at least as much as pasting
|
|
it, for no gain over the two options above.
|
|
|
|
### Offline behavior (decided)
|
|
|
|
- The app shows the last cached data when it has no route to HA, with a visible **"Offline"** marker in
|
|
the header/title area — never a blank screen or silent staleness.
|
|
- **All edits made while offline are queued** (receipt uploads, profile edits, manual trip/fill-up
|
|
entries, etc.) and flushed once connectivity returns, rather than rejected outright. Given this is a
|
|
single-user app, conflict resolution can stay simple (last-write-wins) — there's no concurrent second
|
|
editor to reconcile against.
|
|
|
|
---
|
|
|
|
## 4. Connectivity: Option B — narrow gateway, chosen over Tailscale-always-on
|
|
|
|
**Decision: Option B.** (Option A — making the phone's Tailscale connection always-on instead of
|
|
triggered by the car's WLAN SSID — was the lower-effort alternative and remains available if this
|
|
turns out to be worth revisiting, but Option B is what's being built.)
|
|
|
|
### Goal
|
|
The app must be able to read and write data **from any network the phone is on** (cellular, foreign
|
|
Wi-Fi, home Wi-Fi) — not just when Tailscale happens to be connected — while **Home Assistant itself
|
|
stays exactly as closed/local-only as it is today**: no port-forward, no Funnel, HA's own port/UI/login
|
|
never reachable from the public internet. Only a narrow, purpose-built slice of the API is ever exposed.
|
|
|
|
### Shape (two existing, maintained HA Community Add-ons — not custom code to maintain)
|
|
|
|
1. **Cloudflared add-on** — runs `cloudflared` as an HA add-on. Opens an **outbound-only** encrypted
|
|
tunnel from the HAOS host (the OptiPlex) to Cloudflare's edge; no router port ever opens. Confirmed
|
|
capable of routing **multiple public hostnames to multiple local ports/services** — so it points at
|
|
the reverse-proxy add-on's port below, not at HA's own port 8123.
|
|
2. **Nginx Proxy Manager or Traefik add-on** — a path-scoped reverse proxy sitting between the tunnel
|
|
and HA's internal API, on the Supervisor's internal Docker network. Configured with an **allowlist**
|
|
of only the specific paths the companion app needs (e.g. `/api/states/sensor.audi_*`,
|
|
`/api/services/pyscript/audi_dashboard_*` — exact path list still TBD, depends on the final entity/
|
|
service names once the FMM003 mapping is built). Everything else — `/lovelace`, `/config`, `/auth`,
|
|
the HA frontend itself — is blocked at this layer, never reaching the tunnel at all.
|
|
3. **Auth stays a second, independent layer:** every proxied call still requires the same Bearer LLAT
|
|
HA's REST API already demands. Even a path that slipped through the allowlist would be useless
|
|
without that token, and any individual LLAT can be revoked from the HA profile if the phone is ever
|
|
lost.
|
|
|
|
### Why this over Option A
|
|
Feels like a normal app to the user — plain HTTPS, no VPN client running on the phone, no "why is my
|
|
battery draining" VPN-always-on tradeoff. The cost is two add-ons' worth of configuration (tunnel +
|
|
path allowlist) instead of a single phone setting, and the honest caveat that a narrow, purpose-built
|
|
surface *is* now internet-facing (even though HA itself never is) — accepted as worth it for the UX.
|
|
|
|
### Explicitly not chosen
|
|
- **Tailscale Funnel** — ruled out earlier for the raw FMM003→decoder TCP ingress path (port/protocol
|
|
restrictions), and not adopted for this gateway either, since it would mean quietly reversing the
|
|
existing "no Funnel" rule; not adopted without that being an explicit, separate decision.
|
|
- **Direct port-forward** — ruled out, contradicts the standing "Home Assistant is never exposed
|
|
publicly" principle from `bauauftrag.md` §3, which continues to hold for HA itself under this design.
|
|
|
|
---
|
|
|
|
## 5. Open items (not yet decided)
|
|
|
|
Deliberately deferred until hardware arrives / the next architecture review — not blocking anything
|
|
else in this document:
|
|
|
|
1. **Exact reverse-proxy path allowlist** — depends on the final pyscript entity/service names once the
|
|
FMM003 combined-entity mapping is implemented; write these down here once decided.
|
|
2. **No-hardcoding mapping system** for the FMM003 combined entity — discussed in an earlier session,
|
|
not yet written to a file or finalized in detail.
|
|
3. **Reverse-proxy add-on: Nginx Proxy Manager vs. Traefik** — both viable (§4), pick deferred to the
|
|
next audit pass. Doesn't depend on the FMM003 — could be set up and tested against HA's existing API
|
|
before the hardware arrives, if worth doing ahead of time.
|
|
4. **Cloudflare Tunnel domain** — ✅ **DECIDED: `datametric360.app`**, being registered fresh at
|
|
all-inkl for this purpose only (no websites, no mail on it). The tunnel will publish a subdomain of
|
|
it. Note `.app` is on the HSTS preload list, so browsers force HTTPS on it unconditionally — which
|
|
suits a Cloudflare Tunnel (always HTTPS) and removes any chance of an accidental plaintext request.
|
|
|
|
The constraint that drove "register a separate domain rather than reuse an existing one":
|
|
|
|
Cloudflare Tunnel needs the domain's DNS hosted at Cloudflare — i.e. **"Full setup": the domain's
|
|
nameservers get repointed to Cloudflare, moving *all* DNS records for that domain (A, MX/email,
|
|
everything) out of all-inkl's DNS management.** The alternative that avoids this — Cloudflare's
|
|
"partial / CNAME setup", which keeps DNS at the existing provider — is **Business-plan-only
|
|
($200/month)**, i.e. not realistic here.
|
|
|
|
**Therefore: never point this at an existing all-inkl domain carrying live websites or email** —
|
|
hence the dedicated `datametric360.app`. Nothing about the owner's existing domains/mail is touched.
|
|
|
|
Note the all-inkl "Neue Domain anlegen" dialog asks for a target (Webspace / Redirect /
|
|
Webbaukasten) — that choice is irrelevant here, since DNS gets delegated to Cloudflare afterwards
|
|
and the all-inkl webspace binding then goes unused. The meaningful step is the **nameserver change
|
|
to Cloudflare**, done after registration, not anything in that dialog.
|
|
|
|
---
|
|
|
|
## 6. Explicit non-goals for this piece (carried over from `bauauftrag.md`)
|
|
|
|
- Home Assistant's own UI/API is never exposed publicly, under either connectivity option.
|
|
- No change to the vehicle being read-only (no remote control) — this app remains a viewer/data-entry
|
|
tool, same as the existing panel.
|