"""Die Entitäten, über die beide Oberflächen die Daten der App lesen. Das Frontend liest hass.states["sensor.audi_dashboard_profil"] usw. direkt und holt sich die Nutzlast aus dem Attribut `daten` - kein Dienstaufruf mit Rückgabewert, dessen Verhalten aus Sicht des Browsers nicht durchgängig dokumentiert ist. WARUM ECHTE ENTITÄTEN UND NICHT NUR EINTRÄGE IN DER ZUSTANDSMASCHINE -------------------------------------------------------------------- Wegen einer einzigen Zeile: `_unrecorded_attributes`. Die Nutzlast dieser Entitäten ist groß - das Fahrtenarchiv wächst über die Jahre auf Hunderte Kilobyte. Home Assistant zeichnet Zustands-Attribute standardmäßig bei jeder Änderung in der Recorder-Datenbank auf und warnt ab ~16 KB, dass genau das Datenbankprobleme macht. In der pyscript-Fassung war das eine offene Flanke: jede Veröffentlichung schrieb den kompletten Bestand erneut in die Datenbank, alle 60 Sekunden. Nur eine Entität, die zu einer Integration gehört, kann Attribute von der Aufzeichnung ausnehmen. Ein roher Eintrag in der Zustandsmaschine (was pyscripts state.set() macht) kann das nicht. Die Nutzlast geht damit weiterhin an jede Oberfläche, landet aber nie in der Datenbank. Die Zustände selbst sind bewusst schlicht ("aktuell"): der Inhalt steckt im Attribut, der Zustand sagt nur, dass etwas da ist. Bei den drei Reifen-Entitäten und der App-Version ist der Zustand dagegen der eigentliche Wert - die sind zur Kontrolle in Entwicklerwerkzeuge -> Zustände und für eigene Automationen des Nutzers gedacht. """ from __future__ import annotations from dataclasses import dataclass from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import UnitOfLength from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( DOMAIN, E_APP_VERSION, E_BATTERIEVERLAUF, E_BELEG_ERGEBNIS, E_FAHRTEN, E_FAHRZEUGSTATUS, E_IMPORT_STATUS, E_PROFIL, E_REIFEN_AKTIV, E_REIFEN_SOMMER, E_REIFEN_WINTER, E_TANKVORGAENGE, E_ZUORDNUNG, SIGNAL_AKTUALISIERT, ) from .koordinator import Koordinator @dataclass(frozen=True) class Beschreibung: schluessel: str name: str icon: str einheit: str | None = None kategorie: EntityCategory | None = None BESCHREIBUNGEN: tuple[Beschreibung, ...] = ( Beschreibung(E_PROFIL, "Audi Dashboard Profil", "mdi:car-info"), Beschreibung(E_FAHRTEN, "Audi Dashboard Fahrten", "mdi:road-variant"), Beschreibung(E_TANKVORGAENGE, "Audi Dashboard Tankvorgänge", "mdi:gas-station"), Beschreibung(E_FAHRZEUGSTATUS, "Audi Dashboard Fahrzeugstatus", "mdi:car-connected"), Beschreibung(E_BATTERIEVERLAUF, "Audi Dashboard Batterieverlauf", "mdi:car-battery"), Beschreibung(E_REIFEN_SOMMER, "Audi Dashboard Reifen Sommer km", "mdi:tire", UnitOfLength.KILOMETERS), Beschreibung(E_REIFEN_WINTER, "Audi Dashboard Reifen Winter km", "mdi:snowflake", UnitOfLength.KILOMETERS), Beschreibung(E_REIFEN_AKTIV, "Audi Dashboard Reifen aktiver Satz", "mdi:tire"), Beschreibung(E_ZUORDNUNG, "Audi Dashboard Entitäten", "mdi:link-variant", None, EntityCategory.DIAGNOSTIC), Beschreibung(E_BELEG_ERGEBNIS, "Audi Dashboard Beleg Ergebnis", "mdi:receipt-text", None, EntityCategory.DIAGNOSTIC), Beschreibung(E_IMPORT_STATUS, "Audi Dashboard Import Status", "mdi:database-import", None, EntityCategory.DIAGNOSTIC), Beschreibung(E_APP_VERSION, "Audi Dashboard App Version", "mdi:tag-outline", None, EntityCategory.DIAGNOSTIC), ) async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: koordinator: Koordinator = entry.runtime_data async_add_entities( AudiEntitaet(koordinator, beschreibung) for beschreibung in BESCHREIBUNGEN ) class AudiEntitaet(SensorEntity): """Eine Entität je Datenbestand. Zustand kurz, Inhalt im Attribut `daten`.""" # Der eigentliche Grund für echte Entitäten - siehe Kopfkommentar. _unrecorded_attributes = frozenset({"daten"}) # Bewusst KEIN has_entity_name: die Entity-IDs (sensor.audi_dashboard_*) # sind ein Vertrag mit beiden Oberflächen (siehe const.py). Mit # has_entity_name würde Home Assistant den Gerätenamen davorsetzen und die # IDs hingen am - vom Nutzer änderbaren - Gerätenamen. _attr_has_entity_name = False _attr_should_poll = False def __init__(self, koordinator: Koordinator, beschreibung: Beschreibung) -> None: self._koordinator = koordinator self._beschreibung = beschreibung self._attr_name = beschreibung.name self._attr_icon = beschreibung.icon self._attr_native_unit_of_measurement = beschreibung.einheit self._attr_entity_category = beschreibung.kategorie self._attr_unique_id = f"{koordinator.entry.entry_id}_{beschreibung.schluessel}" # Die gewünschte Objekt-ID ausdrücklich vorgeben, statt sie aus dem # Namen ableiten zu lassen: aus dem Namen käme zwar dasselbe heraus, # aber nur solange niemand den Namen anfasst. self.internal_integration_suggested_object_id = ( f"audi_dashboard_{beschreibung.schluessel}" ) self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, koordinator.entry.entry_id)}, name="Audi Dashboard", manufacturer="Audi Dashboard", sw_version=koordinator.version, entry_type=None, ) async def async_added_to_hass(self) -> None: self.async_on_remove( async_dispatcher_connect( self.hass, f"{SIGNAL_AKTUALISIERT}_{self._beschreibung.schluessel}", self._neu_zeichnen, ) ) @callback def _neu_zeichnen(self) -> None: self.async_write_ha_state() def _wert(self) -> tuple[str, object]: return self._koordinator.werte.get(self._beschreibung.schluessel, ("unbekannt", None)) @property def available(self) -> bool: """Bis zur ersten Veröffentlichung gibt es nichts zu zeigen. "unavailable" ist hier ehrlicher als ein Platzhalterwert: die Oberfläche unterscheidet ausdrücklich zwischen "kein Wert" und "Wert unbekannt" und darf sich darauf verlassen.""" return self._beschreibung.schluessel in self._koordinator.werte @property def native_value(self) -> str: return self._wert()[0] @property def extra_state_attributes(self) -> dict[str, object]: return {"daten": self._wert()[1]}