From 12418a3d52c85551092a961067a9d4fe60afd99a Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Thu, 13 Aug 2026 13:43:25 +0200 Subject: [PATCH] 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. --- REVIEW_main_2026-08-13.md | 391 ++++++++++++++++++++++++++++++++++++++ ha_install.md | 247 ++++++++++++++++++++++++ 2 files changed, 638 insertions(+) create mode 100644 REVIEW_main_2026-08-13.md create mode 100644 ha_install.md diff --git a/REVIEW_main_2026-08-13.md b/REVIEW_main_2026-08-13.md new file mode 100644 index 0000000..c78eb9e --- /dev/null +++ b/REVIEW_main_2026-08-13.md @@ -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 `