# 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. --- > ## ✅ Umgesetzt am 2026-08-13 > > Alle Befunde dieses Dokuments sind behoben — siehe die fünf Commits von > `Setup-Zuordnung: Zuruecksetzen wirkt…` bis `Kleinere Befunde und Dokumentation…`. > Das Dokument bleibt als Befundprotokoll stehen: es begründet, warum die > Änderungen so aussehen, wie sie aussehen. > > **Zwei Punkte wurden bewusst anders gelöst als hier vorgeschlagen:** > > 1. *Karte nur bei echtem Ansichtswechsel neu aufbauen* — nicht umgesetzt. `render()` ersetzt den > Inhalt per `innerHTML`; eine überlebende Leaflet-Instanz zeigte danach auf ein abgehängtes > Element und bliebe leer. Der Neuaufbau hängt am 20-Sekunden-Takt des Backends und ließe sich > nur durch einen Umbau der Render-Architektur ändern. Die Begründung steht im Code. > Behoben ist dagegen die Standortabfrage, die tatsächlich ungedrosselt lief. > 2. *`SPECIFICATION.md`* wurde nicht in Teilen umgeschrieben, sondern hat einen datierten > Statushinweis am Anfang bekommen. Ein Umschreiben der §4/§5-Tabellen hätte neue > Ungenauigkeiten riskiert. > > **Drei Fehler kamen beim Umsetzen dazu**, sichtbar erst im laufenden Panel: ein unlesbares Datum > ergab „NaN/N" statt eines Strichs, ein fehlender Tanksensor „0 %" statt „unbekannt", und neben dem > Typenschild stand die Ausführung doppelt. Alle drei sind mit behoben. > > Und ein pyscript-Fallstrick ist dabei aufgefallen und im Code vermerkt: **Generatorausdrücke sind > nicht implementiert** (`not implemented ast ast_generatorexp`), Mengen-, Listen- und > Dict-Comprehensions dagegen schon. --- ## 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 `