# 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).

> **Ü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 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. **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. **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** — ✅ **ERLEDIGT: `datametric360.app` ist bei all-inkl registriert**
   (Stand 2026-08-11). 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.app`.

   `.app` steht auf der HSTS-Preload-Liste: Browser erzwingen dort HTTPS bedingungslos. Das passt zum
   Cloudflare Tunnel (immer HTTPS) und schließt eine versehentliche Klartextverbindung von vornherein
   aus.

   **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.

   Noch zu entscheiden (Kleinigkeit, erst bei der Einrichtung): ob die App unter der Domain selbst
   liegt und die abgesicherte Schnittstelle unter einer Unteradresse (etwa `api.datametric360.app`),
   oder umgekehrt. Beides funktioniert; ein getrennter Hostname für die Schnittstelle macht die
   Pfad-Freigabeliste im Reverse Proxy übersichtlicher.

   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.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.
