From 4f85a179cf49e0154a8837367a6194329b320c74 Mon Sep 17 00:00:00 2001 From: Tobi G Date: Fri, 28 Aug 2026 10:54:16 +0200 Subject: [PATCH] Setup-Menue: Sensorkennung per naechstem Verwandten statt globalem Praefix Zwei weitere Korrekturrunden, beide vom Nutzer live erkannt: - .6: paarweise Abstimmung statt fortlaufender Verengung - robuster gegen einen einzelnen Ausreisser, aber grundsaetzlich noch falsch. - .7: die eigentliche Ursache war, dass diese Installation zwei etwa gleich grosse Sensorquellen gleichzeitig hat (9 FMM003-Rollen, 18 Rollen einer zweiten Integration mit "audi_rs_4_avant_"-Praefix) - ein EINZELNES globales "Sieger"-Praefix kann immer nur eine der beiden Quellen richtig kuerzen. entitaetIdKurz() sucht jetzt pro Feld den naechsten Verwandten unter allen zugeordneten Sensoren und kuerzt nur dessen gemeinsames Praefix - unabhaengig von Anzahl und Groesse der Quellen. Diesmal vor dem Deploy anhand der ECHTEN sensor.audi_dashboard_entitaeten- Zuordnung durchgerechnet statt mit erfundenen Test-IDs (das hatte den .6-Fehler faelschlich als behoben erscheinen lassen). Version 2026.8.28.7, live verifiziert: alle 20 Einzelwert-Felder zeigen die richtige, kurze Kennung, keine installationsspezifischen Reste aus beiden Quellen mehr. Co-Authored-By: Claude Sonnet 5 --- AGENTS.md | 40 ++++++++++ .../frontend/audi-dashboard-app.js | 79 ++++++++++--------- .../audi_dashboard/manifest.json | 2 +- 3 files changed, 83 insertions(+), 38 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d6c81ef..17ebe67 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4241,6 +4241,46 @@ plus the `TANK_DISTANZ_SENSOR` fallback case above and a screenshot confirming t --check` clean at each step, manifest bumped `2026.8.28.4` → `.5`, `audi_ha_test` restarted and confirmed clean via log both times. +**`.5`'s own verification above was itself misleading - the owner caught the real bug immediately after +("I still see testcar_b9_fmm003_testintegratoin_ and audi_rs_4_avant_").** `.5`'s `setupGemeinsamesPraefix()` +computed ONE global prefix by intersecting every mapped sensor's ID down to their shared characters - and a +single outlier (any one field mapped to a sensor from a different source than the rest) collapses that +intersection to nothing, so EVERY field falls back to its full, unstripped ID. The live verification quoted +above only exercised four fields and happened not to include the one that broke it - a real gap in how +thoroughly that pass was checked, not a fluke in the code. + +Fixed properly in `.6`: replaced the single global intersection with pairwise voting (every sensor pair's own +common prefix, the most frequently occurring one wins) - more robust against one outlier, but **still wrong +for a different reason**, found by the owner's report and confirmed by pulling the actual live +`sensor.audi_dashboard_entitaeten` data (not by re-reading the source - the only way this surfaced): this +installation genuinely has **two comparably-sized sensor sources at once** - 9 FMM003-sourced roles +(ignition, odometer, tank, battery, GPS) and 18 roles from a second integration, prefixed +`audi_rs_4_avant_` (all four doors, all four windows, tailgate/hood/lock/sunroof/light, all four service-date +fields). A single global "winner" prefix, however it's computed, can only ever strip ONE of the two families +correctly and leaves the other's IDs fully unstripped - exactly what both `.5` and `.6` did, just for +different roles each time. + +**`.7` drops the idea of a single global prefix entirely.** `entitaetIdKurz()` now finds, for the entity +being displayed, its own single closest relative among every other currently-mapped sensor (the one it +shares the longest prefix with) and strips only that pairwise prefix - computed independently per field, so +it doesn't matter how many different sensor sources are mixed together or how large each cluster is. Verified +by pulling the *actual* stored `werte` mapping from `sensor.audi_dashboard_entitaeten` (not fabricated test +IDs like the `.5`/`.6` passes used) and running the real algorithm against it before deploying - this is what +caught the `.6` design flaw *before* declaring it fixed, rather than after another owner report. A pleasant +side effect: `NAECHSTER_OELWECHSEL_SENSOR`/`NAECHSTE_INSPEKTION_SENSOR` (`oil_change_due`/`inspection_due`) +and their `_STRECKE_SENSOR` counterparts (`oil_change_distance`/`inspection_distance`) now pair off with each +other specifically and reduce to `"due"`/`"distance"` - each field lands next to its true sibling, not just +"some" shared prefix. + +Verified live in `audi_ha_test` at every step (`.6` then `.7`), each time reading the actual rendered Setup +popup fresh after a real restart (not a stale tab - confirmed via `performance.getEntriesByType('resource')` +showing the new `?v=` served with a nonzero transfer size each time). Final state (`.7`), all 20 single-value +fields checked in one pass: every FMM003 role shows its bare signal name +(`external_power_voltage`, `can_ambient_air_temperature`, ...), every `audi_rs_4_avant_` role shows its own +(`tailgate`, `hood`, `sunroof`, `parking_lights`, `due`, `distance`, ...), nothing shows installation noise +from either source. `node --check` clean at each step. Manifest `2026.8.28.5` → `.6` → `.7`, `audi_ha_test` +restarted and confirmed clean via log each time. + --- ## Working conventions (observed — keep them) diff --git a/custom_components/audi_dashboard/frontend/audi-dashboard-app.js b/custom_components/audi_dashboard/frontend/audi-dashboard-app.js index 17eb331..5c7e566 100644 --- a/custom_components/audi_dashboard/frontend/audi-dashboard-app.js +++ b/custom_components/audi_dashboard/frontend/audi-dashboard-app.js @@ -4061,53 +4061,58 @@ function sheetMarkup() { /* ------------------------------------------------------------ Setup-Popup */ const LUPE_SVG = ``; -/* Das gemeinsame Präfix aller aktuell zugeordneten Sensoren (domain-frei), - bis zum letzten gemeinsamen "_" - z. B. "testcar_b9_fmm003_testintegratoin_". - NICHT hart auf "fmm003_" verdrahtet: sowohl der Geräte-Slug - ("testcar_b9") als auch der Instanzname der Integration - ("testintegratoin") sind installationsspezifisch, vom Nutzer beim - Einrichten seines eigenen flespi-Kanals selbst vergeben - bei ihm heißt - das anders. Nur was WIRKLICH von allen zugeordneten Sensoren geteilt - wird, ist reines Rauschen; alles danach ist die eigentliche, stabile - Signalbezeichnung. Mit weniger als zwei zugeordneten Sensoren (nichts zum - Vergleichen) oder wenn Sensoren aus zwei erkennbar verschiedenen Quellen - kommen (kein gemeinsames Präfix), liefert das null - dann zeigt - entitaetIdKurz() lieber die volle, ungekürzte ID als eine geratene, - möglicherweise falsche Kürzung. */ -function setupGemeinsamesPraefix() { +/* Gemeinsames Präfix zweier domain-freier IDs, bis zum letzten gemeinsamen + "_" - oder null, wenn sie nicht einmal ein volles Segment teilen. */ +function praefixPaar(a, b) { + let i = 0; + while (i < a.length && i < b.length && a[i].toLowerCase() === b[i].toLowerCase()) i++; + const idxUnter = a.slice(0, i).lastIndexOf("_"); + return idxUnter > 0 ? a.slice(0, idxUnter + 1) : null; +} + +/* Alle aktuell im Setup zugeordneten Entity-IDs (domain-frei), außer der + übergebenen selbst - Vergleichsbasis für entitaetIdKurz() unten. */ +function setupAndereIdsOhneDomain(ausser) { const ids = []; for (const feld of setupKatalog()) { const wert = setupZuordnung[feld.key]; - if (feld.liste) { for (const w of (wert || [])) if (w) ids.push(w); } - else if (wert) ids.push(wert); + if (feld.liste) { for (const w of (wert || [])) if (w && w !== ausser) ids.push(w); } + else if (wert && wert !== ausser) ids.push(wert); } - const ohneDomain = ids.map((id) => id.includes(".") ? id.split(".").slice(1).join(".") : id); - if (ohneDomain.length < 2) return null; - let praefix = ohneDomain[0]; - for (const s of ohneDomain.slice(1)) { - let i = 0; - while (i < praefix.length && i < s.length && praefix[i].toLowerCase() === s[i].toLowerCase()) i++; - praefix = praefix.slice(0, i); - if (!praefix) return null; - } - const idxUnter = praefix.lastIndexOf("_"); - return idxUnter > 0 ? praefix.slice(0, idxUnter + 1) : null; + return ids.map((id) => id.includes(".") ? id.split(".").slice(1).join(".") : id); } -/* Die Entity-ID ohne Domain und ohne das gemeinsame, installationsspezifische - Präfix (siehe setupGemeinsamesPraefix()) - die tatsächliche, stabile - Kennung des zugeordneten Sensors, unabhängig davon, wie Geräte-Slug und - Integrationsinstanz beim jeweiligen Nutzer heißen. Passt das gemeinsame - Präfix nicht auf DIESE Entität (z. B. eine einzelne Entität aus einer - anderen Quelle als der Rest), bleibt die volle domain-freie ID stehen. */ +/* Die Entity-ID ohne Domain und ohne das installationsspezifische Präfix, + das sie mit ihrem NÄCHSTEN Verwandten unter den übrigen zugeordneten + Sensoren teilt (dem, mit dem sie das längste gemeinsame Präfix hat) - die + tatsächliche, stabile Kennung des zugeordneten Sensors, unabhängig davon, + wie Geräte-Slug und Integrationsinstanz beim jeweiligen Nutzer heißen + (z. B. "testcar_b9_fmm003_testintegratoin_" oder "audi_rs_4_avant_"). + + Bewusst PRO ENTITÄT der beste Nachbar statt eines einzigen, für alle + Felder gemeinsamen Präfixes (frühere Fassung) - eine reale Installation + hat oft MEHRERE Sensorquellen gleichzeitig (hier: FMM003 für Fahrt-/ + Tank-/Batteriewerte, eine zweite Integration für Türen/Fenster/ + Service-Termine, per "Nur passende Sensoren anzeigen" beide gleichzeitig + vorgeschlagen) - keine ist objektiv "die Mehrheit". Ein einziges globales + Präfix (egal ob per fortlaufender Verengung oder per Mehrheitsentscheid) + kann immer nur EINE Quelle richtig kürzen und zeigt die Sensoren der + anderen ungekürzt - genau das hat der Nutzer live beobachtet + ("audi_rs_4_avant_" gewann die Abstimmung, alle FMM003-Felder blieben + unangetastet). Mit dem nächsten Verwandten statt einem globalen Sieger + kürzt jede Rolle unabhängig von den anderen richtig, egal wie viele + verschiedene Quellen gleichzeitig im Spiel sind. Findet sich kein + Verwandter mit gemeinsamem Präfix, bleibt die volle domain-freie ID + stehen statt einer geratenen, möglicherweise falschen Kürzung. */ function entitaetIdKurz(entityId) { if (!entityId) return null; const ohneDomain = entityId.includes(".") ? entityId.split(".").slice(1).join(".") : entityId; - const praefix = setupGemeinsamesPraefix(); - if (praefix && ohneDomain.toLowerCase().startsWith(praefix.toLowerCase())) { - return ohneDomain.slice(praefix.length); + let bestesPraefix = null, besteLaenge = 0; + for (const andere of setupAndereIdsOhneDomain(entityId)) { + const kandidat = praefixPaar(ohneDomain, andere); + if (kandidat && kandidat.length > besteLaenge) { bestesPraefix = kandidat; besteLaenge = kandidat.length; } } - return ohneDomain; + return bestesPraefix ? ohneDomain.slice(bestesPraefix.length) : ohneDomain; } /* Die Art des erwarteten Werts, als kurzes Klammer-Etikett direkt neben der diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index 6f42c3f..43be552 100644 --- a/custom_components/audi_dashboard/manifest.json +++ b/custom_components/audi_dashboard/manifest.json @@ -1,7 +1,7 @@ { "domain": "audi_dashboard", "name": "Audi Dashboard", - "version": "2026.8.28.5", + "version": "2026.8.28.7", "documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md", "issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues", "codeowners": ["@paul"],