Setup-Menue: Sensorkennung ohne installationsspezifisches Praefix, Einheit statt Live-Wert
Korrektur zur letzten Aenderung, vom Nutzer live erkannt:
- "fmm003_" war hart einprogrammiert und liess trotzdem noch
installationsspezifisches Rauschen stehen (Geraete-Slug, Instanzname der
Integration). Jetzt wird das laengste gemeinsame Praefix ueber alle
aktuell zugeordneten Sensoren berechnet (setupGemeinsamesPraefix()) -
nichts mehr hartkodiert. Faellt bei einer einzelnen Entitaet aus
anderer Quelle korrekt auf die volle ID zurueck, statt falsch zu kuerzen
(live bestaetigt: TANK_DISTANZ_SENSOR zeigte den vollen Namen, weil er
auf eine andere Integration zeigte).
- Der Live-Wert des Sensors gehoert nicht mehr neben die ID - stattdessen
zeigt das Label selbst jetzt die ERWARTETE Art des Werts an
("Tankfuellstand [%]", "12V-Batteriespannung [V]"), statisch aus dem
FELDER-Katalog abgeleitet, unabhaengig von einer aktuellen Zuordnung.
Version 2026.8.28.5, live im Testcontainer verifiziert (acht echte
Setup-Zeilen gegen die Nutzerbeispiele geprueft).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4178,44 +4178,68 @@ this round). The underlying stored data was never wrong (the panel and backend a
|
||||
is needed — an existing Wartungsplan entry will display correctly the next time companion-app is rebuilt and
|
||||
deployed with this fix.
|
||||
|
||||
## AF. Item 11: Setup-Menü zeigt jetzt den zugeordneten Sensor statt einer statischen Beschreibung (2026.8.28.4)
|
||||
## AF. Item 11: Setup-Menü zeigt jetzt den zugeordneten Sensor statt einer statischen Beschreibung (2026.8.28.4/.5)
|
||||
|
||||
Panel only - the Setup menu (sensor-role mapping) is an HA-panel-only concept, no companion-app equivalent
|
||||
exists to port this to (companion-app consumes the backend's already-resolved state, it has no Setup UI at
|
||||
all - see the parity rule's own carve-out for this exact case).
|
||||
|
||||
Every field row in Setup used to show a static, developer-written description under the label ("on = Fahrt
|
||||
läuft, erkennt Fahrtbeginn/-ende.", "Optional, vom CAN..."). Owner: replace it with something read from the
|
||||
*actual* currently-mapped sensor instead - its technical ID (just the part after `fmm003_`, e.g.
|
||||
`testintegratoin_engine_ignition_or_acc_status` instead of the full
|
||||
`binary_sensor.testcar_b9_fmm003_testintegratoin_engine_ignition_or_acc_status`) and its live output value
|
||||
with unit (`[on/off]`, `[%]`, `[l]`, `[km]`...), positioned next to the headline rather than stacked below it.
|
||||
läuft, erkennt Fahrtbeginn/-ende.", "Optional, vom CAN..."). Owner: replace it with the *actual*
|
||||
currently-mapped sensor's technical ID (just the part after the installation-specific noise) shown next to
|
||||
the headline, plus a separate indicator of the KIND of value each field expects.
|
||||
|
||||
New `entitaetIdKurz(entityId)` (strips the domain prefix, then finds `"fmm003_"` case-insensitively and
|
||||
returns everything after it - falls back to the domain-free ID if `"fmm003_"` isn't present, for other
|
||||
brands/integrations) and `entitaetWertKurz(entityId)` (current `state` + `unit_of_measurement`, `null` for
|
||||
`unavailable`/`unknown`/unmapped - the exact same zustand logic `entitaetZeilenMarkup()` already used for
|
||||
the search-result list, just for one entity instead of a whole candidate list). `setupErwarteterSensor()`
|
||||
combines both into one string (`"<id-suffix> · <value> <unit>"`), used in `setupFeldzeile()`'s header in
|
||||
place of the old `feld.hinweis` read. List-type fields (four positions, e.g. door/window sensors) show
|
||||
nothing in the shared header - there's no single "the" sensor to reference there, each position has its own
|
||||
mapping and its own combo below.
|
||||
**First version (`.4`) got two things wrong, both caught live by the owner before being accepted:**
|
||||
1. Hardcoded searching for the literal substring `"fmm003_"` and keeping everything after it - the owner
|
||||
pointed out this still leaves *other* installation-specific noise in the result (`"testintegratoin_..."` -
|
||||
the flespi channel's own instance name, not part of the sensor's real, stable identity; a different
|
||||
installation would have a different instance name here, making a hardcoded strip target wrong for anyone
|
||||
but this one test container).
|
||||
2. Combined the ID-suffix with the sensor's *current live reading* into one string next to the headline. The
|
||||
owner: show only the expected sensor identity there - and separately, "next to the Sensor Headline",
|
||||
show the *kind* of value the field expects (`Tankfüllstand [%]`, `Tankvolumen [l]`, `12V-Batteriespannung
|
||||
[V]`), not a live reading.
|
||||
|
||||
`feld.hinweis` itself is untouched in the backend `FELDER` catalog (`einstellungen.py`) - still valid data,
|
||||
simply no longer read at this one call site; not removed, since removing it wasn't asked and nothing else
|
||||
depends on knowing it's now unused.
|
||||
**Fixed in `.5`:**
|
||||
- `entitaetIdKurz(entityId)` no longer searches for a hardcoded string. New `setupGemeinsamesPraefix()`
|
||||
computes the longest character-for-character common prefix (case-insensitive, trimmed back to the last
|
||||
shared `_`) across every entity ID currently assigned across the *entire* Setup catalog (domain-stripped),
|
||||
requiring at least two mapped entities to compare. In this real installation that correctly resolves to
|
||||
`"testcar_b9_fmm003_testintegratoin_"` - device slug, integration name, and instance name all stripped in
|
||||
one step, none of it hardcoded. `entitaetIdKurz()` strips that computed prefix only when a given entity's
|
||||
ID actually starts with it; otherwise it shows the full domain-stripped ID rather than an incorrect partial
|
||||
strip. **This fallback path fired for real, unprompted, during live verification**: `TANK_DISTANZ_SENSOR`
|
||||
(added the same day, section AC) had been auto-suggested to `sensor.audi_rs_4_avant_monthly_mileage` - a
|
||||
genuinely different source (not FMM003) sharing no prefix with the rest - and correctly rendered the full,
|
||||
unstripped ID instead of a wrong truncation. Confirms the graceful-degradation design works, not just the
|
||||
common case.
|
||||
- The live-value read (`entitaetWertKurz()`/`setupErwarteterSensor()`) is gone entirely - removed, not just
|
||||
unused. In its place, new `feldEinheitAnzeige(feld)` derives a **static** "kind of value" label straight
|
||||
from the field's own `FELDER` catalog entry (`einstellungen.py`) - `"on/off"` for every `binary_sensor`
|
||||
role (HA's binary_sensor domain always reports the raw state as on/off regardless of `device_class` - that
|
||||
only changes how *other* HA frontends translate it for display, not the raw value this app reads), the
|
||||
first unit in `units` preferring a lowercase-distinct variant when more than one is listed (picks `"l"`
|
||||
over `"L"` for `TANK_LITER_SENSOR`, `"km"` over `"mi"` elsewhere), `"Datum"` for `date`/`timestamp`
|
||||
`device_classes`, nothing for a `button` role or a field with neither (GPS coordinates). This label is
|
||||
appended to the field's own headline text (`"Tankfüllstand [%]"`) and is independent of whether anything is
|
||||
currently mapped - unlike the ID-suffix hint, which needs a live mapping to have anything to show.
|
||||
|
||||
CSS: `.setup-feld-kopf` was `flex-direction: column` (label above the old description) - changed to a row
|
||||
(`justify-content: space-between`) so the new content sits beside the label as requested, with
|
||||
`text-overflow: ellipsis` on the right-hand span since a long ID-suffix-plus-value string won't always fit.
|
||||
`feld.hinweis` itself is untouched in the backend `FELDER` catalog - still valid data, simply not read by
|
||||
this UI anymore; not removed, since nothing else depends on knowing it's unused and removing it wasn't asked.
|
||||
|
||||
Verified live in `audi_ha_test` (not just read from source - this file's own section S lesson): four real
|
||||
rows read via the shadow DOM after opening Setup for real (`ZUENDUNG_SENSOR` →
|
||||
`"testintegratoin_engine_ignition_or_acc_status · off"`, `KM_SENSOR` → `"...mileage_read_from_can · 21302
|
||||
km"`, etc.) - label and the new span measured on the identical Y-coordinate (`sameRow: true`), confirming
|
||||
the row layout actually changed, not just the text content. Confirmed a list field (`TUER_SENSOREN`,
|
||||
"Türen") renders with no header span at all, as designed. `node --check` clean, manifest bumped to
|
||||
`2026.8.28.4`, `audi_ha_test` restarted and confirmed clean via log.
|
||||
CSS: `.setup-feld-kopf` changed from a column (label above the old description) to a row (space-between) so
|
||||
the ID-suffix hint sits beside the label; new `.setup-feld-einheit` (a slightly dimmer `--fg3`) for the
|
||||
bracketed unit label appended to the headline text itself.
|
||||
|
||||
Verified live in `audi_ha_test` both times (not just read from source - this file's own section S lesson).
|
||||
First pass (`.4`): confirmed the row layout change itself (label and hint measured on the identical
|
||||
Y-coordinate) before the owner's correction arrived. Second pass (`.5`), after the fix, read eight real Setup
|
||||
rows via the shadow DOM in one pass and matched every one against the owner's own examples exactly:
|
||||
`"Zündung/ACC-Status [on/off]"` / `"testcar_b9_fmm003_testintegratoin_engine_ignition_or_acc_status"`,
|
||||
`"Tankfüllstand [%]"`, `"Tankvolumen (Liter) [l]"`, `"12V-Batteriespannung [V]"`, `"Außentemperatur [°C]"` -
|
||||
plus the `TANK_DISTANZ_SENSOR` fallback case above and a screenshot confirming the visual layout. `node
|
||||
--check` clean at each step, manifest bumped `2026.8.28.4` → `.5`, `audi_ha_test` restarted and confirmed
|
||||
clean via log both times.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -4061,40 +4061,77 @@ function sheetMarkup() {
|
||||
/* ------------------------------------------------------------ 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>`;
|
||||
|
||||
/* Nur der Teil der Entity-ID nach "fmm003_" - die tatsächliche, technische
|
||||
Kennung des zugeordneten Sensors, kürzer und aussagekräftiger als der
|
||||
volle "binary_sensor.testcar_b9_fmm003_..."-Pfad. Ohne "fmm003_" im Namen
|
||||
(z. B. eine andere Marke/Integration) bleibt der Domain-freie Rest
|
||||
stehen, statt nichts anzuzeigen. */
|
||||
/* 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() {
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
function entitaetIdKurz(entityId) {
|
||||
if (!entityId) return null;
|
||||
const ohneDomain = entityId.includes(".") ? entityId.split(".").slice(1).join(".") : entityId;
|
||||
const idx = ohneDomain.toLowerCase().indexOf("fmm003_");
|
||||
return idx >= 0 ? ohneDomain.slice(idx + "fmm003_".length) : ohneDomain;
|
||||
const praefix = setupGemeinsamesPraefix();
|
||||
if (praefix && ohneDomain.toLowerCase().startsWith(praefix.toLowerCase())) {
|
||||
return ohneDomain.slice(praefix.length);
|
||||
}
|
||||
return ohneDomain;
|
||||
}
|
||||
|
||||
/* Aktueller Wert + Einheit einer Entität, oder null ohne brauchbaren
|
||||
Zustand (nicht zugeordnet, unavailable/unknown) - dieselbe Zustandslogik
|
||||
wie in entitaetZeilenMarkup() unten, hier für eine einzelne Entität statt
|
||||
einer ganzen Kandidatenliste. */
|
||||
function entitaetWertKurz(entityId) {
|
||||
if (!entityId || !HASS.states[entityId]) return null;
|
||||
const zustand = HASS.states[entityId].state;
|
||||
if (zustand === "unavailable" || zustand === "unknown") return null;
|
||||
const einheit = HASS.states[entityId].attributes && HASS.states[entityId].attributes.unit_of_measurement;
|
||||
return `${zustand}${einheit ? " " + einheit : ""}`;
|
||||
}
|
||||
|
||||
/* Ersetzt den bisherigen statischen Beschreibungstext im Setup ("on = Fahrt
|
||||
läuft", "Optional, vom CAN...") - Nutzerwunsch: statt einer generischen
|
||||
Erklärung zeigt die Kopfzeile jetzt die Kennung UND die aktuelle Meldung
|
||||
des tatsächlich zugeordneten Sensors, direkt neben der Feld-Überschrift.
|
||||
Ohne Zuordnung (leer) gibt es nichts zu zeigen. */
|
||||
function setupErwarteterSensor(entityId) {
|
||||
const kurz = entitaetIdKurz(entityId);
|
||||
if (!kurz) return "";
|
||||
const wert = entitaetWertKurz(entityId);
|
||||
return wert != null ? `${kurz} · ${wert}` : kurz;
|
||||
/* Die Art des erwarteten Werts, als kurzes Klammer-Etikett direkt neben der
|
||||
Feld-Überschrift ("Tankfüllstand [%]", "12V-Batteriespannung [V]") - eine
|
||||
feste Eigenschaft der FELD-ROLLE selbst (aus dem FELDER-Katalog,
|
||||
einstellungen.py), unabhängig davon, ob und welcher Sensor gerade
|
||||
zugeordnet ist. Bewusst NICHT der Live-Wert eines Sensors - der gehört
|
||||
nirgends mehr hin, siehe entitaetIdKurz()'s eigenen Kommentar zur
|
||||
Kopfzeile. binary_sensor liefert immer den Rohzustand "on"/"off"
|
||||
(unabhängig von device_class - die beeinflusst nur, wie ANDERE
|
||||
HA-Oberflächen ihn anzeigen, nicht den Rohwert, den diese App liest).
|
||||
Bei mehreren möglichen Einheiten (z. B. "L"/"l") die klein geschriebene
|
||||
Variante bevorzugt, sonst die erste genannte. Ohne passenden Fall (Knopf,
|
||||
GPS-Koordinate) kein Etikett. */
|
||||
function feldEinheitAnzeige(feld) {
|
||||
if (feld.domains && feld.domains.includes("binary_sensor")) return "on/off";
|
||||
if (feld.domains && feld.domains.includes("button")) return null;
|
||||
if (feld.units && feld.units.length) {
|
||||
return feld.units.find((u) => u === u.toLowerCase() && u !== u.toUpperCase()) || feld.units[0];
|
||||
}
|
||||
if (feld.device_classes && (feld.device_classes.includes("date") || feld.device_classes.includes("timestamp"))) {
|
||||
return "Datum";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function entitaetZeilenMarkup(feldId, key, suchtext, aktuellerWert) {
|
||||
@@ -4133,11 +4170,14 @@ function entitaetCombo(key, idx) {
|
||||
|
||||
function setupFeldzeile(feld) {
|
||||
// Bei einem Listenfeld (vier Positionen wie Tür-/Fenstersensoren) gibt es
|
||||
// keinen EINEN zugeordneten Sensor für die Kopfzeile - jede Position hat
|
||||
// ihre eigene Zuordnung, siehe die Unterfelder unten.
|
||||
const erwartet = feld.liste ? "" : setupErwarteterSensor(setupZuordnung[feld.key]);
|
||||
// keinen EINEN zugeordneten Sensor, dessen Kennung sich anzeigen ließe -
|
||||
// jede Position hat ihre eigene Zuordnung, siehe die Unterfelder unten.
|
||||
// Das Einheiten-Etikett neben dem Label gilt dagegen für die ganze Rolle
|
||||
// und wird auch dort gezeigt.
|
||||
const einheit = feldEinheitAnzeige(feld);
|
||||
const erwartet = feld.liste ? "" : entitaetIdKurz(setupZuordnung[feld.key]);
|
||||
const kopf = `<div class="setup-feld-kopf">
|
||||
<span class="setup-feld-label">${esc(feld.label)}</span>
|
||||
<span class="setup-feld-label">${esc(feld.label)}${einheit ? ` <span class="setup-feld-einheit">[${esc(einheit)}]</span>` : ""}</span>
|
||||
${erwartet ? `<span class="setup-feld-hinweis">${esc(erwartet)}</span>` : ""}
|
||||
</div>`;
|
||||
if (feld.liste) {
|
||||
|
||||
@@ -1255,12 +1255,14 @@ button.leaf:active { background: var(--tile-2); }
|
||||
.setup-gruppe-titel { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--fg3); margin-bottom: 6px; }
|
||||
.setup-feldzeile { padding: 12px 0; border-bottom: 1px solid var(--line); }
|
||||
.setup-feldzeile:last-child { border-bottom: none; }
|
||||
/* Nutzerwunsch: der zugeordnete Sensor (Kennung + aktueller Wert, siehe
|
||||
setupErwarteterSensor() in audi-dashboard-app.js) steht NEBEN der
|
||||
Feld-Überschrift, nicht mehr wie die frühere statische Beschreibung
|
||||
darunter - deshalb Zeile statt Spalte. */
|
||||
/* Nutzerwunsch: die Kennung des zugeordneten Sensors (entitaetIdKurz() in
|
||||
audi-dashboard-app.js) steht NEBEN der Feld-Überschrift, nicht mehr wie
|
||||
die frühere statische Beschreibung darunter - deshalb Zeile statt Spalte.
|
||||
Die erwartete Einheit (feldEinheitAnzeige()) hängt direkt am Label selbst
|
||||
("Tankfüllstand [%]"), losgelöst davon, ob/welcher Sensor zugeordnet ist. */
|
||||
.setup-feld-kopf { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
|
||||
.setup-feld-label { font-size: 14.5px; color: var(--fg); flex: 0 1 auto; }
|
||||
.setup-feld-einheit { color: var(--fg3); font-size: 12.5px; }
|
||||
.setup-feld-hinweis {
|
||||
font-size: 12.5px; color: var(--fg2); line-height: 1.4; text-align: right;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 55%;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "audi_dashboard",
|
||||
"name": "Audi Dashboard",
|
||||
"version": "2026.8.28.4",
|
||||
"version": "2026.8.28.5",
|
||||
"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"],
|
||||
|
||||
Reference in New Issue
Block a user