diff --git a/homeassistant/www/audi-dashboard-app.js b/homeassistant/www/audi-dashboard-app.js index 3cdd77b..ee0df3e 100644 --- a/homeassistant/www/audi-dashboard-app.js +++ b/homeassistant/www/audi-dashboard-app.js @@ -1966,24 +1966,56 @@ function entitaetKandidaten(feldKey, suchtext, aktuellerWert) { return liste.slice(0, 60); } +/* Ab diesem Wert gilt ein Vorschlag als belastbar. entitaetScore() vergibt für + einen reinen Domain-Treffer bereits 3 Punkte - eine Schwelle von 3 wäre + deshalb immer erfüllt und hätte jedes leere Feld mit dem erstbesten Sensor + der passenden Domain belegt (Reihenfolge aus Object.keys(HASS.states), also + willkürlich). 5 verlangt mindestens einen Stichworttreffer. */ +const SETUP_VORSCHLAG_SCHWELLE = 5; + +/* Bester Vorschlag für ein Feld, der noch nicht anderweitig vergeben ist. + Das "noch nicht vergeben" ist der Punkt: ohne diese Prüfung bekamen alle + vier Positionen einer Liste (Türen, Fenster, Schlösser) denselben Sensor, + weil der Vorschlag nicht vom Index abhängt. Der Sicherheitscheck hätte + danach viermal dieselbe Tür geprüft und "Sicher abgestellt" gemeldet, + obwohl drei Türen nie geprüft wurden. */ +function setupVorschlag(feld, schonVergeben) { + for (const kandidat of entitaetKandidaten(feld.key, "", "")) { + if (schonVergeben.has(kandidat.id)) continue; + if (entitaetScore(feld, kandidat, "") < SETUP_VORSCHLAG_SCHWELLE) return ""; + return kandidat.id; + } + return ""; +} + function setupOeffnen() { + if (!setupBereit()) return; ALLE_ENTITAETEN = alleEntitaeten(); setupNurPassend = true; const werte = (ENTITAETEN && ENTITAETEN.werte) || {}; const zuordnung = {}; + /* Bereits gesetzte und bereits vorgeschlagene IDs, damit kein Sensor + zweimal landet. Vorbelegte Werte kommen zuerst hinein. */ + const vergeben = new Set(); + for (const feld of setupKatalog()) { + const vorhandeneWerte = feld.liste ? (werte[feld.key] || []) : [werte[feld.key]]; + for (const id of vorhandeneWerte) if (id) vergeben.add(id); + } for (const feld of setupKatalog()) { if (feld.liste) { zuordnung[feld.key] = feld.positionen.map((_, i) => { const vorhanden = (werte[feld.key] || [])[i]; if (vorhanden) return vorhanden; - const vorschlag = entitaetKandidaten(feld.key, "", "")[0]; - return vorschlag && entitaetScore(feld, vorschlag, "") >= 3 ? vorschlag.id : ""; + const vorschlag = setupVorschlag(feld, vergeben); + if (vorschlag) vergeben.add(vorschlag); + return vorschlag; }); } else { const vorhanden = werte[feld.key]; if (vorhanden) { zuordnung[feld.key] = vorhanden; continue; } - const vorschlag = entitaetKandidaten(feld.key, "", "")[0]; - zuordnung[feld.key] = vorschlag && entitaetScore(feld, vorschlag, "") >= 3 ? vorschlag.id : ""; + const vorschlag = setupVorschlag(feld, vergeben); + if (vorschlag) vergeben.add(vorschlag); + zuordnung[feld.key] = vorschlag; } } setupZuordnung = zuordnung; @@ -1991,6 +2023,19 @@ function setupOeffnen() { setupSucheOffen = null; setupSuchtext = ""; setupSpeichertLaeuft = false; setupOffen = true; render(); } + +/* Das Setup lässt sich erst öffnen, wenn der Katalog wirklich da ist. + Ohne diese Prüfung öffnete sich das Fenster leer (setupKatalog() liefert + dann []), und ein Klick auf "Speichern" schrieb {} - was entitaeten.json + vollständig ersetzt und damit ALLE Zuordnungen löscht. Weil die Werte im + laufenden Prozess erhalten bleiben, wäre das erst beim nächsten Neustart + aufgefallen. */ +function setupBereit() { + if (setupKatalog().length) return true; + hinweis("Setup noch nicht bereit", + "Die Liste der Sensor-Rollen wurde noch nicht vom Server geladen. Nach einem Neustart dauert das einen Moment - bitte gleich noch einmal versuchen."); + return false; +} function setupSchliessen() { setupOffen = false; setupSucheOffen = null; setupZuordnung = null; setupZuordnungStart = null; render(); } @@ -2025,6 +2070,13 @@ function setupGeaenderteTriggerFelder() { } function setupSpeichernAusfuehren() { + // Zweiter Riegel gegen den Datenverlust-Pfad: selbst wenn das Fenster auf + // irgendeinem Weg leer aufgeht, wird nichts geschrieben. overrides_schreiben() + // ersetzt die Datei vollstaendig - ein leeres Objekt loescht alles. + if (!setupZuordnung || !Object.keys(setupZuordnung).length) { + hinweis("Nichts zu speichern", "Es ist keine Zuordnung geladen. Bitte das Setup schliessen und neu oeffnen."); + return; + } setupSpeichertLaeuft = true; render(); (async () => { try { @@ -3707,6 +3759,9 @@ function nachladeAnstossen() { NACHGELADEN.tank = finden("pyscript.audi_dashboard_tankvorgaenge") || NACHGELADEN.tank; NACHGELADEN.status = finden("pyscript.audi_dashboard_fahrzeugstatus") || NACHGELADEN.status; NACHGELADEN.batt = finden("pyscript.audi_dashboard_batterieverlauf") || NACHGELADEN.batt; + // Ohne diese Zeile bleibt der Setup-Katalog nach einem HA-Neustart leer, + // bis zufaellig ein Push kommt - siehe setupBereit(). + NACHGELADEN.entitaeten = finden("pyscript.audi_dashboard_entitaeten") || NACHGELADEN.entitaeten; } catch (e) { // wird beim naechsten Versuch erneut probiert } @@ -3725,7 +3780,7 @@ async function datenLaden(erzwingen) { // Unabhängig vom geaendert-Block unten gelesen (der trackt nur profil/ // fahrten/tank/status/batt) - ändert sich erst nach einem Setup-Speichern, // nicht Teil des normalen Update-Rhythmus. - const entitaetenState = HASS.states["pyscript.audi_dashboard_entitaeten"]; + const entitaetenState = HASS.states["pyscript.audi_dashboard_entitaeten"] || NACHGELADEN.entitaeten; if (entitaetenState && entitaetenState.attributes) ENTITAETEN = entitaetenState.attributes.daten; if (belegState && belegState !== letzterBelegState) {