Review des aktuellen Stands und Plan fuer die native HA-Integration

Zwei Dokumente, beide reine Analyse - kein Code geaendert.

REVIEW_main_2026-08-13.md: 15 Befunde aus den Commits c66ed82..d5562c7,
nach Schaden sortiert, jeder mit Datei-Zeile und der Kette vom Ausloeser zur
Auswirkung. Die drei schwersten liegen im neuen Setup-Menue:

1. Speichern, solange der Katalog noch nicht geladen ist, ueberschreibt
   entitaeten.json mit {} und loescht alle Zuordnungen. Faellt erst beim
   naechsten Neustart auf, weil die setattr-Werte im Prozess bestehen
   bleiben - und entitaeten.json wird von backup.py nicht gesichert.
2. "Zuruecksetzen" wirkt bei 15 von 17 Feldern nicht, weil
   overrides_anwenden() leere Werte ueberspringt und der Standardwert bei
   fast allen Feldern leer ist. Isoliert nachgestellt.
3. Alle vier Listenpositionen bekommen denselben Sensor, weil der
   Vorschlag den Index nicht auswertet. Folge: der Sicherheitscheck prueft
   viermal dieselbe Tuer und meldet "Sicher abgestellt", obwohl drei Tueren
   nie geprueft wurden.

Jeder Befund ist als belegt oder plausibel gekennzeichnet.

ha_install.md: Plan fuer die Umstellung von pyscript auf eine eigene
Integration mit Konfigurationsdialog - Einrichtung komplett ueber die
Oberflaeche, kein YAML, Updates als Knopfdruck. Alle genannten
Schnittstellen sind an der laufenden 2026.8.1-Instanz geprueft, inklusive
Signaturen.

Zur Verteilung: HACS kann ausschliesslich GitHub und scheidet fuer das
Gitea-Repository aus. App-Repositories akzeptieren zwar beliebige Git-URLs,
Apps sind aber Docker-Container und damit der falsche Behaelter fuer eine
Integration. Nativ bleibt eine eigene UpdateEntity, deren Logik in
updateverwaltung.py bereits zu grossen Teilen existiert.

Festgehalten ist ausserdem, welche der Review-Befunde die Umstellung
konstruktionsbedingt aufloest - unter anderem alle drei oben genannten sowie
die Token-Datei im Klartext, weil eine Integration den Recorder direkt
abfragen kann statt sich selbst ueber HTTP.
This commit is contained in:
Paul Nothaft
2026-08-13 13:43:25 +02:00
parent d5562c71ef
commit 12418a3d52
2 changed files with 638 additions and 0 deletions
+391
View File
@@ -0,0 +1,391 @@
# Review: Branch `main`, Commits `c66ed82..d5562c7`
**Datum:** 2026-08-13 · **Umfang:** 18 Commits, 24 Dateien, +2752/259 Zeilen
**Gegenstand:** Setup-Menü zur Sensor-Zuordnung, Umstellung von WLAN/VAG auf FMM003,
Standort-Kachel mit Live-Position, iOS-/Großbildschirm-Optikauflage, DuckDNS und Zertifikate.
Geprüft wurde gegen eine Arbeitskopie von `origin/main`. Die mit **[belegt]** markierten Befunde
sind am Code nachvollzogen oder nachgestellt; **[plausibel]** heißt: aus dem Code abgeleitet, aber
nicht im Browser reproduziert.
---
## Gesamteindruck
Handwerklich stark. Das Setup-Menü ersetzt „vor der Installation `einstellungen.py` editieren"
durch eine Oberfläche mit Live-Wertvorschau, Unavailable-Warnung, Duplikat-Prüfung und einem
ehrlichen Neustart-Hinweis für die drei triggergebundenen Felder. Der Kniff dahinter — `setattr`
auf dem laufenden Modul-Objekt, weil alle Leser `einstellungen.KM_SENSOR` als lebenden
Attributzugriff machen — ist elegant und im Kopfkommentar sauber begründet.
Positiv außerdem: Escaping ist im neuen Code durchgängig (kein XSS gefunden), Karten-Ebenen werden
vor dem Neuaufbau entfernt, die Haversine-Distanz ist mathematisch korrekt, Listenfelder sind gegen
zu kurze Arrays abgesichert, und die Python-Syntax aller acht geänderten pyscript-Dateien ist in
Ordnung.
Die Fehler unten liegen fast alle im selben Bereich — dem neuen Setup-Menü — und **drei davon
werden erst nach einem Neustart sichtbar**, also mit maximalem zeitlichem Abstand zur Ursache.
## Übersicht
| # | Befund | Wirkung | Ort |
|---|---|---|---|
| 1 | Speichern bei fehlendem Katalog löscht alle Zuordnungen | Datenverlust | `audi-dashboard-app.js:1773, 2031` |
| 2 | „Zurücksetzen" wirkt bei 15 von 17 Feldern nicht | falscher Zustand | `entitaeten.py:188` |
| 3 | Alle vier Listenpositionen bekommen denselben Sensor | falsche Sicherheitsaussage | `audi-dashboard-app.js:1976` |
| 4 | Vorschlagsschwelle akzeptiert beliebige Sensoren | falscher Fahrt-Trigger | `audi-dashboard-app.js:1980` |
| 5 | Geleerte Listen setzen `["","","",""]` | Status dauerhaft „unbekannt" | `entitaeten.py:188` |
| 6 | Zuordnung wird nirgends gesichert | Verlust bei Wiederherstellung | `backup.py:32` |
| 7 | Kaputte `entitaeten.json` legt das Panel lahm | Totalausfall | `entitaeten.py:159` |
| 8 | Standortabfrage im Dauerfeuer bei abgelehnter Freigabe | Akku, Netz | `audi-dashboard-app.js:553/557` |
| 9 | Kein Tastaturweg in die Einstellungen unter 860 px | Bedienbarkeit | `audi-dashboard-app.js:3849` |
| 10 | `.navmarke` nur in der iOS-Auflage versteckt | Tab-Leiste zerfällt | `audi-dashboard-ios.css:194` |
---
# Schwerwiegend
## 1. Speichern bei fehlendem Katalog löscht die gesamte Zuordnung [belegt]
**Kette:**
1. `ENTITAETEN` wird an genau einer Stelle gefüllt (`audi-dashboard-app.js:3723`), aus `hass.states`.
2. Der Nachlade-Pfad nach einem HA-Neustart (`:3699-3703`) holt Profil, Fahrten, Tankvorgänge,
Status und Batterieverlauf nach — **nicht** aber `pyscript.audi_dashboard_entitaeten`.
3. `DATEN_GELADEN` hängt allein am Profil. Die App gilt also als geladen, während der Katalog fehlt.
4. Der Knopf „Setup — Sensoren zuordnen" (`:1773`) ist nur an `einrichtenOffen` gekoppelt, nicht an
den Katalog. `setupKatalog()` liefert `[]`, das Popup öffnet **ohne eine Zeile und ohne
Fehlermeldung**, `setupZuordnung` bleibt `{}`.
5. „Speichern" schickt `"{}"` (`:2031`). `overrides_schreiben()` ersetzt die Datei vollständig,
ohne Zusammenführen (`entitaeten.py:166-173`).
**Auswirkung:** `data/entitaeten.json` enthält danach `{}`. Weil die `setattr`-Werte im laufenden
Prozess bestehen bleiben, fällt der Verlust **erst beim nächsten Neustart** auf. Zusammen mit
Befund 6 (keine Sicherung) gibt es dann nichts zurückzuholen.
**Vorschlag:** Setup-Knopf sperren, solange `ENTITAETEN` null ist, und in
`setupSpeichernAusfuehren()` abbrechen, wenn `setupZuordnung` leer ist. Zusätzlich die Entität in
den Nachlade-Pfad aufnehmen.
## 2. „Zurücksetzen" wirkt bei 15 von 17 Feldern nicht [belegt, nachgestellt]
`entitaeten.py:188` überspringt leere Werte:
```python
if wert in (None, "", []):
continue
```
Der Kopfkommentar derselben Datei beschreibt das Problem exakt richtig — `setattr` verändert das
Modul dauerhaft, „Zurücksetzen" muss den Standardwert *aktiv* zurückschreiben. Das Frontend tut das
auch (`setupStandardwert`). Nur ist der Standardwert bei 15 der 17 Katalogfelder `""` oder `[]`
und genau die werden übersprungen.
Nachgestellt:
```
nach Zuordnung: KM_SENSOR = 'sensor.alt_vag_mileage'
nach Zurücksetzen: KM_SENSOR = 'sensor.alt_vag_mileage' ← erwartet: ''
```
**Auswirkung:** Für den Nutzer sieht es aus, als hätte das Speichern versagt — `aktueller_stand()`
liest aus dem laufenden Modul, das Feld zeigt beim nächsten Zeichnen wieder den alten Wert.
Funktioniert nur bei `ZUENDUNG_SENSOR`, `BATTERIE_SENSOR` und `STANDORT_TRACKER`, den drei Feldern
mit nicht-leerem Standard.
## 3. Alle vier Listenpositionen bekommen denselben Sensor [belegt]
`audi-dashboard-app.js:1976-1981`:
```js
zuordnung[feld.key] = feld.positionen.map((_, i) => {
const vorhanden = (werte[feld.key] || [])[i];
if (vorhanden) return vorhanden;
const vorschlag = entitaetKandidaten(feld.key, "", "")[0]; // i geht nicht ein
return vorschlag && entitaetScore(feld, vorschlag, "") >= 3 ? vorschlag.id : "";
});
```
`entitaetKandidaten()` hängt nicht vom Index ab und schließt bereits vergebene IDs nicht aus —
alle vier Positionen bekommen dieselbe Entität. Betrifft `TUER_SENSOREN`, `FENSTER_SENSOREN`,
`TUERSCHLOSS_SENSOREN`.
**Auswirkung:** Bei einer Frischinstallation (genau der Fall, für den das Menü gebaut wurde) prüft
`_sicherheitscheck()` danach viermal dieselbe Tür. **„Sicher abgestellt" meldet gesichert, obwohl
drei Türen nie geprüft wurden.** Die Duplikat-Warnung fängt das beim Speichern ab, ist aber mit
„Trotzdem speichern" wegklickbar.
## 4. Die Vorschlagsschwelle akzeptiert beliebige Sensoren [belegt]
`entitaetScore()` (`:1936`) vergibt: Stichwort +5, Domain +3, device_class +3, Einheit +2. Die
Schwelle ist `>= 3`**ein Domain-Treffer allein genügt**, ohne dass ein einziges Stichwort passt.
Verschärfend: `setupNurPassend` wird zwei Zeilen vorher auf `true` gesetzt (`:1971`), wodurch
`entitaetKandidaten()` ohnehin hart auf die Domain filtert. Die Schwelle ist damit *immer* erfüllt.
Die Reihenfolge kommt aus `Object.keys(HASS.states)`, ist also faktisch willkürlich.
**Auswirkung:** Ohne passenden Zündungssensor wird `ZUENDUNG_SENSOR` stumm mit dem erstbesten
`binary_sensor.*` vorbelegt — Bewegungsmelder, Fensterkontakt, was zuerst kommt. Das Feld sieht
ausgefüllt aus, wird mitgespeichert, und **die gesamte Fahrterkennung hängt an einem
Zufallssensor**. Bei Einzelfeldern greift keine Duplikat-Warnung. Dasselbe für `TANK_SENSOR` (jeder
%-Sensor, etwa Luftfeuchte) und `REFRESH_BUTTON` (jedes `button.*`).
**Vorschlag:** Schwelle auf `>= 5` — also mindestens ein Stichworttreffer.
## 5. Geleerte Listenfelder setzen `["","","",""]` [belegt]
Die Skip-Regel aus Befund 2 prüft auf `[]`. Ein Array aus vier leeren Zeichenketten ist das nicht:
```python
["","","",""] in (None, "", []) # False -> wird per setattr gesetzt
```
`_sicherheitscheck()` zippt dann über vier leere Entity-IDs und erzeugt vier „unbekannt"-Zeilen —
mal drei Listen sind das zwölf.
**Auswirkung:** Weil die Gesamtaussage bewusst `None` wird, sobald *eine* Prüfung unbekannt ist,
steht die Übersicht danach **dauerhaft auf „Zustand unbekannt"** statt auf grün.
Kurios ist die Kombination mit Befund 2: Bei Einzelfeldern wirkt „Zurücksetzen" gar nicht, bei
Listenfeldern zu stark. Beides verschwindet mit demselben Fix — statt leere Werte zu überspringen,
immer den Standardwert aus `_STANDARDWERTE` zurückschreiben:
```python
def overrides_anwenden():
overrides = overrides_lesen()
for key in _SCHLUESSEL:
wert = overrides.get(key)
leer = wert in (None, "", []) or (isinstance(wert, list) and not any(wert))
setattr(einstellungen, key, _STANDARDWERTE[key] if leer else wert)
```
## 6. Die Sensor-Zuordnung wird nirgends gesichert [belegt]
`backup.py:32` sichert `fahrzeugprofil.json`, `fahrten.jsonl`, `tankvorgaenge.jsonl`
`entitaeten.json` fehlt. Auch `audi_dashboard_backup_wiederherstellen` kennt es nicht, `update.ps1`
fasst `data/` bewusst nicht an, und in `homeassistant/.gitignore` steht es nicht neben den anderen
Laufzeitdateien.
**Auswirkung:** Nach einer Wiederherstellung ist die komplette Sensor-Zuordnung still weg. In
Verbindung mit Befund 1 gibt es keinen Rückweg.
## 7. Eine beschädigte `entitaeten.json` legt das Panel lahm [belegt]
`overrides_lesen()` (`entitaeten.py:159`) ruft `json.loads` ohne Absicherung. Der Aufruf steht in
`beim_start()` **vor** `alles_veroeffentlichen()` — wirft er, wird gar nichts veröffentlicht, das
Panel bleibt komplett leer.
Geschrieben wird zwar atomar über `.tmp` + `os.replace`, das Risiko ist also gering — der Ausfall
wäre aber total. Dieselbe Fehlerklasse, die im Branch `umsetzung-datametric360` bei `profil_lesen()`
bereits abgefangen wurde.
---
# Mittel
## 8. Standortabfrage im Dauerfeuer, Karte bei jedem Render neu [belegt]
`USER_POS_TS` wird nur im Erfolgs-Callback gesetzt (`:553`), nicht im Fehlerfall (`:557`). Die
30-Sekunden-Drossel in `standortErfassenFallsNoetig()` (`:560`) greift damit nie, wenn der Nutzer
die Standortfreigabe ablehnt oder kein Fix zustande kommt.
Parallel dazu zerstört `render()` die Vorschaukarte und baut sie neu (`:2879-2880`), sobald die
Route `home` ist — bei **jedem** Backend-Update.
**Auswirkung:** Mit einem GPS-Tracker als Quelle (genau der FMM003-Fall) sind das dutzende
`getCurrentPosition({enableHighAccuracy:true})`-Anfragen pro Stunde plus sichtbares Kachelflackern.
**Vorschlag:** `USER_POS_TS = Date.now()` auch im Fehlerpfad; Karte nur bei echtem Ansichtswechsel
neu bauen — die Variable `gleicheAnsicht` gibt es in `render()` bereits.
## 9. Kein Tastaturweg in die Einstellungen unter 860 px [belegt]
`audi-dashboard-ios.css:81` versteckt `.profilbtn` — den echten `<button aria-label="Einstellungen">`
und holt ihn erst in der Container-Abfrage ab 860 px zurück (`:230`). Der Ersatz ist ein
Klick-Handler auf `#marke` (`:2965`), und das ist ein `<div class="rings" aria-label="Audi">`
(`:3849`): ohne `role`, ohne `tabindex`.
**Auswirkung:** In Telefonbreite gibt es für Tastatur- und Screenreader-Nutzer **keinen** Zugang zu
den Einstellungen. Ein `<div>` mit `aria-label` ohne Rolle wird von den meisten Screenreadern nicht
einmal angesagt. Gleiche Fehlerklasse wie das Swipe-Löschen aus `AUDIT_2026-08-10.md`.
**Vorschlag:** `#marke` zu einem `<button aria-label="Einstellungen">` machen; dann kann auch
`pointer-events:auto` in `audi-dashboard-ios.css:78` entfallen.
## 10. `.navmarke` nur in der iOS-Auflage versteckt [belegt]
`audi-dashboard-app.js:2956-2963` hängt den Ringklon **unbedingt** in `#tabbar`.
`.navmarke{display:none}` steht ausschließlich in `audi-dashboard-ios.css:194`. `.tabbar` ist ein
`display:grid` mit `repeat(5, 1fr)` (`audi-dashboard.css:211`).
**Auswirkung:** Lädt `/local/audi-dashboard-ios.css` nicht (404, Cache), wird der Klon ein sechstes
Grid-Element — alle Tabs rutschen eine Spalte weiter, „Tanken" fällt in eine zweite Zeile, die
Menüleiste zerfällt. Kein theoretischer Fall: Commit `3548f77` dokumentiert genau dieses
Deployment-Loch.
**Vorschlag:** `.navmarke{display:none}` gehört in die Basis-CSS, die Sichtbarkeit in die Auflage.
## 11. Standort-Menü ohne `pointercancel` [belegt]
`standortMenuVerdrahten()` (`:706-731`) setzt `smY0` nur in `pointerup` zurück. Alle drei anderen
Zeigergesten der Datei haben einen `pointercancel`-Handler (`:2929`, `:3055`, `:3122`), diese nicht.
**Auswirkung:** Übernimmt das System die Berührung (iOS-Randgeste, Multitouch, eingehender Anruf),
bleibt `smY0` gesetzt. Der `pointermove`-Handler ruft dann `preventDefault()` für jede weitere
Bewegung — **Scrollen ist in der ganzen App blockiert**, bis irgendwo ein `pointerup` kommt, der
dann ein `dy` aus einem veralteten Startpunkt auswertet.
## 12. Fehlgeschlagenes Reverse-Geocoding wird dauerhaft gecacht [belegt]
`:572-586` setzt `STANDORT_ADRESSE_KEY` auch im `catch`-Zweig.
**Auswirkung:** Ein einziger fehlgeschlagener Nominatim-Abruf (Rate-Limit, kurzer Netzausfall) für
eine Zelle führt dazu, dass für dieselbe Zelle die **gesamte Sitzung** lang nur Koordinaten
angezeigt werden, auch wenn das Netz längst wieder da ist.
**Vorschlag:** Key nur im Erfolgsfall setzen.
## 13. Setup-Popup hält den `role="dialog"`-Vertrag nicht ein [belegt]
`:2774` deklariert `role="dialog" aria-modal="true"`. Es fehlen: Escape zum Schließen (in der Datei
gibt es keinen `keydown`-Handler), eine Fokusfalle (Tab wandert in den überdeckten Hintergrund),
und der Schalter „Nur passende Sensoren anzeigen" (`:2779`) hat keinen zugänglichen Namen — der
Beschriftungstext steht im Geschwister-`<span>` außerhalb des `<label>`.
Dazu: Die Entitäts-Auswahl öffnet ausschließlich im `click`-Handler (`:3181`). Wer per Tab ins Feld
springt und tippt, löst zwar den `input`-Handler aus, der bricht aber ab, weil `setupSucheOffen`
noch `null` ist — sichtbar passiert nichts. `role="combobox"`, `aria-expanded` und `aria-controls`
fehlen ebenfalls.
## 14. Standort-Menü nicht per Tastatur bedienbar [belegt]
Im eingeklappten Zustand steht das Blatt per `transform` überwiegend außerhalb des Sichtbereichs,
bleibt aber vollständig im DOM und im Tab-Fokus — Adresse, Route-Link und Teilen-Knopf sind
fokussierbar, ohne sichtbar zu sein (kein `inert`, kein `aria-hidden`). Öffnen geht gar nicht per
Tastatur: `.standortmenu-griffzone` ist ein `<div>` mit reinen Pointer-Handlern. Der Umschalter
Straße/Satellit (`:773`) hat kein `aria-pressed`, sein Label bleibt in beiden Zuständen gleich.
## 15. `disconnectedCallback()` räumt nur eine von drei Karten ab [belegt]
`:3815-3817` zerstört `MAP`, nicht aber `SMAP`/`TMAP`. Wird das Panel abgehängt, während Standort-
oder Übersichtsseite offen ist, bleibt eine Leaflet-Instanz samt Resize-Listener und
Kachel-Abrufen am Leben.
---
# Klein
| Ort | Befund |
|---|---|
| `audi-dashboard-ios.css:88, 208` | `main{padding:…}` (0,0,1) verliert gegen `main#view` (1,0,1) in `audi-dashboard.css:197`. Auf Großbildschirmen bleibt der Seitenrand bei 20 px statt der beabsichtigten 44 px. **Achtung:** `.standort-vollbild{margin:0 -20px}` (`audi-dashboard.css:704`) passt heute genau dazu — beides gehört zusammen angefasst. |
| `audi-dashboard-app.js:3190/3612` | „Nur passende Sensoren anzeigen" wirkt nicht, solange ein Dropdown offen ist: Der Klick-Handler ersetzt das Overlay-DOM, bevor das `change`-Event des Kontrollkästchens ausgeliefert wird. [plausibel] |
| `audi-dashboard-app.js:2966` | Theme-Umschalten ruft kein `render()` — die Leaflet-Kacheln bleiben bis zum nächsten Backend-Update im alten Stil. Durch die neue Dauerkarte auf der Übersicht deutlich sichtbarer als früher. |
| `audi-dashboard-app.js:804` | `${de(CAR.tankPct)} %` zeigt ohne Tanksensor „0 %“, während direkt daneben korrekt „Reichweite unbekannt“ steht. |
| `audi-dashboard-app.js:496, 518, 117` | Toter Code: `fahrzeugGlyphPfade()` nie aufgerufen, `zustand.parkplatz` nie gelesen, `standortGenauigkeitM`/`standortZeit` gemappt aber ungenutzt. Kommentare behaupten das Gegenteil. |
| `audi-dashboard-app.js:595` | Einzige Interpolation im neuen Code, die ohne `esc()` in ein Attribut geht. Heute nicht ausnutzbar, weil `_zu_zahl()` im Backend nur Zahlen durchlässt — als Härtung trotzdem sinnvoll, ebenso `Number.isFinite` in `standortBekannt()`. |
---
# Dokumentation
## AGENTS.md widerspricht sich an sechs Stellen [belegt]
Die Datei ist auf 427 Zeilen gewachsen; neue Einträge wurden angehängt statt eingearbeitet.
| Stelle | Aussage | Widerspruch |
|---|---|---|
| Z. 94 | „trip detection via the iPhone WLAN sensor" | Z. 249 ff.: läuft über `ZUENDUNG_SENSOR`, WLAN „fully removed" |
| Z. 110 | „Codec JSON → MQTT/TLS → Mosquitto" | Z. 227 ff.: Schwenk auf flespi, „not MQTT at all" |
| Z. 93 | „4 modules" | tatsächlich 5 (`entitaeten.py` neu) |
| Z. 97 | „3.130 lines" | tatsächlich 3.873 |
| Z. 100 | „`einstellungen.py` — the one file edited before install" | genau das ersetzt jetzt das Setup-Menü |
| Z. 320 | `STANDORT_TRACKER` „left empty" | steht auf `device_tracker.testzone_fmm003` |
Dazu veraltete Codeverweise: `fakeTrack()` liegt bei `:421` statt `:414`, die Statistik-Behauptung
in `INSTALL.md` bei Z. 229 statt Z. 204.
Die Datei verlangt selbst „nicht festhalten, was Code und Git-History schon zeigen" (Z. 47) —
Z. 282401 sind 120 Zeilen Changelog-Prosa für fünf Einträge, und Z. 249279 dupliziert inhaltlich
Z. 133153.
## INSTALL.md [belegt]
- **Zirkuläre Schrittfolge:** Schritt 4 verweist auf „siehe Schritt 9 zuerst" (Z. 96), Schritt 7
setzt Schritt 4 voraus (Z. 176). Die Reihenfolge 4→9→7→4 wird nirgends aufgelöst.
- **Ausgelieferte Default-Entities werden verschwiegen:** `ZUENDUNG_SENSOR`, `BATTERIE_SENSOR` und
`STANDORT_TRACKER` sind mit den `testzone_fmm003`-Entities der Autoren-Instanz vorbelegt. Bei
einer Frischinstallation zeigen sie ins Leere. Z. 108110 behauptet nur für KM/TANK/RANGE
„unbelegt".
- **Override-Datei nicht benannt:** Z. 104 spricht von „einer Override-Datei" — der Pfad
`/config/audi_dashboard/entitaeten.json` steht nirgends, und ihre fehlende Sicherung (Befund 6)
ist nicht erwähnt.
- Schritt 2 verweist auf `fahrzeugprofil.example.json`, deren Z. 2 weiterhin den „WLAN-Namen"
verlangt, obwohl `fahrzeug.wlan_name` im selben Branch entfernt wurde.
## WLAN-Reste in der übrigen Doku [belegt]
Im Code ist die Entfernung vollständig (keine Fundstelle). In der Doku nicht:
`SPECIFICATION.md` (Z. 16, 147, 181, 196, 217), `homeassistant/README.md:106` und die Profilvorlage
nennen WLAN-Sensor und `TommiG1/HA_VAG-EU-Data-Act` weiterhin als aktuellen Stand.
## `update.ps1` [belegt]
Für `www/` ist das Skript nach dem Fix **vollständig**`audi-dashboard-ios.css` und `badges/`
sind ergänzt, Cache-Busting greift auch für die neue CSS-Datei.
**Verbleibende Lücke:** `homeassistant/data/shell_beleg_parser.py` ist Code, kein Datenbestand —
`belegverarbeitung.py:41` lädt ihn aus `/config/audi_dashboard/`. `update.ps1` schließt `data/`
pauschal aus, `INSTALL.md` behandelt ihn als Einmal-Kopie. **Änderungen am Beleg-Parser, dem
einzigen getesteten Teil des Repos, kämen auf keiner Instanz an.**
## `design/` [belegt]
`dm360-host.html` lädt `audi-dashboard-app.js` (Z. 12) und `audi-dashboard-ios.css` (Z. 33) — beide
existieren in `design/` nicht. Aus der Repo-Kopie ist das Board funktionslos; es läuft nur im
Claude-Design-Projekt. Nirgends dokumentiert.
`design/README.md:19` führt `audi-dashboard-ios.css` in der Inhaltstabelle des Ordners (die Datei
liegt in `homeassistant/www/`) und behauptet, die Auflage „rührt `audi-dashboard-app.js` nicht an" —
Z. 5759 derselben Datei beschreibt, dass genau dort der Stylesheet-Loader ergänzt wurde.
**Markenassets:** Der Diff fügt keine neuen Binärdateien hinzu. `DataMetric360 Board.dc.html`
verwendet die vorhandenen Audi-Type-Dateien per `@font-face` — das Lizenzrisiko bleibt unverändert,
nicht erhöht.
## `.gitignore` [belegt]
`installationspaket/` ist korrekt ergänzt und schließt nichts Getracktes aus. Es fehlt
`data/entitaeten.json` neben den anderen drei Laufzeitdateien — heute harmlos, weil die Datei nur
unter `/config/audi_dashboard/` entsteht, aber inkonsistent zum eigenen Grundsatz der Datei.
---
# Was den Branch `umsetzung-datametric360` betrifft
**Der flespi-Schwenk macht `homeassistant/FMM003_MAPPING.md` überholt.** Das Dokument beschreibt
Codec JSON über MQTT/TLS mit eigener CA und einem `mosquitto_sub`-Mitschnitt. Mit flespi läuft es
über den nativen Codec8/TCP-Kanal mit IMEI-Authentifizierung, ohne Zertifikate und ohne eingehenden
Port; die Feldzuordnung entstünde aus der flespi-REST-API. Beim Zusammenführen anpassen oder
ersetzen.
**Das CORS-Rätsel ist damit auch erklärt.** Die Notiz auf `main`, der `http:`-Block sei aus der
`configuration.yaml` entfernt worden, „nachdem HA angefangen hat, ihn zu migrieren beziehungsweise
zu ignorieren", erklärt, warum `cors_allowed_origins` in HA 2026.8 wirkungslos blieb — auch bei
gleichem Ursprung. Gehört in `homeassistant/REVERSE_PROXY.md` nachgetragen.
**Die Falle beim Zusammenführen** steht bereits in `AGENTS.md` dieses Branches: `profil_lesen()`
gibt hier bei fehlender Profildatei `None` zurück, `profil.py` ist auf `main` unverändert und geht
konfliktfrei durch — die dortigen Aufrufstellen prüfen das aber nicht.
---
# Empfehlung
Reihenfolge nach Schaden:
1. **Befund 1** (Datenverlust) — zwei Riegel, wenige Zeilen.
2. **Befunde 2 und 5** (Zurücksetzen) — ein gemeinsamer Fix in `overrides_anwenden()`.
3. **Befunde 3 und 4** (falsche Vorbelegung) — Index in den Vorschlag einbeziehen, Schwelle auf 5.
4. **Befund 6** (Sicherung) — eine Zeile in `backup.py`, plus Wiederherstellung.
5. **Befund 7** (Absicherung beim Lesen), **8** (Akku), **9/10** (Bedienbarkeit, Robustheit).
Die Punkte 1 bis 4 betreffen alle das neue Setup-Menü und lassen sich in einem Zug erledigen.
+247
View File
@@ -0,0 +1,247 @@
# DataMetric360 als native Home-Assistant-Integration
**Stand: 2026-08-13.** Vorhaben: Das HA-Backend von „pyscript-Skripte plus Dateien kopieren" auf
eine **eigene Integration mit Konfigurationsdialog** umstellen — Einrichtung vollständig über die
Oberfläche, kein YAML, Updates als Knopfdruck in HAs eigener Update-Liste.
Alle unten genannten Schnittstellen sind **an der laufenden Instanz geprüft** (Home Assistant
2026.8.1 im Container `audi_ha_test`), nicht aus der Dokumentation abgeschrieben. Die geprüften
Signaturen stehen jeweils dabei.
Verwandte Dokumente: `homeassistant/INSTALL.md` (heutiger, manueller Weg — bleibt gültig, bis diese
Umstellung steht), `UMSETZUNGSPLAN.md`, `REVIEW_main_2026-08-13.md` (die Fehler, die hier
konstruktionsbedingt verschwinden).
---
## 1. Was heute nötig ist — und was davon bleibt
| Heute | Nach der Umstellung |
|---|---|
| HACS installieren, darüber pyscript | entfällt |
| drei Ordner nach `/config/` kopieren (`pyscript/`, `data/`, `www/`) | einmalig **ein** Ordner nach `custom_components/` |
| zwei Blöcke in `configuration.yaml` einfügen | entfällt vollständig |
| Entity-IDs zuordnen | bleibt, aber als HA-Konfigurationsdialog statt als selbstgebautes Menü |
| langlebiges Token in `audi_dashboard/ha_token.txt` ablegen | **entfällt ersatzlos** |
| `pip install pypdf` von Hand | entfällt (`manifest.json``requirements`) |
| Neustart | einmalig, wie bei jeder Integration |
| Updates über `update.ps1` per Samba | Knopfdruck in *Einstellungen → System → Updates* |
## 2. Die Bausteine, die HA dafür mitbringt
Alle in 2026.8.1 vorhanden und geprüft:
| Baustein | Ersetzt |
|---|---|
| `EntitySelector` mit `domain`, `device_class`, `multiple`, `reorder`, `exclude_entities` | das gesamte Setup-Menü (`entitaeten.py` + ~800 Zeilen JS) |
| `panel_custom.async_register_panel(...)` | den `panel_custom`-Block in `configuration.yaml` |
| `hass.http.async_register_static_paths([StaticPathConfig(url_path, path, cache_headers)])` | Kopieren nach `/config/www/` samt Cache-Busting-Stub |
| `ConfigEntry.add_update_listener(...)` | den „wirkt erst nach Neustart"-Hinweis bei den drei triggergebundenen Feldern |
| `async_track_state_change_event` / `async_track_time_interval` / `async_track_time_change` | `@state_trigger` / `@time_trigger("period")` / `@time_trigger("cron")` |
| `recorder.history.get_significant_states(...)` | den HTTP-Aufruf auf `/api/history/period` **samt Token-Datei** |
| `hass.async_add_executor_job(...)` | `task.executor(io.open, ...)` |
| `Store` (`homeassistant.helpers.storage`) | handgeschriebenes JSON-I/O (optional, siehe §6) |
| `HomeAssistantView` / `websocket_api.async_register_command` | die 16-KB-Attributgrenze für Fahrten und Tankvorgänge |
| `UpdateEntity` (`INSTALL`, `PROGRESS`, `BACKUP`, `RELEASE_NOTES`) | `updateverwaltung.py` als sichtbare Update-Entität |
Der wichtigste Einzelfund: **`EntitySelector` kann `exclude_entities`.** Die Duplikat-Prüfung wäre
damit keine wegklickbare Warnung mehr, sondern schlicht nicht anwählbar.
## 3. Aufbau
```
custom_components/datametric360/
├── manifest.json Abhängigkeiten, "config_flow": true, Version
├── const.py Rollen-Tabelle (heute FELDER in entitaeten.py)
├── config_flow.py Ersteinrichtung + Optionen (die 17 Sensor-Rollen)
├── __init__.py Dienste, Horcher, Zeitgeber, Panel, statische Dateien
├── update.py UpdateEntity gegen das eigene Gitea
├── api.py HomeAssistantView für die Companion-App (später)
└── www/ audi-dashboard-app.js, .css, badges/ — mitgeliefert
```
### Konfigurationsdialog
Statt einer eigenen Katalogverwaltung reicht ein Schema. Skizze für zwei der 17 Rollen:
```python
vol.Schema({
vol.Required("zuendung_sensor"): EntitySelector(
EntitySelectorConfig(domain=["binary_sensor"])
),
vol.Optional("tuer_sensoren", default=[]): EntitySelector(
EntitySelectorConfig(domain=["binary_sensor"], device_class=["door"],
multiple=True, reorder=True)
),
})
```
Suche, Übersetzung, Gerätezuordnung und Mehrfachauswahl kommen von HA. Die vier Türpositionen
werden zu einer sortierbaren Mehrfachauswahl — die Reihenfolge ersetzt die heutigen festen
Positionsfelder.
### Panel und Dateien
```python
await hass.http.async_register_static_paths([
StaticPathConfig("/datametric360", hass.config.path("custom_components/datametric360/www"),
cache_headers=False),
])
panel_custom.async_register_panel(
hass, frontend_url_path="datametric360", webcomponent_name="audi-dashboard-panel",
sidebar_title="Mein Audi", sidebar_icon="mdi:car-sports",
module_url=f"/datametric360/audi-dashboard-app.js?v={VERSION}",
)
```
Die Versionsnummer in der URL macht den Lade-Stub `audi-dashboard-panel.js` und
`audi-dashboard-version.json` überflüssig — das Cache-Problem löst sich, weil die Integration ihre
eigene Version kennt.
**Das Frontend selbst bleibt unangetastet.** Alle 3.873 Zeilen `audi-dashboard-app.js` laufen
weiter; nur das Setup-Menü darin wird überflüssig.
## 4. Übersetzung der pyscript-Eigenheiten
Mechanisch, kein Neuentwurf. Die Fachlogik — Fahrterkennung, Tankerkennung, Beleg-Parser,
Reifenzähler, Prognose — geht nahezu unverändert mit.
| pyscript | Integration |
|---|---|
| `@service def audi_dashboard_x()` | `hass.services.async_register(DOMAIN, "x", handler, schema)` |
| `@state_trigger(f"{SENSOR}")` | `async_track_state_change_event(hass, [sensor], handler)` |
| `@time_trigger("period(now, 20 seconds)")` | `async_track_time_interval(hass, handler, timedelta(seconds=20))` |
| `@time_trigger("cron(0 4 * * *)")` | `async_track_time_change(hass, handler, hour=4, minute=0, second=0)` |
| `task.unique()` + `task.sleep()` | `asyncio.Task` merken und bei Bedarf `cancel()` |
| `task.executor(io.open, …)` | `hass.async_add_executor_job(...)` |
| `state.set("pyscript.x", …)` | eigene Entität oder `HomeAssistantView` (siehe §6) |
| `subprocess.run(shell_beleg_parser.py)` | `pypdf` direkt importieren, in einem Executor-Job |
| `urllib.request` auf `/api/history/period` | `recorder.history.get_significant_states(...)` |
Die letzte Zeile ist die wichtigste: Sie beseitigt **zwei** Befunde auf einmal — den blockierenden
Aufruf, den HA seit 2026.8 abbricht (siehe `REVIEW_main_2026-08-13.md`, und derselbe Fehler wurde
im Branch `umsetzung-datametric360` bereits behoben), **und** die Token-Datei im Klartext.
## 5. Installation und Updates
Hier liegt der eigentliche Knackpunkt, deshalb ausführlich.
### HACS scheidet aus
HACS spricht ausschließlich mit GitHub. Anfragen, auch GitLab, Gitea oder Codeberg zuzulassen, gibt
es seit Jahren; umgesetzt sind sie nicht. Das Repository liegt auf `gitea.nothaft.cloud` — HACS ist
damit kein Weg, außer man spiegelt nach GitHub.
### „App" ist der falsche Behälter
App-Repositories (früher Add-ons) akzeptieren **beliebige Git-URLs**, auch selbst gehostete; nötig
ist nur eine `repository.yaml` im Wurzelverzeichnis. Das ist die einzige Stelle, an der Gitea
nativ mitspielen würde.
Inhaltlich passt es trotzdem nicht: Apps sind **Docker-Container** neben Home Assistant. Eine
Integration muss *im* HA-Prozess laufen, um Entitäten anzulegen, Dienste zu registrieren und ein
Panel einzuhängen. Eine App kann das nicht.
*(Es gäbe den Trick, eine winzige App zu bauen, die nichts weiter tut, als die Integration nach
`custom_components/` zu schreiben — dann käme auch die Erstinstallation aus dem eigenen Gitea. Ein
Docker-Container für einen einmaligen Kopiervorgang ist das aus meiner Sicht nicht wert; der
Vollständigkeit halber notiert.)*
### Der native Weg: eigene UpdateEntity
`UpdateEntity` lässt die Integration ihre Updates selbst melden — sichtbar in **Einstellungen →
System → Updates**, derselben Liste wie HA-Kern und Apps. Geprüfte Möglichkeiten:
```
UpdateEntityFeature: INSTALL, SPECIFIC_VERSION, PROGRESS, BACKUP, RELEASE_NOTES
async_install(version: str | None, backup: bool, **kwargs) -> None
```
`BACKUP` gibt es genau für das, was `updateverwaltung.py` heute von Hand macht: vor dem Einspielen
den alten Code sichern.
**Die Logik ist bereits vorhanden.** `updateverwaltung.py` klont flach, vergleicht Versionen,
sichert nach `code_backups/` und ersetzt die Dateien. Das wandert nahezu unverändert in
`async_install()`. Neu sind nur zwei Eigenschaften:
```python
@property
def installed_version(self) -> str: return VERSION # aus manifest.json
@property
def latest_version(self) -> str | None: return self._neueste # aus Gitea
```
Für `latest_version` empfiehlt sich statt der heutigen Zeitstempel-Zahl aus
`audi-dashboard-version.json` ein **Git-Tag**. Gitea bietet eine GitHub-kompatible Schnittstelle:
```
GET https://gitea.nothaft.cloud/api/v1/repos/paul/audi-app/releases/latest
```
Ein einzelner HTTP-Aufruf, kein Klonen. In HA steht dann „1.4.0 → 1.5.0" statt einer nichtssagenden
Zahl, und die Release-Beschreibung lässt sich über `release_notes` direkt anzeigen.
### Was bleibt
**Die Erstinstallation bleibt ein einmaliges Kopieren** des Ordners nach `custom_components/`. Für
Integrationen aus Nicht-GitHub-Quellen gibt es keinen nativen Installationsweg. Jedes weitere
Update ist danach ein Knopfdruck.
## 6. Datenhaltung — bewusst zu entscheiden
Zwei Möglichkeiten, beide vertretbar:
- **`Store`** (`.storage/datametric360`): atomar, versioniert, mit Migrationspfad. Nachteil: für
Menschen nicht mehr lesbar, und die heutige Sicherung über `backup.py` müsste neu gedacht werden.
- **JSON-Dateien behalten** unter `/config/audi_dashboard/`, gelesen über Executor-Jobs. Nachteil:
kein Migrationspfad. Vorteil: Fahrten und Tankvorgänge bleiben von Hand einsehbar und mit den
bestehenden Sicherungen kompatibel.
**Empfehlung:** Dateien behalten. Bei einem Fahrzeugprofil, einem Fahrtenbuch und einer
Tankhistorie ist Lesbarkeit mehr wert als Migrationskomfort — und die vorhandene Sicherungslogik
bleibt gültig.
Unabhängig davon: Fahrten und Tankvorgänge gehören **nicht** in Entitäts-Attribute (16-KB-Grenze,
siehe `frontend_veroeffentlichung.py`), sondern hinter einen `HomeAssistantView`. Das ist zugleich
die saubere Schnittstelle für die Companion-App, die heute Attribut-Blobs auslesen muss.
## 7. Was dabei konstruktionsbedingt verschwindet
Aus `REVIEW_main_2026-08-13.md`:
- **Befund 1** (Speichern mit leerem Katalog löscht alle Zuordnungen) — der Konfigurationsdialog
validiert serverseitig, ein leeres Formular kommt gar nicht durch.
- **Befunde 2 und 5** (Zurücksetzen wirkungslos bzw. zu stark) — die `setattr`-Override-Ebene
entfällt komplett; HA verwaltet Optionen selbst.
- **Befunde 3 und 4** (falsche Vorbelegung, beliebige Sensoren) — HAs Entity-Picker schlägt nichts
vor, der Nutzer wählt; `exclude_entities` verhindert Doppelbelegung.
- **Befund 7** (kaputte `entitaeten.json` legt das Panel lahm) — die Datei existiert nicht mehr.
- Der Neustart-Zwang bei den drei triggergebundenen Feldern — `add_update_listener` lädt die
Integration neu und registriert die Horcher dabei mit den neuen Entity-IDs.
Nicht gelöst werden dadurch: die Frontend-Befunde (Standort-Dauerfeuer, Barrierefreiheit,
`.navmarke`) — das Frontend bleibt ja unverändert.
## 8. Aufwand und Reihenfolge
Realistisch einige Tage, nicht Wochen — der Großteil ist Umschreiben von Dekoratoren.
1. **Zuerst die drei Setup-Fehler roh fixen** (etwa eine Stunde). Sonst läuft die Instanz bis zur
Umstellung mit einem Datenverlust-Pfad.
2. **Gerüst aufsetzen:** `manifest.json`, `config_flow.py`, Panel- und Dateiregistrierung. Die
pyscript-Skripte laufen dabei zunächst unverändert weiter.
3. **Logik Datei für Datei herüberziehen**, beginnend mit `fahrtabschluss_logik.py` — dort
verschwinden Token und blockierender Aufruf zuerst.
4. **`update.py`** gegen das Gitea-Repository.
5. **`api.py`** für die Companion-App, sobald diese so weit ist.
### Was dabei zu beachten ist
- **Neuladen im laufenden Betrieb entfällt.** pyscript lädt bei jeder Dateiänderung neu; eine
Integration braucht einen Neustart oder ein Neuladen. Beim Entwickeln spürbar, im Betrieb egal.
- **Die Companion-App liest heute `pyscript.audi_dashboard_*`.** Werden daraus eigene Entitäten
oder ein eigener Endpunkt, ändert sich die Tabelle `ENTITAETEN` in
`companion-app/src/api/types.ts` — eine Datei, überschaubar.
- **`shell_beleg_parser.py` wandert mit in die Integration.** Heute liegt er unter
`/config/audi_dashboard/` und wird von keinem Update-Pfad ausgeliefert (siehe Review) — als Teil
des Integrationsordners löst sich das mit.