Setup-Menü für Sensor-Zuordnung + Umstellung von WLAN/VAG-Integration auf FMM003
Setup-Menü (Einstellungen -> Fahrzeug einrichten -> Setup): ordnet alle von der App genutzten Sensor-Rollen echten HA-Entitäten zu, statt sie in einstellungen.py von Hand einzutragen - durchsuchbares Dropdown je Feld, Vorauswahl aus vorhandenen Entitäten, Schalter für "nur passende Sensoren". Neues Modul entitaeten.py (Katalog + JSON-Override, zur Laufzeit über setattr() auf einstellungen angewendet, kein Neustart nötig außer für die drei trigger-gebundenen Felder). Zusätzlich: Fahrzeug-WLAN-Erkennung und alle Entity-IDs der nicht mehr genutzten TommiG1/HA_VAG-EU-Data-Act-Integration entfernt. Fahrterkennung läuft jetzt über den Zündungs-/ACC-Status des neu angebundenen Teltonika FMM003 (ersetzt die WLAN-Verbindungserkennung); Standort und 12V- Batteriespannung kommen ebenfalls vom FMM003. Kilometerstand, Tankfüllstand, Reichweite, Türen/Fenster/Schlösser sowie Ölwechsel-/Inspektionsdaten haben dadurch vorerst keine Quelle mehr und zeigen "unbekannt" - die Funktionen selbst bleiben erhalten und lassen sich über das neue Setup-Menü jederzeit neu zuordnen. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -37,6 +37,7 @@ let CAR = null;
|
||||
let TRIPS = [];
|
||||
let FILLS = [];
|
||||
let BATTERIEVERLAUF = [];
|
||||
let ENTITAETEN = null; // {katalog, werte} aus pyscript.audi_dashboard_entitaeten (Setup-Menü)
|
||||
let DATEN_GELADEN = false;
|
||||
// Zeitpunkt (ms) des letzten echten Fahrzeugstatus-Updates vom Backend -
|
||||
// null, solange keiner bekannt ist (siehe standAlterText()).
|
||||
@@ -88,7 +89,6 @@ function profilZuConfig(p) {
|
||||
nachtBis: p.einstellungen.nacht_bis,
|
||||
pauseMin: p.einstellungen.fahrten_pausenzeit_min,
|
||||
markenname: p.einstellungen.kraftstoffanbieter,
|
||||
wlanName: p.fahrzeug.wlan_name,
|
||||
backupIntervall: p.einstellungen.backup_intervall || "aus",
|
||||
letztesBackup: p.einstellungen.letztes_backup || null,
|
||||
ausfuehrung: p.fahrzeug.ausfuehrung || "",
|
||||
@@ -156,7 +156,6 @@ function configCarZuProfil(vorherigesProfil) {
|
||||
p.fahrzeug.zusatz = CONFIG.fahrzeugzusatz;
|
||||
p.fahrzeug.untertitel = CONFIG.fahrzeuguntertitel;
|
||||
p.fahrzeug.kennzeichen = CONFIG.kennzeichen;
|
||||
p.fahrzeug.wlan_name = CONFIG.wlanName;
|
||||
p.fahrzeug.hauptuntersuchung_faellig = CAR.hu;
|
||||
p.fahrzeug.fin = CAR.fin;
|
||||
p.fahrzeug.erstzulassung = CAR.erstzulassung;
|
||||
@@ -1754,10 +1753,8 @@ function vEinst() {
|
||||
<input id="einKennzeichen" value="${esc(CONFIG.kennzeichen || "")}" data-einrichten="kennzeichen"></div>
|
||||
<div class="feld"><label for="einErstzulassung">Erstzulassung</label>
|
||||
<input id="einErstzulassung" value="${esc(CAR.erstzulassung || "")}" data-einrichten="erstzulassung" placeholder="MM/JJJJ"></div>
|
||||
<div class="feld"><label for="einAusfuehrung">Ausführung</label>
|
||||
<div class="feld" style="border-bottom:0"><label for="einAusfuehrung">Ausführung</label>
|
||||
<input id="einAusfuehrung" value="${esc(CONFIG.ausfuehrung || "")}" data-einrichten="ausfuehrung"></div>
|
||||
<div class="feld" style="border-bottom:0"><label for="einWlan">Fahrzeug-WLAN (SSID)</label>
|
||||
<input id="einWlan" value="${esc(CONFIG.wlanName || "")}" data-einrichten="wlan" placeholder="z. B. Audi_MMI_XXXX_5GHz"></div>
|
||||
<span class="label" style="margin-top:20px">Ölwechsel-Intervall</span>
|
||||
<div class="seg" style="margin-top:8px">
|
||||
<button data-oel="hersteller" class="${CAR.oel.modus === "hersteller" ? "on" : ""}">Herstellervorgabe</button>
|
||||
@@ -1773,8 +1770,9 @@ function vEinst() {
|
||||
<select id="oelKm" data-oelkm>${[5000, 10000, 15000, 20000].map((k) => `<option value="${k}" ${k === CAR.oel.km ? "selected" : ""}>${de(k)} km</option>`).join("")}</select></div>
|
||||
<div class="feld" style="border-bottom:0"><label for="oelM">Nach Zeit</label>
|
||||
<select id="oelM" data-oelm>${[12, 24].map((m) => `<option value="${m}" ${m === CAR.oel.monate ? "selected" : ""}>${m === 12 ? "1 Jahr" : "2 Jahre"}</option>`).join("")}</select></div>`}
|
||||
<button class="aktion" style="margin-top:20px" data-setup-oeffnen>Setup — Sensoren zuordnen</button>
|
||||
` : ""}
|
||||
<button class="aktion ${einrichtenOffen ? "primaer" : ""}" data-einrichten-auf style="margin-top:16px">${einrichtenOffen ? "Fertig" : "Einrichten"}</button>
|
||||
<button class="aktion ${einrichtenOffen ? "primaer" : ""}" style="margin-top:16px" data-einrichten-auf>${einrichtenOffen ? "Fertig" : "Einrichten"}</button>
|
||||
</div>
|
||||
<div class="tile"><span class="label">Darstellung</span>
|
||||
<div class="seg">
|
||||
@@ -1906,6 +1904,94 @@ let nmOffen = false;
|
||||
let einrichtenOffen = false;
|
||||
let bildMenuOffen = null;
|
||||
let smartdealPopupOffen = false;
|
||||
|
||||
/* --------------------------------------------------------- Setup-Popup
|
||||
Sensor-zu-Entität-Zuordnung (Einstellungen -> Fahrzeug einrichten ->
|
||||
Setup). setupZuordnung ist eine Arbeitskopie, die erst bei "Speichern"
|
||||
an den Service geht - Abbrechen verwirft sie einfach ohne Nebeneffekt. */
|
||||
let setupOffen = false;
|
||||
let setupZuordnung = null; // {KEY: "entity.id"} bzw. {KEY: [4 IDs]} für Listenfelder
|
||||
let setupNurPassend = true; // globaler Schalter: Kandidaten auf passende Domain/Einheit einschränken
|
||||
let setupSucheOffen = null; // Feld-Id ("KEY" oder "KEY:idx") des offenen Dropdowns, sonst null
|
||||
let setupSuchtext = "";
|
||||
let setupSpeichertLaeuft = false;
|
||||
let ALLE_ENTITAETEN = [];
|
||||
|
||||
function setupKatalog() { return (ENTITAETEN && ENTITAETEN.katalog) || []; }
|
||||
|
||||
function alleEntitaeten() {
|
||||
if (!HASS || !HASS.states) return [];
|
||||
return Object.keys(HASS.states).map((id) => {
|
||||
const attrs = (HASS.states[id] && HASS.states[id].attributes) || {};
|
||||
return { id, name: attrs.friendly_name || id, domain: id.split(".")[0], unit: attrs.unit_of_measurement || "", deviceClass: attrs.device_class || "" };
|
||||
});
|
||||
}
|
||||
|
||||
function entitaetName(id) {
|
||||
if (!id) return "";
|
||||
const treffer = ALLE_ENTITAETEN.find((e) => e.id === id);
|
||||
return treffer ? treffer.name : id;
|
||||
}
|
||||
|
||||
/* Höher = besser passend: Stichwort- und Freitext-Treffer zählen am meisten,
|
||||
Domain/Einheit/Device-Class sind Zusatzpunkte fürs Sortieren, wenn der
|
||||
Schalter "Nur passende Sensoren" aus ist (dann filtern sie nicht hart,
|
||||
siehe entitaetKandidaten()). */
|
||||
function entitaetScore(feld, e, suchtext) {
|
||||
let score = 0;
|
||||
const hay = (e.id + " " + e.name).toLowerCase();
|
||||
for (const wort of feld.stichworte || []) if (hay.includes(wort)) score += 5;
|
||||
if (feld.domains && feld.domains.includes(e.domain)) score += 3;
|
||||
if (feld.device_classes && feld.device_classes.length && feld.device_classes.includes(e.deviceClass)) score += 3;
|
||||
if (feld.units && feld.units.length && feld.units.includes(e.unit)) score += 2;
|
||||
if (suchtext && hay.includes(suchtext.trim().toLowerCase())) score += 10;
|
||||
return score;
|
||||
}
|
||||
|
||||
function entitaetKandidaten(feldKey, suchtext, aktuellerWert) {
|
||||
const feld = setupKatalog().find((f) => f.key === feldKey);
|
||||
if (!feld) return [];
|
||||
const text = (suchtext || "").trim().toLowerCase();
|
||||
const liste = ALLE_ENTITAETEN.filter((e) => {
|
||||
if (e.id === aktuellerWert) return true;
|
||||
if (setupNurPassend) {
|
||||
if (feld.domains && feld.domains.length && !feld.domains.includes(e.domain)) return false;
|
||||
if (feld.units && feld.units.length && !feld.units.includes(e.unit)) return false;
|
||||
}
|
||||
if (text && !(e.id.toLowerCase().includes(text) || e.name.toLowerCase().includes(text))) return false;
|
||||
return true;
|
||||
});
|
||||
liste.sort((a, b) => entitaetScore(feld, b, suchtext) - entitaetScore(feld, a, suchtext));
|
||||
return liste.slice(0, 60);
|
||||
}
|
||||
|
||||
function setupOeffnen() {
|
||||
ALLE_ENTITAETEN = alleEntitaeten();
|
||||
setupNurPassend = true;
|
||||
const werte = (ENTITAETEN && ENTITAETEN.werte) || {};
|
||||
const zuordnung = {};
|
||||
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 : "";
|
||||
});
|
||||
} 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 : "";
|
||||
}
|
||||
}
|
||||
setupZuordnung = zuordnung;
|
||||
setupSucheOffen = null; setupSuchtext = ""; setupSpeichertLaeuft = false;
|
||||
setupOffen = true; render();
|
||||
}
|
||||
function setupSchliessen() {
|
||||
setupOffen = false; setupSucheOffen = null; setupZuordnung = null; render();
|
||||
}
|
||||
function vReifen() {
|
||||
const r = CAR.reifen;
|
||||
const feld = (k, schluessel, bez, typ) => {
|
||||
@@ -1994,10 +2080,11 @@ function vTripsFormular() {
|
||||
function vTrips() {
|
||||
const jahre = [...new Set(TRIPS.map((t) => new Date(t.ts_start).getFullYear()))].sort((a, b) => b - a);
|
||||
/* D5: Leerzustand nennt die Ursache - die Fahrtenerkennung greift erst, wenn
|
||||
das Telefon einmal im Fahrzeug-WLAN war. */
|
||||
die Zündungs-Entität (Setup -> Für die Fahrterkennung nötig) erkennt,
|
||||
dass der Motor läuft. */
|
||||
if (!jahre.length) return `<div class="tile"><div class="leer">
|
||||
<strong>Noch keine Fahrten</strong>
|
||||
<p>Fahrten werden erkannt, sobald sich das Telefon mit dem WLAN des Fahrzeugs verbindet (${esc(CONFIG.wlanName || "noch nicht eingerichtet")}). Bis dahin lässt sich jede Fahrt von Hand erfassen.</p>
|
||||
<p>Fahrten werden erkannt, sobald der Motor gestartet wird. Bis dahin lässt sich jede Fahrt von Hand erfassen.</p>
|
||||
</div></div>
|
||||
${vTripsFormular()}`;
|
||||
let h = `<div class="tile">`;
|
||||
@@ -2546,6 +2633,89 @@ function sheetMarkup() {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ Setup-Popup */
|
||||
const LUPE_SVG = `<svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><circle cx="8.5" cy="8.5" r="6"/><path d="m17 17-4.3-4.3"/></svg>`;
|
||||
|
||||
function entitaetZeilenMarkup(feldId, key, suchtext, aktuellerWert) {
|
||||
const kandidaten = entitaetKandidaten(key, suchtext, aktuellerWert);
|
||||
if (!kandidaten.length) return `<div class="entitaet-leer">Keine passende Entität gefunden</div>`;
|
||||
return kandidaten.map((e) => `
|
||||
<button type="button" class="entitaet-eintrag ${e.id === aktuellerWert ? "aktiv" : ""}" data-setup-wahl="${esc(feldId)}" data-setup-entity="${esc(e.id)}">
|
||||
<span class="entitaet-eintrag-name">${esc(e.name)}</span>
|
||||
<span class="entitaet-eintrag-id">${esc(e.id)}${e.unit ? " · " + esc(e.unit) : ""}</span>
|
||||
</button>`).join("");
|
||||
}
|
||||
|
||||
function entitaetCombo(key, idx) {
|
||||
const feldId = idx == null ? key : `${key}:${idx}`;
|
||||
const wert = idx == null ? setupZuordnung[key] : (setupZuordnung[key] || [])[idx];
|
||||
const offen = setupSucheOffen === feldId;
|
||||
const anzeige = offen ? setupSuchtext : entitaetName(wert);
|
||||
return `<div class="entitaet-combo">
|
||||
<span class="entitaet-combo-icon">${LUPE_SVG}</span>
|
||||
<input type="text" class="entitaet-combo-input" autocomplete="off" spellcheck="false"
|
||||
placeholder="Entität suchen …" value="${esc(anzeige)}" data-setup-suche="${esc(feldId)}">
|
||||
${offen ? `<div class="entitaet-liste" data-setup-liste="${esc(feldId)}">${entitaetZeilenMarkup(feldId, key, setupSuchtext, wert)}</div>` : ""}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function setupFeldzeile(feld) {
|
||||
const kopf = `<div class="setup-feld-kopf">
|
||||
<span class="setup-feld-label">${esc(feld.label)}</span>
|
||||
<span class="setup-feld-hinweis">${esc(feld.hinweis)}</span>
|
||||
${feld.trigger_gebunden ? `<span class="setup-feld-neustart">Wirkt nach dem Speichern erst nach einem Neustart von Home Assistant.</span>` : ""}
|
||||
</div>`;
|
||||
if (feld.liste) {
|
||||
return `<div class="setup-feldzeile">${kopf}
|
||||
<div class="setup-unterfelder">
|
||||
${feld.positionen.map((pos, i) => `
|
||||
<div class="setup-unterfeld">
|
||||
<span class="setup-unterfeld-label">${esc(pos)}</span>
|
||||
${entitaetCombo(feld.key, i)}
|
||||
</div>`).join("")}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
return `<div class="setup-feldzeile">${kopf}${entitaetCombo(feld.key, null)}</div>`;
|
||||
}
|
||||
|
||||
const SETUP_GRUPPEN = [
|
||||
["fahrterkennung", "Für die Fahrterkennung nötig"],
|
||||
["uebersicht", "Übersicht & Status"],
|
||||
["sicherheit", "Sicherheit"],
|
||||
["standort", "Standort"],
|
||||
];
|
||||
|
||||
function vSetupPopup() {
|
||||
if (!setupOffen) return "";
|
||||
const katalog = setupKatalog();
|
||||
return `<div class="setup-catcher" data-setup-abbrechen></div>
|
||||
<div class="setup-popup" role="dialog" aria-modal="true" aria-label="Setup">
|
||||
<div class="setup-kopf">
|
||||
<strong>Setup</strong>
|
||||
<span>Sensoren dieser Home-Assistant-Instanz den App-Funktionen zuordnen.</span>
|
||||
<div class="setup-kopf-schalter">
|
||||
<label class="switch"><input type="checkbox" data-setup-nurpassend ${setupNurPassend ? "checked" : ""}><span class="track"></span></label>
|
||||
<span>Nur passende Sensoren anzeigen</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setup-koerper">
|
||||
${SETUP_GRUPPEN.map(([key, titel]) => {
|
||||
const felder = katalog.filter((f) => f.gruppe === key);
|
||||
if (!felder.length) return "";
|
||||
return `<div class="setup-gruppe">
|
||||
<div class="setup-gruppe-titel">${esc(titel)}</div>
|
||||
${felder.map((feld) => setupFeldzeile(feld)).join("")}
|
||||
</div>`;
|
||||
}).join("")}
|
||||
</div>
|
||||
<div class="setup-fuss">
|
||||
<button type="button" class="aktion" data-setup-abbrechen>Abbrechen</button>
|
||||
<button type="button" class="aktion primaer" data-setup-speichern>${setupSpeichertLaeuft ? "Speichert …" : "Speichern"}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
let letzteAnsicht = null;
|
||||
let routeStack = [];
|
||||
let navRichtung = null;
|
||||
@@ -2595,7 +2765,23 @@ function render() {
|
||||
v.classList.add(navRichtung === "raus" ? "push-raus" : "push-rein");
|
||||
}
|
||||
navRichtung = null;
|
||||
ROOT.getElementById("overlay").innerHTML = sheetMarkup();
|
||||
// .setup-koerper ist ein eigener Scroll-Container innerhalb des Overlays -
|
||||
// die innerHTML-Ersetzung gleich darunter erzeugt ihn neu (scrollTop
|
||||
// faellt sonst bei jedem render(), z. B. beim Umschalten des Filters,
|
||||
// auf 0 zurueck).
|
||||
const setupKoerperScroll = setupOffen ? (ROOT.querySelector(".setup-koerper") || {}).scrollTop : 0;
|
||||
ROOT.getElementById("overlay").innerHTML = sheetMarkup() + vSetupPopup();
|
||||
if (setupOffen) {
|
||||
const koerper = ROOT.querySelector(".setup-koerper");
|
||||
if (koerper) koerper.scrollTop = setupKoerperScroll || 0;
|
||||
}
|
||||
// Ein offenes Setup-Suchfeld verliert bei jedem render() sein DOM-Element
|
||||
// (v.innerHTML-Ersatz) - hier neu fokussieren, Cursor ans Ende, sonst
|
||||
// müsste man nach jedem Tippen erneut anklicken.
|
||||
if (setupOffen && setupSucheOffen) {
|
||||
const feld = ROOT.querySelector(`[data-setup-suche="${setupSucheOffen}"]`);
|
||||
if (feld) { feld.focus(); const len = feld.value.length; feld.setSelectionRange(len, len); }
|
||||
}
|
||||
/* D4: die Ringe sind nur auf den Tab-Ebenen sichtbar - auf Unterseiten steht
|
||||
dort der Zurück-Pfeil. */
|
||||
ROOT.getElementById("marke").style.display = istTab(route.name) ? "inline-flex" : "none";
|
||||
@@ -2651,7 +2837,7 @@ function randwischenVerdrahten() {
|
||||
const phone = ROOT.querySelector(".phone");
|
||||
phone.addEventListener("pointerdown", (e) => {
|
||||
randX0 = null;
|
||||
if (!e.isPrimary || sheet) return;
|
||||
if (!e.isPrimary || sheet || setupOffen) return;
|
||||
const links = phone.getBoundingClientRect().left;
|
||||
if (e.clientX - links <= 26 && !istTab(route.name)) { randX0 = e.clientX; randY0 = e.clientY; }
|
||||
});
|
||||
@@ -2876,6 +3062,46 @@ function ereignisseVerdrahten() {
|
||||
return;
|
||||
}
|
||||
if (sheet) return;
|
||||
if (e.target.closest("[data-setup-oeffnen]")) { setupOeffnen(); return; }
|
||||
if (setupOffen) {
|
||||
if (e.target.closest("[data-setup-abbrechen]")) { setupSchliessen(); return; }
|
||||
const wahl = e.target.closest("[data-setup-wahl]");
|
||||
if (wahl) {
|
||||
const feldId = wahl.dataset.setupWahl, entityId = wahl.dataset.setupEntity;
|
||||
const teile = feldId.split(":");
|
||||
if (teile.length === 2) setupZuordnung[teile[0]][Number(teile[1])] = entityId;
|
||||
else setupZuordnung[feldId] = entityId;
|
||||
setupSucheOffen = null; setupSuchtext = ""; render();
|
||||
return;
|
||||
}
|
||||
if (e.target.closest("[data-setup-speichern]")) {
|
||||
if (setupSpeichertLaeuft) return;
|
||||
setupSpeichertLaeuft = true; render();
|
||||
(async () => {
|
||||
try {
|
||||
await HASS.callService("pyscript", "audi_dashboard_entitaeten_schreiben", { zuordnung_json: JSON.stringify(setupZuordnung) });
|
||||
} catch (err) {
|
||||
setupSpeichertLaeuft = false; hinweis("Speichern fehlgeschlagen", err.message); return;
|
||||
}
|
||||
setupOffen = false; setupSucheOffen = null; setupZuordnung = null; setupSpeichertLaeuft = false; render();
|
||||
})();
|
||||
return;
|
||||
}
|
||||
// Klick aufs (noch geschlossene) Suchfeld öffnet dessen Dropdown; ein
|
||||
// Klick, während es schon offen ist (z. B. Cursor neu setzen), soll
|
||||
// keinen render() auslösen - würde die laufende Eingabe unterbrechen.
|
||||
const sucheInput = e.target.closest("[data-setup-suche]");
|
||||
if (sucheInput) {
|
||||
const feldId = sucheInput.dataset.setupSuche;
|
||||
if (setupSucheOffen !== feldId) { setupSucheOffen = feldId; setupSuchtext = ""; render(); }
|
||||
return;
|
||||
}
|
||||
// Klick irgendwo sonst im Popup (aber nicht auf den Catcher, der schon
|
||||
// oben behandelt wurde) schließt ein offenes Dropdown, ohne das ganze
|
||||
// Popup zu verlassen.
|
||||
if (setupSucheOffen && !e.target.closest(".entitaet-combo")) { setupSucheOffen = null; setupSuchtext = ""; render(); return; }
|
||||
return;
|
||||
}
|
||||
const bvReset = e.target.closest("[data-bvreset]");
|
||||
if (bvReset) { bvDomain = [...bvVollDomain]; bvAusgewaehlt = null; bvZeichnen(); return; }
|
||||
// Nachlaufender Klick derselben Geste (Maus feuert ihn trotz Bewegung
|
||||
@@ -3155,6 +3381,20 @@ function ereignisseVerdrahten() {
|
||||
});
|
||||
|
||||
ROOT.addEventListener("input", (e) => {
|
||||
const sucheFeld = e.target.dataset.setupSuche;
|
||||
if (sucheFeld) {
|
||||
// Nur die Kandidatenliste patchen, nicht render() - ein voller
|
||||
// Re-Render würde bei jedem Tastendruck das Eingabefeld neu erzeugen
|
||||
// und den Fokus verlieren (gleiches Problem/gleiche Lösung wie beim
|
||||
// bestehenden data-tankpreisfeld-Handler direkt hier drunter).
|
||||
setupSuchtext = e.target.value;
|
||||
const teile = sucheFeld.split(":");
|
||||
const key = teile[0];
|
||||
const wert = teile.length === 2 ? (setupZuordnung[key] || [])[Number(teile[1])] : setupZuordnung[key];
|
||||
const liste = ROOT.querySelector(`[data-setup-liste="${sucheFeld}"]`);
|
||||
if (liste) liste.innerHTML = entitaetZeilenMarkup(sucheFeld, key, setupSuchtext, wert);
|
||||
return;
|
||||
}
|
||||
const praefix = e.target.dataset.tankpreisfeld;
|
||||
if (!praefix) return;
|
||||
const liter = parseFloat((ROOT.getElementById(`${praefix}Liter`).value || "0").replace(",", "."));
|
||||
@@ -3223,7 +3463,6 @@ function ereignisseVerdrahten() {
|
||||
if (ein) {
|
||||
if (ein === "kennzeichen") CONFIG.kennzeichen = e.target.value;
|
||||
else if (ein === "ausfuehrung") CONFIG.ausfuehrung = e.target.value;
|
||||
else if (ein === "wlan") CONFIG.wlanName = e.target.value;
|
||||
else if (ein === "modell") CONFIG.fahrzeugtitel = e.target.value;
|
||||
else CAR[ein] = e.target.value;
|
||||
profilSpeichern(); return;
|
||||
@@ -3282,6 +3521,7 @@ function ereignisseVerdrahten() {
|
||||
if (e.target.dataset.oelkm !== undefined) { CAR.oel.km = parseInt(e.target.value, 10); profilSpeichern(); return; }
|
||||
if (e.target.dataset.oelm !== undefined) { CAR.oel.monate = parseInt(e.target.value, 10); profilSpeichern(); return; }
|
||||
if (e.target.dataset.boden !== undefined) { bodenAn = e.target.checked; render(); return; }
|
||||
if (e.target.dataset.setupNurpassend !== undefined) { setupNurPassend = e.target.checked; render(); return; }
|
||||
if (e.target.dataset.startbild !== undefined) { CONFIG.startbild = e.target.value; profilSpeichern(); render(); return; }
|
||||
if (e.target.dataset.svsel !== undefined) { svAuswahl = parseInt(e.target.value, 10); render(); return; }
|
||||
});
|
||||
@@ -3388,6 +3628,11 @@ async function datenLaden(erzwingen) {
|
||||
const statusState = HASS.states["pyscript.audi_dashboard_fahrzeugstatus"] || NACHGELADEN.status;
|
||||
const battState = HASS.states["pyscript.audi_dashboard_batterieverlauf"] || NACHGELADEN.batt;
|
||||
const belegState = HASS.states["pyscript.audi_dashboard_beleg_ergebnis"];
|
||||
// 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"];
|
||||
if (entitaetenState && entitaetenState.attributes) ENTITAETEN = entitaetenState.attributes.daten;
|
||||
|
||||
if (belegState && belegState !== letzterBelegState) {
|
||||
letzterBelegState = belegState;
|
||||
|
||||
Reference in New Issue
Block a user