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:
@@ -0,0 +1,123 @@
|
||||
"""Fahrgestellnummer (FIN/VIN) automatisch aus den zugeordneten Sensoren ableiten.
|
||||
|
||||
Die FIN ist ein reines Profilfeld (fahrzeugprofil.json, "Fahrzeug einrichten"),
|
||||
ohne eigene Sensor-Zuordnung im Setup-Menü - obwohl mindestens eine der ohnehin
|
||||
schon zugeordneten Integrationen sie meist bereits mitbringt: die
|
||||
cupra_eu_data_act-Integration trägt sie als Geräte-Identifier/Seriennummer
|
||||
(nicht als eigene Entität), der Teltonika FMM003 kann sie je nach Firmware-/
|
||||
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
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import fields
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .ablage import Ablage
|
||||
from .einstellungen import Sensorzuordnung
|
||||
from .zuordnung import Zuordnung
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# 17 Zeichen, Großbuchstaben ohne I/O/Q (im VIN-Standard wegen Verwechslungs-
|
||||
# gefahr mit 1/0 verboten) und Ziffern - erkennt eine FIN, ohne andere
|
||||
# Geräte-Identifier (z. B. "FMM003 - Testintegratoin") fälschlich zu matchen.
|
||||
_SIEHT_AUS_WIE_FIN = re.compile(r"^[A-HJ-NPR-Z0-9]{17}$")
|
||||
|
||||
|
||||
def _zugeordnete_entity_ids(werte: Sensorzuordnung) -> list[str]:
|
||||
"""Alle aktuell zugeordneten Entity-IDs, Listenfelder (Türen/Fenster ...)
|
||||
eingeschlossen - dieselbe Quelle, aus der auch das Panel seine
|
||||
Vergleichsbasis baut (setupAndereIdsOhneDomain() in audi-dashboard-app.js),
|
||||
hier nur serverseitig."""
|
||||
ids: list[str] = []
|
||||
for feld_info in fields(werte):
|
||||
wert = getattr(werte, feld_info.name)
|
||||
if isinstance(wert, list):
|
||||
ids.extend(eintrag for eintrag in wert if eintrag)
|
||||
elif wert:
|
||||
ids.append(wert)
|
||||
return ids
|
||||
|
||||
|
||||
def _fin_kandidat_fuer_geraet(geraet: dr.DeviceEntry) -> str | None:
|
||||
werte = [geraet.serial_number] + [kennung for _domain, kennung in geraet.identifiers]
|
||||
for wert in werte:
|
||||
if wert and _SIEHT_AUS_WIE_FIN.match(wert.upper()):
|
||||
return wert.upper()
|
||||
return None
|
||||
|
||||
|
||||
def fin_kandidaten_ermitteln(hass: HomeAssistant, werte: Sensorzuordnung) -> dict[str, str]:
|
||||
"""{Geräte-ID: FIN-Kandidatin} für jedes Gerät hinter einer aktuell
|
||||
zugeordneten Entität, dessen Geräte-Identifier/Seriennummer wie eine
|
||||
echte FIN aussieht."""
|
||||
ent_reg = er.async_get(hass)
|
||||
dev_reg = dr.async_get(hass)
|
||||
gefunden: dict[str, str] = {}
|
||||
for entity_id in _zugeordnete_entity_ids(werte):
|
||||
eintrag = ent_reg.async_get(entity_id)
|
||||
if not eintrag or not eintrag.device_id:
|
||||
continue
|
||||
geraet = dev_reg.async_get(eintrag.device_id)
|
||||
if not geraet:
|
||||
continue
|
||||
kandidat = _fin_kandidat_fuer_geraet(geraet)
|
||||
if kandidat:
|
||||
gefunden[geraet.id] = kandidat
|
||||
return gefunden
|
||||
|
||||
|
||||
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."""
|
||||
profil = await ablage.profil_lesen()
|
||||
if profil is None:
|
||||
return
|
||||
fahrzeug = profil.setdefault("fahrzeug", {})
|
||||
if fahrzeug.get("fin"):
|
||||
return
|
||||
|
||||
kandidaten = fin_kandidaten_ermitteln(hass, zuordnung.werte)
|
||||
eindeutig = set(kandidaten.values())
|
||||
if not eindeutig:
|
||||
return
|
||||
if len(eindeutig) > 1:
|
||||
_LOGGER.warning(
|
||||
"Fahrgestellnummer nicht eindeutig aus den zugeordneten Sensoren "
|
||||
"ableitbar (gefunden: %s) - bitte in \"Fahrzeug einrichten\" von "
|
||||
"Hand eintragen.",
|
||||
", ".join(sorted(eindeutig)),
|
||||
)
|
||||
return
|
||||
|
||||
fin = eindeutig.pop()
|
||||
fahrzeug["fin"] = fin
|
||||
await ablage.profil_schreiben(profil)
|
||||
_LOGGER.info(
|
||||
"Fahrgestellnummer automatisch aus den zugeordneten Sensoren übernommen: %s", fin
|
||||
)
|
||||
Reference in New Issue
Block a user