Files
audi-app/COMPANION_APP_ARCHITECTURE.md
T
tobias 137d32d28d Domain auf datametric360.de umgestellt (war .app)
Alle Referenzen in Doku und Code aktualisiert (REVERSE_PROXY.md,
INTERNET_ZUGRIFF_EINRICHTEN.md, COMPANION_APP_ARCHITECTURE.md, AGENTS.md,
UMSETZUNGSPLAN.md, companion-app/src/api/umgebung.ts-Kommentar). Dabei den
.app-spezifischen HSTS-Preload-Hinweis in COMPANION_APP_ARCHITECTURE.md §5.4
korrigiert - gilt für .de nicht, Force-SSL in Nginx Proxy Manager deckt das
weiterhin ab. UMSETZUNGSPLAN.md Phase 12 zusätzlich mit einem
Aktualisierungshinweis versehen (zwei-Hostnamen-Plan und pyscript-Namen dort
waren ohnehin schon überholt, jetzt klar auf REVERSE_PROXY.md/
INTERNET_ZUGRIFF_EINRICHTEN.md als maßgeblich verwiesen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 15:06:07 +02:00

455 lines
33 KiB
Markdown

# DataMetric360 — Companion App Architecture (design decided, not yet built)
**App name: `DataMetric360`** (decided 2026-08-10). Public hostname: a subdomain of
**`datametric360.de`**,
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).
> **ÜBERHOLT durch eine neue Erkenntnis (2026-08-11): Der FMM003 kann Codec JSON.**
> Damit entfällt Traccar ersatzlos — siehe den Abschnitt „§2b Codec JSON + MQTT" direkt unterhalb.
> Der folgende Traccar-Abschnitt bleibt als Entscheidungsprotokoll stehen, ist aber **nicht mehr der
> geplante Weg**.
**Überholte Entscheidung (2026-08-10, nach Recherche): Traccar, minimale Variante.**
**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.
---
## 2b. Codec JSON + MQTT — der tatsächlich geplante Weg (2026-08-11)
**Neue Information vom Besitzer: der FMM003 beherrscht „Codec JSON".** Das ändert die Architektur
grundlegend, weil damit die eine Sache entfällt, die Traccar überhaupt nötig gemacht hat: das
Entschlüsseln des binären Codec8-Protokolls. Das Gerät liefert die Daten dann bereits als JSON.
### Was recherchiert und bestätigt ist
- **Codec JSON ist ein Datenprotokoll, kein Transportweg.** Eingestellt wird es im Teltonika
Configurator unter *System → System Settings → Data Protocol → Codec JSON*.
- **Der zugehörige Transportweg ist MQTT**, nicht ein HTTP-Webhook: *GPRS Server Settings → MQTT*.
Das war die wichtigste Überraschung — die erste Vermutung „JSON heißt HTTP-POST an einen
HA-Webhook" ist falsch.
- **Ein eigener Broker ist vorgesehen und bestätigt möglich:** *GPRS Server Settings → MQTT Settings →
MQTT Client Type* bietet laut der Hilfe im Configurator (Screenshot vom Besitzer, 2026-08-11) vier
Werte: `0 = AWS IoT Shadow`, `1 = AWS IoT Custom`, `2 = Azure IoT`, **`3 = Custom server`**. Die
öffentliche Teltonika-Doku (Wiki, PDF-Guides) kennt nur die ersten drei — „Custom server" ist offenbar
erst mit einer späteren Firmware/Configurator-Version dazugekommen und deshalb dort nicht beschrieben.
Für „Custom server" werden IP/Domain und Port des eigenen Brokers (unter *Server Settings*, nicht im
MQTT-Block selbst) eingetragen; Login und Passwort kommen zum Einsatz, wenn der Broker das verlangt.
- **Praktisch bestätigt, mit einem offenen Warnsignal:** ein Teltonika-Community-Thread
([FMC003 - Custom MQTT Broker?](https://community.teltonika.lt/t/fmc003-custom-mqtt-broker/14784),
Juli 2025) zeigt einen Nutzer, der genau unser Setup (eigener Mosquitto auf einem VPS, eigene CA,
Client-Zertifikat vom Gerät) über den Modus „AWS IoT Custom" nachgebaut hat — 24 Stunden stabil
getestet, damals gab es „Custom server" noch nicht als eigene Option. Zwei Monate später berichtet
ein anderer Nutzer **speziell für den FMM003**, dass bei ihm über MQTT keine Pakete am Server
ankamen — nach Rückwechsel auf TCP lief es wieder. Keine Auflösung im Thread sichtbar.
- **Konsequenz:** die MQTT-Anbindung gilt als grundsätzlich machbar, aber **nicht als sicher
funktionierend, bevor sie mit dem echten Gerät getestet wurde** — TCP/Codec8 (der bisherige,
bekannt funktionierende Weg über Traccar) bleibt der Rückfallplan, falls MQTT am FMM003 in der
Praxis Probleme macht.
- **„Custom server" beim Besitzer nicht auswählbar (2026-08-11):** im eigenen Configurator lässt
sich Wert 3 nicht anklicken. Ein zweiter, unabhängiger Praxisbericht
([mdworld.nl, FMC003 + eigener OpenRemote-Server](https://mdworld.nl/asset-tracking), Aug. 2025)
bestätigt den gleichen Umweg wie der Community-Thread: **„AWS IoT Custom" wählen und dort auf den
eigenen Server zeigen** — funktioniert nachweislich, unabhängig davon ob „Custom server" gerade
wählbar ist oder nicht. Das ist damit der empfohlene Standardweg, nicht nur ein Fallback.
- **Zertifikatsketten-Reihenfolge ist umgekehrt zur Konvention (wichtiger Stolperstein, aus
demselben Praxisbericht):** Teltonika-Geräte erwarten die hochgeladene Zertifikatskette in
**umgekehrter Reihenfolge** (Wurzelzertifikat zuerst, nicht zuletzt) — anders als das übliche
`fullchain.pem`-Format (Server-Cert zuerst). Wird das ignoriert, schlägt der TLS-Handshake mit
einem kryptischen Fehler fehl (`SSL routines::tlsv1 alert unknown ca`), der nicht offensichtlich
auf die Reihenfolge hindeutet. **Vor dem Hochladen im Security-Tab die Kette umdrehen.**
- **Topic-Namensschema (aus demselben Praxisbericht, als Orientierung):** z. B.
`<name>/teltonika/%imei%/data` und `.../commands``%imei%` wird vom Gerät automatisch ersetzt.
Exakte eigene Werte erst bei der Inbetriebnahme festlegen.
- **TLS ist Pflicht, nicht optional:** ohne hochgeladene Zertifikate arbeitet MQTT nicht. Zertifikat,
privater Schlüssel und Wurzelzertifikat werden im Configurator unter *Security* hinterlegt.
- **Dateinamen-Endung ist strikt geprüft (2026-08-11, live am Configurator beobachtet):** hochgeladene
Dateien müssen auf `.pem`, `.pem.crt` oder `.pem.key` enden, sonst Fehler „File format is not valid
or not supported". Reiner Inhalt (PEM-Format) reicht nicht, die Endung muss stimmen. Schema folgt
offenbar der AWS-IoT-Download-Konvention: Root-Zertifikat `*.pem`, Geräte-Zertifikat `*.pem.crt`,
privater Schlüssel `*.pem.key`. Eigene CA-Dateien entsprechend umbenennen, nicht nur `.crt`/`.key`.
- **Firmware-Abhängigkeit:** Codec JSON ist nicht in jeder Firmware enthalten (bei mehreren
Modellen erst ab 03.28.00 dokumentiert). Der Besitzer bestätigt, dass es auf diesem Gerät
verfügbar ist — vor der Inbetriebnahme trotzdem die Firmware-Version notieren.
- **Home Assistant kann MQTT von Haus aus:** Mosquitto-Broker als offizielles Add-on, dazu die
MQTT-Integration mit `sensor`/`device_tracker`, `json_attributes_topic` und Templates, um beliebige
JSON-Felder herauszuziehen. Es gibt Community-Berichte, die genau das mit Teltonika-Trackern
gemacht haben.
### Was damit ersatzlos entfällt
| Bisher geplant | Jetzt |
|---|---|
| Traccar-Add-on | entfällt — es gibt nichts mehr zu dekodieren |
| Traccar-Datenbank (H2) inkl. Sicherung der `.mv.db` | entfällt — kein eigener Datenspeicher mehr |
| Position Forwarding → HA-Webhook | entfällt — HA hört direkt am Broker mit |
| Offener roher TCP-Port für Codec8 | entfällt — stattdessen MQTT über TLS |
| Eigener Codec8-Decoder als Alternative | erledigt sich |
Übrig bleibt: **FMM003 → MQTT/TLS → Mosquitto-Add-on → MQTT-Integration → Entitäten in HA.** Ein
Baustein statt dreien, und alle Teile davon sind offizielle HA-Add-ons beziehungsweise
Kernintegrationen statt Fremdsoftware.
### Was dadurch neu zu klären ist
1. **Erreichbarkeit des Brokers von außen.** Das Problem verschwindet nicht, es ändert nur die Form:
Das Fahrzeug muss den Broker im Internet erreichen. Cloudflare Tunnel hilft hier **nicht** — der
ist auf HTTP zugeschnitten, und der FMM003 kann keinen Tunnel-Client mitbringen. Zwei gangbare
Wege:
- **Portfreigabe 8883** direkt auf Mosquitto. Verstößt formal gegen den Grundsatz „nichts
freigeben", ist aber etwas völlig anderes als Home Assistant selbst freizugeben: ein Port mit
TLS-Zwang und Anmeldung, hinter dem nur ein Nachrichtenbroker sitzt.
- **Kleiner Broker auf einem VPS**, der per Mosquitto-eigenem *Bridge*-Modus mit dem Broker
zuhause gekoppelt wird (die Verbindung baut das Zuhause nach außen auf, über Tailscale). Dann
ist zuhause weiterhin nichts erreichbar. Aufwendiger, dafür ohne jede Freigabe.
2. **Tatsächliche Nutzlast.** Wie die CAN/FMS-Werte im JSON heißen, ist noch unbekannt. Sobald das
Gerät läuft: mit MQTT Explorer oder `mosquitto_sub` eine echte Nachricht mitschneiden und daraus
die Zuordnungstabelle bauen (siehe offener Punkt 2 in §5) — nicht vorher raten.
3. **Zertifikate.** Für Mosquitto müssen Server-Zertifikat und die passenden Dateien für das Gerät
erzeugt werden. Bei eigenem Broker heißt das in der Regel eine eigene kleine CA.
4. **Datenvolumen.** Teltonika weist selbst darauf hin, dass Codec JSON deutlich mehr Bytes braucht
als das binäre Codec8E. Bei einem privat genutzten Fahrzeug ist das vernachlässigbar, sollte man
bei einem knappen Mobilfunktarif aber im Blick behalten.
Beides — Broker-Erreichbarkeit und Zertifikate — ist erst bei der Inbetriebnahme zu erledigen und
blockiert nichts, was jetzt gebaut wird.
### 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 add-on** (decided, 2026-08-28 — see §5 item 3) — 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 (`/api/states/
sensor.audi_dashboard_*`, `/api/services/audi_dashboard/*`, plus a handful more — the exact,
current list lives in `homeassistant/REVERSE_PROXY.md`, not duplicated here since entity/service
names can change and this doc would only go stale again). 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.**ERLEDIGT (2026-08-28): reverse-proxy path allowlist defined and current.** Lives in
`homeassistant/REVERSE_PROXY.md`, kept in sync with the real entity/service names
(`sensor.audi_dashboard_*` / `audi_dashboard.<name>` since the native-integration conversion,
`AGENTS.md` §H) rather than duplicated here. The step-by-step setup runbook that applies it in
Nginx Proxy Manager is `homeassistant/INTERNET_ZUGRIFF_EINRICHTEN.md`. One correction worth
recording: the allowlist originally (2026-08-11 planning) assumed the app itself would be served as
a web build under `/local/dm360/` — that was never built. Distribution stayed **native/sideload
only** (§1), so the allowlist instead needed the app's own OTA surface
(`/audi_dashboard_static/app/*`, the `@capgo/capacitor-updater` bundle) rather than a served
web app.
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. **Kann erst nach der ersten echten
MQTT-Nachricht fertig werden** (§2b): die Feldnamen im Codec-JSON sind noch unbekannt und werden
mitgeschnitten, nicht geraten.
2a. ✅ **ERLEDIGT (Entscheidung, 2026-08-11): Portfreigabe 8883**, nicht VPS-Bridge. Begründung: ein
Broker mit TLS-Zwang und Pflicht-Client-Zertifikat lässt vor dem eigentlichen Login niemanden
auch nur ansetzen — der Mehraufwand einer dauerhaften VPS-Miete plus zweitem Mosquitto plus
Bridge-Verbindung stand in keinem Verhältnis zum zusätzlichen Schutz für ein einzelnes
Privatfahrzeug. Betrifft nur den Weg Fahrzeug→Zuhause und ist unabhängig von der Anbindung der
App (§4, dort bleibt es bei Cloudflare Tunnel — kein Widerspruch, siehe dort die Begründung, warum
ein eng begrenzter, zertifikatsgesicherter Port etwas anderes ist als HA selbst offenzulegen).
**Umsetzungsstand (2026-08-11):** DuckDNS-Hostname `datametric360.duckdns.org` angelegt und als
Add-on in HA eingerichtet, aktualisiert zuverlässig die öffentliche IP — das ist der für den
FMM003-Pfad tatsächlich entscheidende Teil. Router bestätigt **Dual-Stack** (kein DS-Lite), die
Portfreigabe 8883 am Speedport Smart 4 Plus ist eingerichtet, FMM003 sendet bestätigt Daten.
**Let's-Encrypt-Zertifikat für die HA-Oberfläche selbst — ✅ ERLEDIGT (2026-08-11), nach
ausführlicher Fehlersuche.** Der `deploy_challenge`-Hook (DNS-01, TXT-Eintrag via DuckDNS-API)
scheiterte zunächst wiederholt mit `timeout 120s` beim Warten auf den eigenen TXT-Eintrag, obwohl
DuckDNS den Eintrag nachweislich korrekt setzte (bestätigt per `nslookup` gegen `8.8.8.8` vom PC
*und* per `dig` aus der HA-eigenen SSH-Konsole gegen den internen Supervisor-Resolver). Der DNS-
Server selbst war am Ende kein Faktor (Erfolg trat auch mit dem Speedport als DNS-Server ein) —
**tatsächliche Ursache war ein fehlerhafter `aliases`-Eintrag** in der DuckDNS-Add-on-Konfiguration
(`alias: datametric360` ohne `.duckdns.org`, überflüssig und falsch, da keine externe Domain per
CNAME verwendet wird — `aliases` ist nur für diesen Fall gedacht) zusammen mit `accept_terms:
false`. Nach Entfernen von `aliases` und Setzen von `accept_terms: true` lief die Zertifikatsanfrage
beim nächsten Versuch sofort erfolgreich durch (`Challenge is valid! ... Creating fullchain.pem...
Done!`), Zertifikat gültig bis 2026-11-09.
Anschließend `configuration.yaml`'s alter `http:`-Block (der HA-Start blockiert hatte, solange die
Zertifikatsdateien nicht existierten) entfernt — HA migriert SSL-Zertifikatspfad/-Schlüssel und die
interne/externe URL inzwischen in die Oberfläche (Einstellungen → System → Netzwerk). **Eigenheit
dabei:** eine geänderte Netzwerkkonfiguration muss innerhalb von 5 Minuten über einen Dialog in der
Oberfläche bestätigt werden, sonst macht HA sie automatisch rückgängig und startet mit dem vorigen
Stand neu (`Pending HTTP config was not confirmed within 0:05:00` im Log) — beim ersten Versuch
deshalb ungewollt zurückgerollt, beim zweiten Versuch bewusst sofort bestätigt, seitdem stabil.
Externe URL bewusst leer gelassen (Port 8123 ist nicht freigegeben, HA bleibt nicht öffentlich
erreichbar); interne URL auf `https://192.168.2.216:8123` gesetzt — das Zertifikat ist auf den
Hostnamen ausgestellt, nicht auf die IP, der Browser zeigt deshalb bei IP-Zugriff weiterhin eine
Namensabgleich-Warnung (Verbindung bleibt trotzdem verschlüsselt); ein lokaler DNS-Eintrag, der
`datametric360.duckdns.org` intern auf die lokale IP auflöst, wäre die sauberere, aber optionale
Nachbesserung.
Nebenbei geklärt: ein Vorschlag, stattdessen komplett auf Cloudflare zu setzen, wurde geprüft und
**ist keine Alternative** — Cloudflare Tunnel kann kein rohes MQTT/TCP transportieren (nur HTTP),
der FMM003 kennt außerdem kein Tunnel-Konzept, nur IP/Domain+Port. Bleibt bei der klaren Trennung:
Cloudflare Tunnel für die App (§4), Portfreigabe+DuckDNS nur für die Fahrzeug-MQTT-Verbindung.
2b. **TLS-Zertifikate für Mosquitto und das Gerät** — ✅ **ERLEDIGT (2026-08-11):** eigene private CA
(10 Jahre gültig) erzeugt, Server-Zertifikat (CN `datametric360.duckdns.org`) und Client-Zertifikat
für das Gerät signiert, beide gegen die CA verifiziert. Mosquitto ist mit `certfile`/`keyfile`/
`cafile`/`require_certificate: true` konfiguriert. **Noch offen:** die drei Geräte-Dateien (Root,
Client-Zertifikat, privater Schlüssel) müssen noch in den FMM003-Configurator hochgeladen werden —
dabei auf die Dateinamen-Endung achten (siehe Anmerkung oben bei „Dateinamen-Endung ist strikt
geprüft").
3.**ERLEDIGT (2026-08-28): Nginx Proxy Manager**, not Traefik. Both remained viable per §4; NPM was
picked and is what `homeassistant/REVERSE_PROXY.md` and `INTERNET_ZUGRIFF_EINRICHTEN.md` are written
against. Setup itself (Cloudflare account, nameserver switch, add-on installation) is still the
owner's own action to perform — see the runbook for the exact steps.
4. **Cloudflare Tunnel domain** — ✅ **ERLEDIGT: `datametric360.de` ist bei all-inkl registriert**
(ursprünglich `.app`, seither auf `.de` umgestellt — Stand 2026-08-28). Eigens dafür angelegt, ohne
Webseite und ohne Postfach darauf — genau so, wie es die unten stehende Einschränkung verlangt.
Vorgesehene Adresse der App später: `https://datametric360.de`.
**Korrektur 2026-08-28:** die ursprüngliche `.app`-Wahl hatte einen zusätzlichen Vorteil, der mit dem
Wechsel auf `.de` entfällt — `.app` steht auf Chromes HSTS-Preload-Liste, `.de` nicht, der Browser
erzwingt dort also nicht von sich aus HTTPS ohne vorherigen Kontakt zur Domain. Das ändert nichts an
der Sicherheit dieses Aufbaus: Cloudflare Tunnel liefert ohnehin ausschließlich HTTPS aus, und Nginx
Proxy Manager kann zusätzlich "Force SSL" erzwingen (siehe `INTERNET_ZUGRIFF_EINRICHTEN.md` Schritt
4) - nur der zusätzliche, browserseitig *vorab* erzwungene Schutz vor dem allerersten Verbindungs-
aufbau (bevor die App überhaupt einmal erfolgreich verbunden war) entfällt. Für ein sideload-only
installiertes, nicht öffentlich beworbenes Gerät eine vernachlässigbare Einbuße.
**Noch offen, und der eigentliche Knackpunkt:** die Nameserver der Domain müssen bei all-inkl auf
Cloudflare umgestellt werden („Full setup") — erst dann kann der Tunnel einen Hostnamen darunter
veröffentlichen. Die Variante, bei der die DNS-Verwaltung bei all-inkl bliebe („partial/CNAME
setup"), gibt es nur im Business-Tarif für 200 $/Monat. Weil auf dieser Domain nichts liegt außer
diesem Vorhaben, ist die Umstellung folgenlos.
**ERLEDIGT (2026-08-28): ein einziger Hostname genügt**, kein Split nötig. Der ursprüngliche
Gedanke (App-Domain vs. API-Subdomain) setzte voraus, dass die App selbst als Web-Build unter einer
eigenen Adresse ausgeliefert würde — das wurde nie gebaut (Distribution blieb nativ/sideload, §1).
Es gibt nur eine Adresse, die überhaupt gebraucht wird: `https://datametric360.de`, die die App
sowohl für API-Aufrufe als auch für ihr eigenes OTA-Bündel verwendet (siehe §5 Punkt 1 oben,
`homeassistant/REVERSE_PROXY.md`).
Die Einschränkung, die zur eigenen Domain geführt hat:
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.de`. 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.