Fahrgestellnummer: expliziter "automatisch"-Schalter statt Nur-wenn-leer-Heuristik
- Neues Profilfeld fahrzeug.fin_automatisch (Standard: an). - identitaet.py: bei "aus" läuft die Ableitung (samt Mehrfach-Sensor-Abgleich) gar nicht erst; bei "an" überschreibt der abgeleitete Wert auch einen vorher von Hand eingetragenen - der Schalter selbst entscheidet jetzt, statt aus einem leeren Feld zu raten. - dienste.py: profil_schreiben löst die Ableitung jetzt auch aus, damit das Umschalten des Schalters selbst sofort wirkt. - Panel: Schalter "automatisch" zwischen Label und Textfeld in der Fahrgestellnummer-Zeile, Feld wird bei "an" deaktiviert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4444,6 +4444,44 @@ needed either - the "Fahrgestellnummer (FIN)" field in "Fahrzeug einrichten" alr
|
||||
profile field, so it now simply arrives pre-filled instead of blank, and stays fully editable/correctable by
|
||||
hand same as before.
|
||||
|
||||
**Same-day follow-up (2026.8.28.16): an explicit "automatisch" switch, per owner request** ("make a Slider
|
||||
next to 'Fahrgestellnummer (FIN)' ... automatisch turns on/off the auto-grab VIN from Sensor-Database ...
|
||||
and also deactivates the sanity-check if VIN matches between sensors"). The original design's "only fill an
|
||||
empty field, never touch a set one" heuristic is gone - replaced by an explicit, user-visible mode:
|
||||
|
||||
- New profile field `fahrzeug.fin_automatisch` (bool, default `true` - added to `vorlage/fahrzeugprofil.json`
|
||||
and defaulted in `profilZuCar()` via `!== false` for profiles from before this field existed).
|
||||
- `identitaet.fin_ableiten_und_uebernehmen()` rewritten: returns immediately if `fin_automatisch` is `false`
|
||||
- **before** even calling `fin_kandidaten_ermitteln()`, so the multi-sensor comparison/mismatch-warning
|
||||
never runs either, exactly as asked. When `true`, the derived value is now treated as authoritative and
|
||||
**overwrites** whatever is currently in `fin` (including a previously hand-typed value) whenever a single
|
||||
clear candidate is found - this is the whole point of an explicit toggle: no more guessing from emptiness,
|
||||
the human's chosen mode decides. A `fin == kandidat` check avoids a pointless rewrite/republish when
|
||||
nothing actually changed.
|
||||
- Panel (`vEinst()`): a `<label class="switch">` reading "automatisch" placed between the "Fahrgestellnummer
|
||||
(FIN)" label and the text input, in the same `.feld` row (owner's exact layout request) - the input gets
|
||||
`disabled` whenever automatic is on (`.feld input:disabled{opacity:.5;cursor:not-allowed}`, new rule) and
|
||||
a title tooltip explaining why, and re-enables the moment automatic is switched off. Wired via the same
|
||||
`data-*`/central-`change`-listener idiom as every other switch in this file (mirrors `data-steuerlast`).
|
||||
`CAR.finAutomatisch`/`p.fahrzeug.fin_automatisch` added to both directions of the profile↔CAR mapping.
|
||||
- `dienste.py`'s `profil_schreiben` handler (not just `entitaeten_schreiben`) now also calls
|
||||
`identitaet.fin_ableiten_und_uebernehmen()` after writing - this is the actual mechanism that makes
|
||||
flipping the switch itself take effect immediately: toggling it goes through the ordinary profile-save
|
||||
path (same as every other "Fahrzeug einrichten" field), so the just-written `fin_automatisch` flag is
|
||||
what the very next derivation attempt reads.
|
||||
|
||||
**Verified live end-to-end, all four transitions, not just read from source**: (1) automatic on, empty
|
||||
underlying state → real VIN appears, log line fires; (2) toggled off → input's `disabled`/`opacity`
|
||||
computed style confirmed `false`/`1` (genuinely editable, not just visually similar - the app's normal
|
||||
`.feld input` background already looks grey by design, so a screenshot alone wasn't proof); (3) typed a
|
||||
fake value while off, saved → persisted to disk exactly as typed, confirmed via direct file read, log
|
||||
stayed silent (sanity-check correctly skipped); (4) toggled back on → immediately overwrote the fake value
|
||||
back to the real derived VIN, both the on-disk file and a fresh log line (`Fahrgestellnummer automatisch
|
||||
... übernommen: WUAZZZF48PA902804`) confirmed within seconds, no manual save/reload needed. Layout also
|
||||
confirmed clean at both a real 1400px desktop width and the default phone-width layout - no overflow at
|
||||
either. `node --check` clean, manifest bumped to `2026.8.28.16`, `audi_ha_test` restarted and confirmed
|
||||
clean via log.
|
||||
|
||||
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
|
||||
|
||||
@@ -219,8 +219,14 @@ def registrieren(hass: HomeAssistant, k: Koordinator) -> None:
|
||||
Die Oberfläche hält Profil, Fahrzeugdaten und Einstellungen ohnehin
|
||||
als einen zusammenhängenden Stand im Speicher und schickt ihn nach
|
||||
jeder Änderung komplett zurück - deshalb ein einziger Dienst statt
|
||||
eines eigenen je Feld."""
|
||||
eines eigenen je Feld.
|
||||
|
||||
Danach auch ein Versuch, die Fahrgestellnummer zu synchronisieren
|
||||
(identitaet.py) - unter anderem der Weg, über den das Umschalten von
|
||||
"automatisch" selbst wirkt: dieser Dienst schreibt auch das gerade
|
||||
geänderte fin_automatisch-Flag, bevor die Ableitung es liest."""
|
||||
await k.ablage.profil_schreiben(_objekt(aufruf.data["profil_json"]))
|
||||
await identitaet.fin_ableiten_und_uebernehmen(k.hass, k.zuordnung, k.ablage)
|
||||
await k.profil_veroeffentlichen()
|
||||
|
||||
async def entitaeten_schreiben(aufruf: ServiceCall) -> None:
|
||||
|
||||
@@ -134,6 +134,10 @@ function profilZuCar(p, status) {
|
||||
const reifenSatz = (k) => p.reifen.saetze[k] || {};
|
||||
return {
|
||||
fin: p.fahrzeug.fin,
|
||||
// Default true (auch für Profile von vor dieser Funktion, die das Feld
|
||||
// noch nicht kennen) - deckt sich mit identitaet.py's eigenem
|
||||
// fahrzeug.get("fin_automatisch", True).
|
||||
finAutomatisch: p.fahrzeug.fin_automatisch !== false,
|
||||
details: p.fahrzeug.details,
|
||||
erstzulassung: p.fahrzeug.erstzulassung,
|
||||
hu: p.fahrzeug.hauptuntersuchung_faellig,
|
||||
@@ -198,6 +202,7 @@ function configCarZuProfil(vorherigesProfil) {
|
||||
p.fahrzeug.kennzeichen = CONFIG.kennzeichen;
|
||||
p.fahrzeug.hauptuntersuchung_faellig = CAR.hu;
|
||||
p.fahrzeug.fin = CAR.fin;
|
||||
p.fahrzeug.fin_automatisch = CAR.finAutomatisch;
|
||||
p.fahrzeug.erstzulassung = CAR.erstzulassung;
|
||||
p.fahrzeug.ausfuehrung = CONFIG.ausfuehrung;
|
||||
p.einstellungen.uebersichtsbild = CONFIG.startbild;
|
||||
@@ -2520,7 +2525,14 @@ function vEinst() {
|
||||
<div class="tile"><span class="label">Fahrzeug einrichten</span>
|
||||
${einrichtenOffen ? `
|
||||
<div class="feld" style="margin-top:16px"><label for="einFin">Fahrgestellnummer (FIN)</label>
|
||||
<input id="einFin" value="${esc(CAR.fin || "")}" data-einrichten="fin"></div>
|
||||
<span style="display:flex;align-items:center;gap:10px;min-width:0">
|
||||
<span style="display:flex;align-items:center;gap:6px;flex:0 0 auto">
|
||||
<span class="label" style="margin:0;text-transform:none;letter-spacing:0;font-size:12.5px;color:var(--fg2)">automatisch</span>
|
||||
<label class="switch"><input type="checkbox" data-fin-automatisch ${CAR.finAutomatisch ? "checked" : ""}><span class="track"></span></label>
|
||||
</span>
|
||||
<input id="einFin" value="${esc(CAR.fin || "")}" data-einrichten="fin" ${CAR.finAutomatisch ? "disabled" : ""}
|
||||
title="${CAR.finAutomatisch ? "Wird automatisch aus den im Setup zugeordneten Sensoren ermittelt - \"automatisch\" ausschalten, um von Hand einzutragen." : ""}">
|
||||
</span></div>
|
||||
<div class="feld"><label for="einModell">Modell</label>
|
||||
<select id="einModell" data-einrichten="modell">
|
||||
${(FAHRZEUG_MODELLE.includes(CONFIG.fahrzeugtitel) ? FAHRZEUG_MODELLE : [CONFIG.fahrzeugtitel, ...FAHRZEUG_MODELLE])
|
||||
@@ -5372,6 +5384,7 @@ function ereignisseVerdrahten() {
|
||||
if (e.target.dataset.steuerzeit !== undefined) { CAR.steuer.zeitraum = e.target.value; profilSpeichern(); render(); return; }
|
||||
if (e.target.dataset.steuerfaellig !== undefined) { CAR.steuer.faellig = e.target.value; profilSpeichern(); render(); return; }
|
||||
if (e.target.dataset.steuerlast !== undefined) { CAR.steuer.lastschrift = e.target.checked; profilSpeichern(); render(); return; }
|
||||
if (e.target.dataset.finAutomatisch !== undefined) { CAR.finAutomatisch = e.target.checked; profilSpeichern(); render(); return; }
|
||||
const vteil = e.target.dataset.vteil;
|
||||
if (vteil) {
|
||||
const [i, feld] = vteil.split("|");
|
||||
|
||||
@@ -702,6 +702,10 @@ button.leaf:active { background: var(--tile-2); }
|
||||
appearance: none;
|
||||
}
|
||||
.feld input:focus, .feld select:focus, .notiz:focus { outline: none; border-color: var(--red); }
|
||||
/* z. B. Fahrgestellnummer bei aktivem "automatisch"-Schalter: der Wert kommt
|
||||
aus den zugeordneten Sensoren, Handeingabe ist erst nach dem Ausschalten
|
||||
wieder möglich - das Feld soll das auf den ersten Blick zeigen. */
|
||||
.feld input:disabled { opacity: .5; cursor: not-allowed; }
|
||||
/* Label und Eingabe untereinander statt nebeneinander, die Eingabe über die
|
||||
volle Zeilenbreite - für Felder, deren Wert typischerweise länger ist als
|
||||
ein Kurztext (Kommentar bei Sommer-/Winterrädern, live wie archiviert).
|
||||
|
||||
@@ -9,18 +9,27 @@ Kanal-Konfiguration ebenfalls liefern. Ein eigenes Setup-Feld dafür würde vom
|
||||
Nutzer verlangen, denselben Wert doppelt zu suchen und zuzuordnen, obwohl die
|
||||
Information am Gerät der ohnehin schon zugeordneten Sensoren hängt.
|
||||
|
||||
Stattdessen wird bei jedem Start und nach jeder Setup-Änderung das
|
||||
Geräte-Register der AKTUELL zugeordneten Sensoren durchsucht (nicht aller
|
||||
Entitäten der Instanz - nur was Sensorzuordnung tatsächlich referenziert):
|
||||
für jede zugeordnete Entity-ID wird ihr Gerät nachgeschlagen und daraus eine
|
||||
FIN-Kandidatin extrahiert (Geräte-Identifier oder Seriennummer, die wie eine
|
||||
echte FIN aussieht - 17 Zeichen, ohne die im VIN-Standard verbotenen
|
||||
Ein Schalter "automatisch" neben dem FIN-Feld ("Fahrzeug einrichten",
|
||||
`fahrzeug.fin_automatisch` im Profil, Standard an) entscheidet, ob diese
|
||||
Ableitung überhaupt läuft. An: bei jedem Start, nach jeder Setup-Änderung und
|
||||
nach jedem Profil-Speichern (also auch direkt beim Umschalten des Schalters
|
||||
selbst) wird das Geräte-Register der AKTUELL zugeordneten Sensoren durchsucht
|
||||
(nicht aller Entitäten der Instanz - nur was Sensorzuordnung tatsächlich
|
||||
referenziert): für jede zugeordnete Entity-ID wird ihr Gerät nachgeschlagen und
|
||||
daraus eine FIN-Kandidatin extrahiert (Geräte-Identifier oder Seriennummer, die
|
||||
wie eine echte FIN aussieht - 17 Zeichen, ohne die im VIN-Standard verbotenen
|
||||
Verwechslungsbuchstaben I/O/Q). Liefern mehrere Geräte je eine Kandidatin,
|
||||
müssen sie übereinstimmen; weichen sie ab, wird nur gewarnt statt geraten -
|
||||
dieselbe "kein Vorschlag ist besser als ein falscher" Haltung wie an anderen
|
||||
Stellen dieser App (siehe z. B. entitaetIdKurz() im Panel). Ist im Profil
|
||||
bereits eine FIN eingetragen, wird sie nie überschrieben - von Hand
|
||||
eingetragene Werte haben Vorrang vor einer Ableitung.
|
||||
Stellen dieser App (siehe z. B. entitaetIdKurz() im Panel). Solange
|
||||
"automatisch" an ist, gilt der abgeleitete Wert als maßgeblich und wird bei
|
||||
jedem Lauf aktualisiert (auch über einen vorherigen Wert hinweg) - genau das
|
||||
ist der Zweck des Schalters, nicht ein "nur einmal beim leeren Feld" wie in
|
||||
einer früheren Fassung dieses Moduls.
|
||||
|
||||
Aus: der Schalter deaktiviert die Ableitung vollständig, einschließlich des
|
||||
Mehrfach-Sensor-Abgleichs oben - das Feld wird dann nirgends mehr angefasst
|
||||
und bleibt reine Handeingabe wie jedes andere Profilfeld auch.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -93,29 +102,35 @@ def fin_kandidaten_ermitteln(hass: HomeAssistant, werte: Sensorzuordnung) -> dic
|
||||
async def fin_ableiten_und_uebernehmen(
|
||||
hass: HomeAssistant, zuordnung: Zuordnung, ablage: Ablage
|
||||
) -> None:
|
||||
"""Trägt die FIN automatisch ins Profil ein, wenn sie dort noch fehlt und
|
||||
sich aus den zugeordneten Sensoren eindeutig ableiten lässt."""
|
||||
"""Hält die FIN im Profil mit den zugeordneten Sensoren synchron, solange
|
||||
fahrzeug.fin_automatisch nicht ausdrücklich abgeschaltet ist (Standard:
|
||||
an)."""
|
||||
profil = await ablage.profil_lesen()
|
||||
if profil is None:
|
||||
return
|
||||
fahrzeug = profil.setdefault("fahrzeug", {})
|
||||
if fahrzeug.get("fin"):
|
||||
if not fahrzeug.get("fin_automatisch", True):
|
||||
return
|
||||
|
||||
kandidaten = fin_kandidaten_ermitteln(hass, zuordnung.werte)
|
||||
eindeutig = set(kandidaten.values())
|
||||
if not eindeutig:
|
||||
# Nichts gefunden - einen eventuell schon eingetragenen Wert nicht
|
||||
# löschen, nur weil gerade kein Kandidat da ist (z. B. Sensoren
|
||||
# kurzzeitig nicht verfügbar).
|
||||
return
|
||||
if len(eindeutig) > 1:
|
||||
_LOGGER.warning(
|
||||
"Fahrgestellnummer nicht eindeutig aus den zugeordneten Sensoren "
|
||||
"ableitbar (gefunden: %s) - bitte in \"Fahrzeug einrichten\" von "
|
||||
"Hand eintragen.",
|
||||
"ableitbar (gefunden: %s) - bitte \"automatisch\" in \"Fahrzeug "
|
||||
"einrichten\" ausschalten und von Hand eintragen.",
|
||||
", ".join(sorted(eindeutig)),
|
||||
)
|
||||
return
|
||||
|
||||
fin = eindeutig.pop()
|
||||
if fahrzeug.get("fin") == fin:
|
||||
return
|
||||
fahrzeug["fin"] = fin
|
||||
await ablage.profil_schreiben(profil)
|
||||
_LOGGER.info(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "audi_dashboard",
|
||||
"name": "Audi Dashboard",
|
||||
"version": "2026.8.28.15",
|
||||
"version": "2026.8.28.16",
|
||||
"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"],
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
"fahrzeug": {
|
||||
"fin": "",
|
||||
"fin_automatisch": true,
|
||||
"kennzeichen": "",
|
||||
"titel": "RS 4 Avant competition",
|
||||
"zusatz": "Avant competition",
|
||||
|
||||
Reference in New Issue
Block a user