Fahrgestellnummer (FIN) automatisch aus den zugeordneten Sensoren ableiten

Neues identitaet.py: durchsucht das Geräte-Register der aktuell im Setup
zugeordneten Sensoren (cupra_eu_data_act, FMM003, ...) nach einem
VIN-förmigen Geräte-Identifier/Seriennummer, statt ein eigenes Setup-Feld zu
verlangen. Mehrere übereinstimmende Quellen werden akzeptiert, abweichende nur
geloggt statt geraten; eine bereits von Hand eingetragene FIN wird nie
überschrieben. Läuft nach jedem Start und nach jeder Setup-Änderung
(koordinator.py/dienste.py).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-28 11:47:08 +02:00
co-authored by Claude Sonnet 5
parent 8f88ccf581
commit c0acc836f0
5 changed files with 189 additions and 3 deletions
+50
View File
@@ -4394,6 +4394,56 @@ Verified live: `getBoundingClientRect()` on both real rendered `.v` blocks now r
Verified: `node --check` clean at both steps, manifest bumped `2026.8.28.12``.13``.14`, `audi_ha_test`
restarted and confirmed clean via log each time.
## AG. Fahrgestellnummer (FIN/VIN) automatisch aus den zugeordneten Sensoren abgeleitet (2026.8.28.15)
Owner: "It shall be available at the EU Data Act as well as FMM003. Read it from all sensors (check which
sensors are used in setup) and compare the VIN if it is matching. If VIN is empty, than use just the one
available." The FIN was, until now, a purely manual profile field (`fahrzeugprofil.json`'s `fahrzeug.fin`,
edited via "Fahrzeug einrichten") - no sensor role for it existed at all.
Investigated the real data in `audi_ha_test` before designing anything, rather than assuming a dedicated VIN
*entity* exists on either integration: neither `cupra_eu_data_act` nor the FMM003 device expose a `sensor.*`
with "vin"/"fahrgestell"/"chassis" in its name or a matching state attribute (checked via a live
`hass.callWS` scan of every entity on both devices). The VIN **is** present, though - on the
`cupra_eu_data_act` device itself, in the HA **device registry**: `identifiers: [["cupra_eu_data_act",
"WUAZZZF48PA902804"]]` and `serial_number: "WUAZZZF48PA902804"`, exactly matching this test profile's own
manually-entered FIN. The FMM003 device in this specific test container does not carry a VIN-shaped
identifier (its own identifier is just its configured integration name) - real hardware may differ depending
on firmware/channel configuration (Teltonika Codec8 Extended has a dedicated VIN AVL parameter), which is
exactly why the owner asked for "check both, don't assume one".
Built on that finding rather than adding a new Setup field: a new `identitaet.py` derives the FIN from the
**device registry of whichever sensors are already mapped** in Setup, no separate FIN sensor role to pick.
`_zugeordnete_entity_ids()` walks every field of the live `Sensorzuordnung` dataclass generically (via
`dataclasses.fields()`, list-typed fields like `TUER_SENSOREN` included) to get every entity ID currently in
use; for each, its owning device is resolved (entity registry → device registry) and checked for a
VIN-shaped identifier/serial number (`_SIEHT_AUS_WIE_FIN`: 17 chars, A-Z minus I/O/Q plus digits - the real
VIN standard's own exclusion of those three letters, chosen specifically so an unrelated device identifier
like "FMM003 - Testintegratoin" can never false-match). Distinct candidates found across all mapped devices
are collected into a set: zero → no-op (nothing found yet); exactly one → adopted automatically, but **only
if the profile's own FIN is still empty** (a manually-entered value is never overwritten - matches this
project's "human input always wins over automation" precedent, e.g. `_fahrt_screenen()`'s `edited_fields`
protection); more than one distinct value → logged as a warning naming both, deliberately not guessed at
(same "no suggestion beats a wrong one" stance as `entitaetIdKurz()`'s own fallback). Wired into two places
so it re-attempts whenever it has a real chance of succeeding: once after every HA start (`koordinator.py`'s
`_nach_start()`, alongside `fahrterkennung.nach_neustart_fortsetzen()` - deliberately not in `starten()`
itself, for the same device/entity-registry-not-ready-yet reason `trigger_neu_binden()`'s own docstring
already explains) and again after every Setup-menu save (`dienste.py`'s `entitaeten_schreiben` - a mapping
change may be exactly what makes derivation newly possible). Both call sites re-publish the profile
afterward so a freshly-derived FIN reaches the frontend immediately, not after the next 60s safety-net tick.
**Verified live, not just read from source** (this file's own section S lesson): backed up the test
container's real `fahrzeugprofil.json`, blanked `fahrzeug.fin`, restarted - log showed
`Fahrgestellnummer automatisch aus den zugeordneten Sensoren übernommen: WUAZZZF48PA902804`, the on-disk
file and the published `sensor.audi_dashboard_profil` entity both confirmed via direct query, no stale
frontend needed. Restored the original file byte-for-byte afterward and restarted once more to confirm a
clean, silent (no re-derivation - the guard against overwriting an already-set FIN held) boot. `py_compile`
clean on all three touched files (`identitaet.py` new, `koordinator.py`, `dienste.py`). No `services.yaml`
change needed (no new service, just two existing handlers doing more internally) and no frontend change
needed either - the "Fahrgestellnummer (FIN)" field in "Fahrzeug einrichten" already reads/writes this exact
profile field, so it now simply arrives pre-filled instead of blank, and stays fully editable/correctable by
hand same as before.
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