diff --git a/AGENTS.md b/AGENTS.md index 239b0a8..4318a07 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md — Project state, review findings, open items, and working rules -**Last updated: 2026-08-11** (full-repo review). This file is the entry point for every new agent +**Last updated: 2026-08-12**. This file is the entry point for every new agent session: what this repo is, what is finished, what is missing, and how to work here. Detail lives in the linked documents — this file points, it does not duplicate. @@ -119,16 +119,24 @@ capacitor/iframe/browser (`umgebung.ts`), types + entity table (`types.ts`), fac ### HA panel — known gaps (most also in SPECIFICATION.md §7) -- **GPS is dead schema:** `start_lat/lon`, addresses, `route`, `avg_speed_kmh` never populated. - The trip-detail map draws a **fabricated** line via `fakeTrack()` - (`audi-dashboard-app.js:414`) — not a real track. +- **GPS is dead schema for trip records specifically:** `start_lat/lon`, addresses, `route`, + `avg_speed_kmh` on individual trips never populated. The trip-detail map draws a **fabricated** + line via `fakeTrack()` (`audi-dashboard-app.js:414`) — not a real track. (Unrelated: the + Übersicht's live Standort-Kachel is no longer in this state — `STANDORT_TRACKER` now points at + a real `device_tracker`, see section B/C. This item is only about per-trip route data, which + nothing currently populates.) - **No GPS fallback in trip completion:** trips without an odometer match stay `status="offen"` forever (`modules/fahrtabschluss_logik.py:16-20`). - **RAM-only state:** running trip (`_fahrt_start_ts`) and fuel low-water-mark (`_tiefststand_pct`) do not survive an HA restart. Deliberately deferred hardening. -- **Inactive features:** `BATTERIE_SENSOR = ""` (entire battery-history feature is a no-op) and - `UPDATE_REPO_URL = ""` (self-update inactive), both in `pyscript/modules/einstellungen.py`. - `www/bilder/` has no vehicle photos (all slots show placeholders); `steuer.faellig` unset. +- **Inactive features:** `UPDATE_REPO_URL = ""` (self-update inactive) in + `pyscript/modules/einstellungen.py`. `www/bilder/` has no vehicle photos (all slots show + placeholders); `steuer.faellig` unset. (`BATTERIE_SENSOR` was the same kind of gap until + 2026-08-12 — now wired to the FMM003's `external_power_voltage`, see section B/C below. Several + *other* fields are newly inactive since the same date for a different reason — the + `TommiG1/HA_VAG-EU-Data-Act` integration they depended on was dropped: `KM_SENSOR`, + `TANK_SENSOR`, `RANGE_SENSOR`, `REFRESH_BUTTON`, all door/window/lock sensors, oil-change/ + inspection sensors. All are configurable again via the new Setup menu, see section C.) - **Robustness:** `profil_lesen()` in `modules/profil.py` does not handle a missing/corrupt `fahrzeugprofil.json` — all callers throw. - **Tests:** only the receipt parser is tested (`data/tests/test_shell_beleg_parser.py`, 10 real @@ -144,6 +152,12 @@ capacitor/iframe/browser (`umgebung.ts`), types + entity table (`types.ts`), fac - `INSTALL.md` step 4 names variables that no longer exist (`DOORS_SENSOR`, `WINDOWS_SENSOR`, `LOCK_ENTITY`, `BATTERY_VOLTAGE_SENSOR`) — actual names: `TUER_SENSOREN`/`FENSTER_SENSOREN`/ `TUERSCHLOSS_SENSOREN`/`BATTERIE_SENSOR`. +- **(worse than drift, since 2026-08-12)** `INSTALL.md` step 4 and its troubleshooting table + still walk through configuring `WLAN_SENSOR` and the `TommiG1/HA_VAG-EU-Data-Act` entities + (`KM_SENSOR`, `TANK_SENSOR`, etc.) — both are gone (see section B). A fresh install following + this doc today would configure settings that no longer exist and skip `ZUENDUNG_SENSOR`/ + `STANDORT_TRACKER`, which now actually matter. Needs a real rewrite, not a find-replace — not + done yet, flagging so it isn't mistaken for accurate. - `homeassistant/README.md:110` mentions the removed 97% full-tank rule (removal documented in `belegverarbeitung.py:18-20`); the README file list omits 5 pyscript files. - Obsolete comment `belegverarbeitung.py:41` ("TODO: Datei ablegen" — file has long existed). @@ -234,8 +248,33 @@ capacitor/iframe/browser (`umgebung.ts`), types + entity table (`types.ts`), fac self-hosted broker is picked back up later. - [ ] Capture the first real Codec JSON message (`mosquitto_sub`/MQTT Explorer) and build the field mapping from it — **do not guess beforehand** (explicit decision) -- [ ] Move trip detection to FMM003 ignition (reuse the `fahrterkennung.py` pattern, keep pause - tolerance); then remove `sensor.iphone_wifi_connection` from `einstellungen.py` +- [x] Move trip detection to FMM003 ignition — done 2026-08-12. `fahrterkennung.py` rewritten: + trigger is now `einstellungen.ZUENDUNG_SENSOR` + (`binary_sensor.testzone_fmm003_engine_ignition_or_acc_status`, on = trip running) instead + of the iPhone WLAN sensor; same `task.unique()` pause-tolerance pattern kept unchanged. + `sensor.iphone_wifi_connection` and the `fahrzeug.wlan_name` profile field are fully removed + (backend and frontend). At the same time, all entity IDs sourced from the now-abandoned + `TommiG1/HA_VAG-EU-Data-Act` HACS integration were blanked in `einstellungen.py` + (`KM_SENSOR`, `TANK_SENSOR`, `RANGE_SENSOR`, `REFRESH_BUTTON`, door/window/lock sensors, + oil-change/inspection sensors) — the consuming features (Übersicht tiles, Reifenzähler, + automatic Tankerkennung, Fahrtabschluss-Screening) are untouched and degrade gracefully to + "unbekannt"/inactive per the existing `zustand_oder_none()` convention, rather than being + deleted; `fahrtabschluss.py`/`tankerkennung.py`/`reifenzaehler.py` now guard their + `@state_trigger(f"{einstellungen.KM_SENSOR}")`-style decorators with + `if einstellungen.KM_SENSOR:` (untested previously whether pyscript tolerates an empty + trigger string — not worth relying on). `STANDORT_TRACKER` now points at the real + `device_tracker.testzone_fmm003` and `BATTERIE_SENSOR` at + `sensor.testzone_fmm003_external_power_voltage` (the vehicle's 12V bus voltage as measured + by the tracker — NOT `sensor.testzone_fmm003_battery_voltage`, which is the tracker's own + internal backup cell and unrelated to the car). Real entity list for this device: the user's + `Entiaeten.csv` export (OneDrive, not in this repo). Verified in `audi_ha_test`: pyscript + reload (`pyscript.reload`) produced no errors/warnings for any of the changed files; the + Übersicht correctly shows "Status unbekannt" and em-dash placeholders for the now-blank VAG + fields instead of crashing (that container itself has no `device_tracker.testzone_fmm003` + registered, so Standort still shows "Kein GPS-Signal" there — expected, the real HA instance + has it). **Follow-up still needed:** `INSTALL.md` step 4 and its troubleshooting table still + document the old WLAN/TommiG1 entity list — now actively wrong, not just stale (see + Documentation drift below). ### C) Maintain the existing HA panel (low priority — being replaced) @@ -276,6 +315,36 @@ capacitor/iframe/browser (`umgebung.ts`), types + entity table (`types.ts`), fac a few seconds in the `audi_ha_test` container (no crash, stays on "Adresse wird ermittelt …" indefinitely) — likely just that container's outbound network to Nominatim specifically; unconfirmed whether this reproduces on the real HA instance. +- [x] Setup menu (Einstellungen → Fahrzeug einrichten → last item when expanded) — done + 2026-08-12. Lets the user map every sensor role the app uses (~18 roles, 3 of them 4-item + door/window/lock lists) to real HA entities from a searchable dropdown, instead of hand- + editing `einstellungen.py`. New backend module `pyscript/modules/entitaeten.py`: a + `FELDER` catalog (label, hint, expected domain/device_class/unit, keywords, whether it's + one of the trigger-bound fields) plus JSON override I/O (`data/entitaeten.json`, same + atomic-write pattern as `profil.py`) and `overrides_anwenden()`, which does + `setattr(einstellungen, key, value)` on the already-imported `einstellungen` module — every + existing consumer does `import einstellungen` + live attribute access (verified: none use + `from einstellungen import X`), so this needs no changes to any consumer file. New service + `audi_dashboard_entitaeten_schreiben` (`frontend_api.py`) and publication + `entitaeten_veroeffentlichen()` (`frontend_veroeffentlichung.py`) follow the project's + existing read-via-state/write-via-service pattern. Frontend: new popup (`vSetupPopup()`, + modeled on the existing `.sheet` pattern) with a from-scratch searchable-combobox component + (none existed in the repo before) — client-side only, built directly from `HASS.states`, no + new backend read service; a global "Nur passende Sensoren anzeigen" switch hard-filters + candidates by domain/unit, a keyword+domain+unit score ranks and auto-suggests for empty + fields. Deliberately does NOT auto-apply changes for the three trigger-bound fields + (`ZUENDUNG_SENSOR`/`KM_SENSOR`/`TANK_SENSOR` — `@state_trigger` bakes the entity ID in at + module-load time) — the UI shows an inline "wirkt erst nach Neustart" hint instead of + attempting a risky self-`pyscript.reload()` from inside a running service call. Two real + bugs found and fixed during live verification, both now also pushed to the Claude Design + project: (1) a full popup re-render on every keystroke fought the search input for focus — + fixed by patching just the candidate-list DOM node on `input`, matching the same fix the + existing `data-tankpreisfeld` handler already uses for the identical reason; (2) the popup + background used `--tile-2`, which Design had since changed to a translucent + `rgba(255,255,255,.1)` in the night theme (for the frosted-glass tile look elsewhere) — made + the whole popup partially see-through against the app content behind it. Fixed by switching + to `--tile-deckend` (Design's own opaque-surface token, already used by `.standortmenu` for + the same reason), with `var(--canvas)` as a defensive fallback. - [ ] Fix documentation drift (statistics claim, INSTALL variable names, README gaps, obsolete TODO comment) — text-only changes - [ ] Harden `profil_lesen()` against missing/corrupt `fahrzeugprofil.json` diff --git a/homeassistant/data/fahrzeugprofil.example.json b/homeassistant/data/fahrzeugprofil.example.json index feb1488..901e04d 100644 --- a/homeassistant/data/fahrzeugprofil.example.json +++ b/homeassistant/data/fahrzeugprofil.example.json @@ -10,7 +10,6 @@ "details": "2.9 TFSI quattro competition · 331 kW · 450 PS", "erstzulassung": "", "hauptuntersuchung_faellig": "", - "wlan_name": "", "tankvolumen_liter": 58, "ausfuehrung": "competition" }, diff --git a/homeassistant/pyscript/fahrtabschluss.py b/homeassistant/pyscript/fahrtabschluss.py index c215168..07eb09a 100644 --- a/homeassistant/pyscript/fahrtabschluss.py +++ b/homeassistant/pyscript/fahrtabschluss.py @@ -13,11 +13,15 @@ import einstellungen import fahrtabschluss_logik -@state_trigger(f"{einstellungen.KM_SENSOR}") -def kilometerstand_geaendert(value=None, old_value=None): - if value is None or old_value is None: - return - fahrtabschluss_logik.screening_durchfuehren(einstellungen.KM_SENSOR) +# Ohne konfigurierten Kilometerstand-Sensor keinen Trigger registrieren - +# eine leere Entity-ID als @state_trigger-Ausdruck ist ungetestetes +# Verhalten, siehe gleiches Muster in fahrterkennung.py. +if einstellungen.KM_SENSOR: + @state_trigger(f"{einstellungen.KM_SENSOR}") + def kilometerstand_geaendert(value=None, old_value=None): + if value is None or old_value is None: + return + fahrtabschluss_logik.screening_durchfuehren(einstellungen.KM_SENSOR) @service diff --git a/homeassistant/pyscript/fahrterkennung.py b/homeassistant/pyscript/fahrterkennung.py index 78f7928..2d7ce9d 100644 --- a/homeassistant/pyscript/fahrterkennung.py +++ b/homeassistant/pyscript/fahrterkennung.py @@ -1,17 +1,25 @@ -"""Fahrterkennung über die WLAN-Verbindung zum Fahrzeug (§7.1). +"""Fahrterkennung über den Zündungs-/ACC-Status des FMM003 (§7.1). -Start: der WLAN-Sensor der Companion App meldet den in den Einstellungen -hinterlegten Fahrzeug-WLAN-Namen (Fahrzeugprofil, Feld fahrzeug.wlan_name). -Ende: die Verbindung bricht ab und kommt binnen der Pausenzeit nicht zurück. +Start: die Zündungs-Entität (einstellungen.ZUENDUNG_SENSOR) wechselt auf +"on". Ende: sie wechselt auf "off" und kommt binnen der Pausenzeit nicht +zurück. -Die Pausenregel wird über task.unique() umgesetzt: jede neue Änderung des -WLAN-Sensors killt eine noch laufende, wartende Bestätigung des vorherigen -Ereignisses. Kommt die Verbindung während der Wartezeit zurück, wird dadurch -die wartende Ende-Bestätigung automatisch abgebrochen, ohne dass die Fahrt -beendet wird - das entspricht der Pausenregel aus §7.1. +2026-08-12: ersetzt die frühere WLAN-basierte Erkennung (Vergleich des vom +iPhone gemeldeten verbundenen WLANs gegen den im Fahrzeugprofil hinterlegten +Namen) - die Zündung ist ein direkteres, von jeder Companion-App-Eigenheit +unabhängiges Signal. + +Die Pausenregel wird über task.unique() umgesetzt: jede neue Änderung der +Zündungs-Entität killt eine noch laufende, wartende Bestätigung des +vorherigen Ereignisses. Kommt die Zündung während der Wartezeit zurück, wird +dadurch die wartende Ende-Bestätigung automatisch abgebrochen, ohne dass die +Fahrt beendet wird - das entspricht der Pausenregel aus §7.1. Die Entity-IDs stehen zentral in pyscript/modules/einstellungen.py - dort vor -der Installation einmalig anpassen (siehe INSTALL.md). +der Installation einmalig anpassen (siehe INSTALL.md), oder über das +Setup-Menü in der Oberfläche zuordnen (siehe entitaeten.py; Änderungen dort +wirken für DIESEN Trigger allerdings erst nach einem Neustart, da +@state_trigger die Entity-ID einmalig beim Modul-Laden einliest). EINSCHRÄNKUNG: der Zustand einer laufenden Fahrt (_fahrt_start_ts) lebt nur im Arbeitsspeicher dieses Skripts. Ein HA-Neustart mitten in einer Fahrt oder @@ -32,11 +40,6 @@ import profil _fahrt_start_ts = None -def _profil_wlan_name(): - p = profil.profil_lesen() - return p.get("fahrzeug", {}).get("wlan_name") - - def _pausenzeit_sekunden(): p = profil.profil_lesen() return p.get("einstellungen", {}).get("fahrten_pausenzeit_min", 15) * 60 @@ -47,39 +50,39 @@ def beim_start(): profil.ordner_sicherstellen() -@state_trigger(f"{einstellungen.WLAN_SENSOR}") -def wlan_verbindung_geaendert(value=None, old_value=None): - # Killt eine noch laufende Ende-Bestätigung aus einer vorherigen - # Änderung - das ist der Mechanismus hinter der Pausenregel. - task.unique("audi_dashboard_fahrt_ende_pruefung") +# Ohne konfigurierte Zündungs-Entität keinen Trigger registrieren - eine +# leere Entity-ID als @state_trigger-Ausdruck ist ungetestetes Verhalten +# (siehe zustand_oder_none()-Kommentar in frontend_veroeffentlichung.py zur +# Grundhaltung "kein geratener Wert"), hier stattdessen ganz weggelassen. +if einstellungen.ZUENDUNG_SENSOR: + @state_trigger(f"{einstellungen.ZUENDUNG_SENSOR}") + def zuendung_geaendert(value=None, old_value=None): + # Killt eine noch laufende Ende-Bestätigung aus einer vorherigen + # Änderung - das ist der Mechanismus hinter der Pausenregel. + task.unique("audi_dashboard_fahrt_ende_pruefung") - ziel = _profil_wlan_name() - if not ziel: - log.warning("audi_dashboard: kein fahrzeug.wlan_name im Fahrzeugprofil hinterlegt") - return + global _fahrt_start_ts + an_jetzt = (value == "on") + an_vorher = (old_value == "on") - global _fahrt_start_ts - verbunden_jetzt = (value == ziel) - verbunden_vorher = (old_value == ziel) + if an_jetzt and not an_vorher: + if _fahrt_start_ts is None: + _fahrt_start_ts = datetime.datetime.now(datetime.timezone.utc) + log.info(f"audi_dashboard: Fahrt gestartet um {_fahrt_start_ts.isoformat()}") + return - if verbunden_jetzt and not verbunden_vorher: - if _fahrt_start_ts is None: - _fahrt_start_ts = datetime.datetime.now(datetime.timezone.utc) - log.info(f"audi_dashboard: Fahrt gestartet um {_fahrt_start_ts.isoformat()}") - return + if an_vorher and not an_jetzt and _fahrt_start_ts is not None: + start_ts = _fahrt_start_ts + abbruch_ts = datetime.datetime.now(datetime.timezone.utc) + wartezeit_s = _pausenzeit_sekunden() - if verbunden_vorher and not verbunden_jetzt and _fahrt_start_ts is not None: - start_ts = _fahrt_start_ts - abbruch_ts = datetime.datetime.now(datetime.timezone.utc) - wartezeit_s = _pausenzeit_sekunden() + # task.unique() oben markiert diese Ausführung als die aktuell + # gültige. Kommt die Zündung vor Ablauf der Wartezeit zurück, + # ruft der nächste Trigger erneut task.unique() auf und killt + # genau diesen task.sleep() - der Code danach läuft dann nie. + task.sleep(wartezeit_s) - # task.unique() oben markiert diese Ausführung als die aktuell - # gültige. Kommt die Verbindung vor Ablauf der Wartezeit zurück, - # ruft der nächste Trigger erneut task.unique() auf und killt genau - # diesen task.sleep() - der Code danach läuft dann nie. - task.sleep(wartezeit_s) - - _fahrt_beenden(start_ts, abbruch_ts) + _fahrt_beenden(start_ts, abbruch_ts) def _fahrt_beenden(start_ts, ende_ts): @@ -120,7 +123,7 @@ def _fahrt_beenden(start_ts, ende_ts): @service def audi_dashboard_fahrt_manuell_anlegen(ts_start=None, ts_end=None, art="privat"): """Manuelle Fahrterfassung aus der Oberfläche (§5.3), unabhängig von der - automatischen WLAN-Erkennung. Aufruf als pyscript.audi_dashboard_fahrt_manuell_anlegen.""" + automatischen Zündungs-Erkennung. Aufruf als pyscript.audi_dashboard_fahrt_manuell_anlegen.""" start_ts = datetime.datetime.fromisoformat(ts_start) ende_ts = datetime.datetime.fromisoformat(ts_end) dauer_s = int((ende_ts - start_ts).total_seconds()) diff --git a/homeassistant/pyscript/frontend_api.py b/homeassistant/pyscript/frontend_api.py index f63a057..0fdb794 100644 --- a/homeassistant/pyscript/frontend_api.py +++ b/homeassistant/pyscript/frontend_api.py @@ -19,6 +19,7 @@ statt für jedes einzelne Feld einen eigenen Service zu brauchen. import json import einstellungen +import entitaeten import frontend_veroeffentlichung import profil @@ -26,6 +27,10 @@ import profil @time_trigger("startup") def beim_start(): profil.ordner_sicherstellen() + # Vor der ersten Veröffentlichung anwenden, damit sie die im Setup-Menü + # gespeicherten Zuordnungen (data/entitaeten.json) schon zeigt, siehe + # entitaeten.py-Kopfkommentar. + entitaeten.overrides_anwenden() frontend_veroeffentlichung.alles_veroeffentlichen() @@ -73,3 +78,22 @@ def audi_dashboard_profil_schreiben(profil_json=None): profil.profil_schreiben(p) frontend_veroeffentlichung.profil_veroeffentlichen() log.info("audi_dashboard: Profil aktualisiert") + + +@service +def audi_dashboard_entitaeten_schreiben(zuordnung_json=None): + """Speichert die im Setup-Menü (Einstellungen -> Fahrzeug einrichten -> + Setup) gewählte Sensor-zu-Entität-Zuordnung, wendet sie sofort an und + veröffentlicht Katalog+Status neu, damit Übersicht/Mein Audi ohne + Neustart die neuen Werte zeigen (Ausnahme: die drei trigger-gebundenen + Felder, siehe entitaeten.py). Aufruf als + pyscript.audi_dashboard_entitaeten_schreiben.""" + if not zuordnung_json: + log.warning("audi_dashboard: entitaeten_schreiben ohne zuordnung_json aufgerufen") + return + mapping = json.loads(zuordnung_json) if isinstance(zuordnung_json, str) else zuordnung_json + entitaeten.overrides_schreiben(mapping) + entitaeten.overrides_anwenden() + frontend_veroeffentlichung.entitaeten_veroeffentlichen() + frontend_veroeffentlichung.fahrzeugstatus_veroeffentlichen() + log.info("audi_dashboard: Entitäten-Zuordnung aktualisiert") diff --git a/homeassistant/pyscript/modules/einstellungen.py b/homeassistant/pyscript/modules/einstellungen.py index 9037bb0..9378306 100644 --- a/homeassistant/pyscript/modules/einstellungen.py +++ b/homeassistant/pyscript/modules/einstellungen.py @@ -7,106 +7,85 @@ des Fahrzeugprofils (data/fahrzeugprofil.json) und über die Oberfläche Fahrzeugstatus-Abfrage brauchen sie als festen Wert, bevor überhaupt ein Profil gelesen werden kann. -In Home Assistant unter Entwicklerwerkzeuge -> Zustände nachschlagen. +In Home Assistant unter Entwicklerwerkzeuge -> Zustände nachschlagen. Werte +lassen sich außerdem über das Setup-Menü in der Oberfläche zuordnen (siehe +entitaeten.py) - Änderungen von dort werden zur Laufzeit auf diese Variablen +angewendet (überschreiben also die hier hinterlegten Standardwerte), ohne +diese Datei anzufassen. -Die drei ersten (WLAN, Kilometerstand, Tankfüllstand) werden vom Backend -zwingend gebraucht (§7.1, §7.2, §7.4, §7.5). Alles danach speist nur die -Übersicht im Frontend (§5.1, §5.2, §9) — bleibt ein Wert leer ("") oder passt -eine Entity-ID nicht zur tatsächlichen Integration, liefert -zustand_oder_none() in frontend_veroeffentlichung.py für das jeweilige Feld -None statt abzustürzen; die Oberfläche zeigt dann „unbekannt" statt eines -falschen Werts. - -Alle Entity-IDs unten sind an der echten Integration TommiG1/HA_VAG-EU-Data-Act -(Version 0.6.35, per HACS installiert) real geprüft, nicht nur vermutet: das -Gerät heißt dort "Audi RS 4 Avant" (mit Leerzeichen vor der "4") - deshalb -"rs_4" mit Unterstrich im Slug, nicht "rs4". Eine ältere Integrationsversion -hatte hier noch deutsche Feldnamen (z. B. "..._tur_vorne_links"); die aktuelle -Version benennt englisch (z. B. "..._front_left_door"). +2026-08-12: Die HACS-Integration TommiG1/HA_VAG-EU-Data-Act (bisherige +Quelle für Kilometerstand, Tankfüllstand, Türen/Fenster/Schlösser, +Ölwechsel-/Inspektionsdaten) wird nicht mehr verwendet - die zugehörigen +Entity-IDs sind deshalb unten bewusst leer. Neue Datenquelle ist der +Teltonika FMM003 (GPS-Tracker mit CAN-Anbindung, siehe AGENTS.md Abschnitt +B); er liefert Standort, Zündungsstatus und Batteriespannung, aber keinen +Tankfüllstand, keine Tür-/Fenster-/Schlossdaten und keine Ölwechsel-/ +Inspektionstermine - die entsprechenden Kacheln zeigen deshalb bis auf +Weiteres "unbekannt" statt eines falschen Werts (siehe zustand_oder_none() +in frontend_veroeffentlichung.py). Bleibt ein Wert leer ("") oder passt eine +Entity-ID nicht zur tatsächlichen Integration, liefert zustand_oder_none() +für das jeweilige Feld None statt abzustürzen. """ -# Companion-App-Sensor für das aktuell verbundene WLAN (iOS-App: -# Einstellungen -> Companion App -> Sensoren -> "Verbundenes WLAN") -WLAN_SENSOR = "sensor.iphone_wifi_connection" +# Fahrterkennung (§7.1): Start/Ende einer Fahrt wird über den Zündungs-/ACC- +# Status des FMM003 erkannt (on = Fahrt läuft), nicht mehr über die WLAN- +# Verbindung des iPhones zum Fahrzeug (siehe fahrterkennung.py). +ZUENDUNG_SENSOR = "binary_sensor.testzone_fmm003_engine_ignition_or_acc_status" -# Kilometerstand aus der HACS-Integration TommiG1/HA_VAG-EU-Data-Act -KM_SENSOR = "sensor.audi_rs_4_avant_mileage" +# Kilometerstand - bisher aus der TommiG1-Integration, aktuell keine Quelle +# vorhanden. Der FMM003 liefert unter sensor.testzone_fmm003_total_calculated_mileage +# einen selbst berechneten Wert, der aber auf einer anderen Zählbasis beruht +# als der echte Fahrzeug-Kilometerstand (GPS-Streckenberechnung statt +# Tacho) - bewusst NICHT automatisch übernommen, um Reifenzähler, +# Ölwechsel-Prognose und Fahrtabschluss-Screening nicht mit einem +# inkonsistenten Basiswert zu verfälschen. Bei Bedarf über das Setup-Menü +# gezielt zuordnen. +KM_SENSOR = "" -# Tankfüllstand aus derselben Integration (Prozent) -TANK_SENSOR = "sensor.audi_rs_4_avant_fuel_level" +# Tankfüllstand (Prozent) - keine Quelle mehr vorhanden (der FMM003 ist kein +# Tankgeber). +TANK_SENSOR = "" -# Reichweite (§5.1 Übersicht) -RANGE_SENSOR = "sensor.audi_rs_4_avant_range_primary" +# Reichweite (§5.1 Übersicht) - keine Quelle mehr vorhanden. +RANGE_SENSOR = "" -# 12V-Batteriespannung (Mein Audi -> Zustand). Die aktuell genutzte -# HACS-Integration (TommiG1/HA_VAG-EU-Data-Act 0.6.35) liefert dafür KEINEN -# Sensor (real geprüft: kein "battery"/"voltage"-Entity unter diesem Gerät) - -# deshalb hier bewusst leer gelassen. Liefert später eine andere Quelle (z. B. -# ein OBD-Adapter) eine Spannung, einfach die Entity-ID eintragen; bis dahin -# zeigt die Oberfläche "unbekannt" statt eines falschen Werts (siehe -# zustand_oder_none() in frontend_veroeffentlichung.py). -BATTERIE_SENSOR = "" +# 12V-Batteriespannung (Mein Audi -> Zustand). Vom FMM003 geliefert - +# external_power_voltage ist die vom Gerät gemessene Bordnetzspannung des +# Fahrzeugs, NICHT battery_voltage (das ist die interne Pufferbatterie des +# Trackers selbst und hat mit der Fahrzeugbatterie nichts zu tun). +BATTERIE_SENSOR = "sensor.testzone_fmm003_external_power_voltage" -# Der von der Integration selbst bereitgestellte Knopf für eine sofortige -# Neuabfrage beim Fahrzeug (Entity-Domäne "button", real geprüft) - genau der -# Weg, den "Ziehen zum Aktualisieren" im Frontend auslöst (siehe -# audi_dashboard_jetzt_aktualisieren() in frontend_api.py), statt eines -# generischen homeassistant.update_entity auf einzelne Sensoren. -REFRESH_BUTTON = "button.audi_rs_4_avant_refresh_now" +# Knopf für eine sofortige Neuabfrage beim Fahrzeug - kam aus der +# TommiG1-Integration, keine Entsprechung beim FMM003 vorhanden. +REFRESH_BUTTON = "" -# Türen (§4.1) - die Integration liefert vier Einzelsensoren statt eines -# Summensensors. binary_sensor, device_class "door": "off" = zu (real -# geprüft). -TUER_SENSOREN = [ - "binary_sensor.audi_rs_4_avant_front_left_door", - "binary_sensor.audi_rs_4_avant_front_right_door", - "binary_sensor.audi_rs_4_avant_rear_left_door", - "binary_sensor.audi_rs_4_avant_rear_right_door", -] +# Türen (§4.1) - keine Quelle mehr vorhanden. +TUER_SENSOREN = [] -# Fenster (§4.1) - device_class "window": "off" = zu (real geprüft) -FENSTER_SENSOREN = [ - "binary_sensor.audi_rs_4_avant_front_left_window", - "binary_sensor.audi_rs_4_avant_front_right_window", - "binary_sensor.audi_rs_4_avant_rear_left_window", - "binary_sensor.audi_rs_4_avant_rear_right_window", -] +# Fenster (§4.1) - keine Quelle mehr vorhanden. +FENSTER_SENSOREN = [] -# Verriegelung - kein lock.*-Entity vorhanden, sondern vier -# Türschloss-Einzelsensoren. binary_sensor mit device_class "lock" (real -# geprüft): "on" = entriegelt, "off" = verriegelt (Home-Assistant-Konvention -# für diese device_class). -TUERSCHLOSS_SENSOREN = [ - "binary_sensor.audi_rs_4_avant_front_left_door_lock", - "binary_sensor.audi_rs_4_avant_front_right_door_lock", - "binary_sensor.audi_rs_4_avant_rear_left_door_lock", - "binary_sensor.audi_rs_4_avant_rear_right_door_lock", -] +# Verriegelung - keine Quelle mehr vorhanden. +TUERSCHLOSS_SENSOREN = [] -# Heckklappe und Motorhaube - von der Integration separat von den vier Türen -# geführt, für "Sicher abgestellt" (§9) aber ebenso relevant wie diese -HECKKLAPPE_SENSOR = "binary_sensor.audi_rs_4_avant_tailgate" -HECKKLAPPENSCHLOSS_SENSOR = "binary_sensor.audi_rs_4_avant_tailgate_lock" -HAUBE_SENSOR = "binary_sensor.audi_rs_4_avant_hood" -HAUBENSCHLOSS_SENSOR = "binary_sensor.audi_rs_4_avant_hood_lock" +# Heckklappe und Motorhaube - keine Quelle mehr vorhanden. +HECKKLAPPE_SENSOR = "" +HECKKLAPPENSCHLOSS_SENSOR = "" +HAUBE_SENSOR = "" +HAUBENSCHLOSS_SENSOR = "" # Vom Fahrzeug selbst gemeldete Service-Fälligkeit (ergänzt die App-eigene, -# aus dem Servicebuch berechnete Prognose - siehe fahrzeugMeldung() im -# Frontend). Je ein Datum (ISO mit Zeitzone) und eine Streckenangabe in km; -# die Streckensensoren liefern negative Werte (Restkilometer als Countdown), -# im Frontend wird davon nur der Betrag angezeigt. -NAECHSTER_OELWECHSEL_SENSOR = "sensor.audi_rs_4_avant_oil_change_due" -OELWECHSEL_STRECKE_SENSOR = "sensor.audi_rs_4_avant_oil_change_distance" -NAECHSTE_INSPEKTION_SENSOR = "sensor.audi_rs_4_avant_inspection_due" -INSPEKTION_STRECKE_SENSOR = "sensor.audi_rs_4_avant_inspection_distance" +# aus dem Servicebuch berechnete Prognose) - keine Quelle mehr vorhanden. +NAECHSTER_OELWECHSEL_SENSOR = "" +OELWECHSEL_STRECKE_SENSOR = "" +NAECHSTE_INSPEKTION_SENSOR = "" +INSPEKTION_STRECKE_SENSOR = "" # Live-GPS-Position des Fahrzeugs (Übersicht -> Standort-Kachel). Erwartet # eine device_tracker.*-Entity mit "latitude"/"longitude"-Attributen (HA- -# Konvention, siehe _standort() in frontend_veroeffentlichung.py) - bleibt -# leer, solange keine Quelle angebunden ist (siehe AGENTS.md Abschnitt B, -# FMM003/flespi offen); die Oberfläche zeigt dann "kein GPS-Signal" statt -# eines falschen Werts, genau wie bei BATTERIE_SENSOR oben. -STANDORT_TRACKER = "" +# Konvention, siehe _standort() in frontend_veroeffentlichung.py) - jetzt +# vom FMM003 geliefert. +STANDORT_TRACKER = "device_tracker.testzone_fmm003" # Update-Funktion (Einstellungen -> "Update suchen", siehe # updateverwaltung.py): Git-Repository, in das dieses Projekt gepflegt wird - diff --git a/homeassistant/pyscript/modules/entitaeten.py b/homeassistant/pyscript/modules/entitaeten.py new file mode 100644 index 0000000..f52848a --- /dev/null +++ b/homeassistant/pyscript/modules/entitaeten.py @@ -0,0 +1,188 @@ +"""Sensor-zu-Entität-Zuordnung fürs Setup-Menü (Einstellungen -> Fahrzeug +einrichten -> Setup). + +Ersetzt NICHT das manuelle Eintragen in einstellungen.py (dessen Kommentare +sind die eigentliche Dokumentation der Integration und bleiben unangetastet), +sondern legt eine zusätzliche, zur Laufzeit angewendete Override-Ebene +darüber: data/entitaeten.json enthält nur die vom Nutzer über das Setup-Menü +geänderten Zuordnungen, keyed nach den einstellungen.py-Variablennamen. + +Warum das funktioniert, ohne jede Verbraucher-Datei anzufassen: Alle +bestehenden Leser (frontend_veroeffentlichung.py, fahrterkennung.py, ...) +machen `import einstellungen` und lesen dann `einstellungen.KM_SENSOR` - +ein lebendiger Attributzugriff auf das Modul-Objekt, kein `from einstellungen +import KM_SENSOR` (das würde den Wert beim Import einfrieren). overrides_ +anwenden() unten nutzt genau das: setattr(einstellungen, key, wert) ändert +das Attribut auf dem bereits importierten Modul-Objekt, jeder spätere +Zugriff `einstellungen.KM_SENSOR` liefert sofort den neuen Wert. + +Eine Ausnahme (siehe TRIGGER_GEBUNDEN unten): mehrere @state_trigger(f"...")- +Dekoratoren backen ZUENDUNG_SENSOR/KM_SENSOR/TANK_SENSOR einmalig beim +Modul-Laden in die Trigger-Registrierung ein. Eine Änderung dieser drei +Felder wirkt für die Fahrterkennung selbst erst nach einem Neustart von +Home Assistant (bzw. pyscript.reload) - das Setup-Menü weist deshalb bei +diesen drei Feldern ausdrücklich darauf hin, statt automatisch und riskant +einen Selbst-Reload aus einer laufenden pyscript-Funktion heraus zu +versuchen. +""" + +import io +import json +import os + +import einstellungen + +BASIS = "/config/audi_dashboard" +OVERRIDES_PFAD = f"{BASIS}/entitaeten.json" + +# Vier gleich aufgebaute Positionen, wie schon in frontend_veroeffentlichung.py +# (dort POSITIONEN genannt) - hier dupliziert statt importiert, damit +# entitaeten.py nicht von frontend_veroeffentlichung.py abhängt (wäre die +# falsche Richtung: die Veröffentlichung importiert diese Datei, nicht +# umgekehrt). +POSITIONEN = ["vorne links", "vorne rechts", "hinten links", "hinten rechts"] + +# Katalog aller von der App genutzten Entity-Rollen, fürs Setup-Menü im +# Frontend. Reihenfolge = Anzeige-Reihenfolge innerhalb jeder Gruppe. +# domains/device_classes/units sind Erwartungen fürs Frontend-seitige +# Filtern+Vorschlagen (Schalter "Nur passende Sensoren anzeigen") - eine +# fehlende oder leere Liste bedeutet "keine Einschränkung" bzw. "diese Rolle +# hat üblicherweise keine Einheit" (binary_sensor/button/device_tracker). +FELDER = [ + {"key": "ZUENDUNG_SENSOR", "label": "Zündung/ACC-Status", "gruppe": "fahrterkennung", + "hinweis": "on = Fahrt läuft, erkennt Fahrtbeginn/-ende (ersetzt die frühere WLAN-Erkennung).", + "domains": ["binary_sensor"], "device_classes": [], "units": [], "liste": False, "pflicht": True, + "stichworte": ["zündung", "ignition", "acc", "motor", "engine"]}, + {"key": "KM_SENSOR", "label": "Kilometerstand", "gruppe": "fahrterkennung", + "hinweis": "Gesamt-Kilometerstand des Fahrzeugs - für Fahrtabschluss-Screening, Reifenzähler und Ölwechsel-Prognose.", + "domains": ["sensor"], "device_classes": ["distance"], "units": ["km", "mi"], "liste": False, "pflicht": False, + "stichworte": ["kilometer", "km", "mileage", "odometer", "tacho", "laufleistung"]}, + {"key": "TANK_SENSOR", "label": "Tankfüllstand", "gruppe": "fahrterkennung", + "hinweis": "Füllstand in Prozent - für die automatische Tankerkennung.", + "domains": ["sensor"], "device_classes": [], "units": ["%"], "liste": False, "pflicht": False, + "stichworte": ["tank", "fuel", "kraftstoff", "füllstand", "level"]}, + {"key": "RANGE_SENSOR", "label": "Reichweite", "gruppe": "uebersicht", + "hinweis": "Für die Übersicht - bleibt leer, zeigt die Oberfläche \"unbekannt\".", + "domains": ["sensor"], "device_classes": ["distance"], "units": ["km", "mi"], "liste": False, "pflicht": False, + "stichworte": ["reichweite", "range"]}, + {"key": "BATTERIE_SENSOR", "label": "12V-Batteriespannung", "gruppe": "uebersicht", + "hinweis": "Nicht jede Integration liefert diesen Wert - bleibt er leer, zeigt \"Mein Audi\" \"unbekannt\".", + "domains": ["sensor"], "device_classes": ["voltage"], "units": ["V"], "liste": False, "pflicht": False, + "stichworte": ["batterie", "battery", "spannung", "volt", "12v"]}, + {"key": "REFRESH_BUTTON", "label": "Sofort-Aktualisierung (Knopf)", "gruppe": "uebersicht", + "hinweis": "Der von der Integration bereitgestellte Knopf für \"Ziehen zum Aktualisieren\".", + "domains": ["button"], "device_classes": [], "units": [], "liste": False, "pflicht": False, + "stichworte": ["refresh", "aktualisieren", "update", "abrufen", "poll"]}, + {"key": "TUER_SENSOREN", "label": "Türen", "gruppe": "sicherheit", + "hinweis": "\"aus\"/off = zu. Für \"Sicher abgestellt\" wichtig.", + "domains": ["binary_sensor"], "device_classes": ["door"], "units": [], "liste": True, "positionen": POSITIONEN, + "pflicht": False, "stichworte": ["tür", "door"]}, + {"key": "FENSTER_SENSOREN", "label": "Fenster", "gruppe": "sicherheit", + "hinweis": "\"aus\"/off = zu.", + "domains": ["binary_sensor"], "device_classes": ["window"], "units": [], "liste": True, "positionen": POSITIONEN, + "pflicht": False, "stichworte": ["fenster", "window"]}, + {"key": "TUERSCHLOSS_SENSOREN", "label": "Türschlösser", "gruppe": "sicherheit", + "hinweis": "\"on\" = entriegelt, \"aus\"/off = verriegelt (device_class \"lock\"-Konvention).", + "domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": True, "positionen": POSITIONEN, + "pflicht": False, "stichworte": ["schloss", "lock", "verriegel", "entriegel"]}, + {"key": "HECKKLAPPE_SENSOR", "label": "Heckklappe", "gruppe": "sicherheit", + "hinweis": "\"aus\"/off = zu.", + "domains": ["binary_sensor"], "device_classes": ["door", "opening"], "units": [], "liste": False, "pflicht": False, + "stichworte": ["heckklappe", "tailgate", "kofferraum", "trunk"]}, + {"key": "HECKKLAPPENSCHLOSS_SENSOR", "label": "Heckklappenschloss", "gruppe": "sicherheit", + "hinweis": "\"on\" = entriegelt, \"aus\"/off = verriegelt.", + "domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": False, "pflicht": False, + "stichworte": ["heckklappe", "tailgate", "schloss", "lock"]}, + {"key": "HAUBE_SENSOR", "label": "Motorhaube", "gruppe": "sicherheit", + "hinweis": "\"aus\"/off = zu.", + "domains": ["binary_sensor"], "device_classes": ["door", "opening"], "units": [], "liste": False, "pflicht": False, + "stichworte": ["haube", "hood", "bonnet", "motorhaube"]}, + {"key": "HAUBENSCHLOSS_SENSOR", "label": "Haubenschloss", "gruppe": "sicherheit", + "hinweis": "\"on\" = entriegelt, \"aus\"/off = verriegelt.", + "domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": False, "pflicht": False, + "stichworte": ["haube", "hood", "schloss", "lock"]}, + {"key": "NAECHSTER_OELWECHSEL_SENSOR", "label": "Nächster Ölwechsel (Datum)", "gruppe": "uebersicht", + "hinweis": "Vom Fahrzeug selbst gemeldete Fälligkeit, ergänzt die App-eigene Servicebuch-Prognose.", + "domains": ["sensor"], "device_classes": ["date", "timestamp"], "units": [], "liste": False, "pflicht": False, + "stichworte": ["ölwechsel", "oil", "service", "change"]}, + {"key": "OELWECHSEL_STRECKE_SENSOR", "label": "Nächster Ölwechsel (Restkilometer)", "gruppe": "uebersicht", + "hinweis": "Meldet die Integration oft als negativen Countdown - die App zeigt nur den Betrag.", + "domains": ["sensor"], "device_classes": ["distance"], "units": ["km", "mi"], "liste": False, "pflicht": False, + "stichworte": ["ölwechsel", "oil", "distance", "strecke"]}, + {"key": "NAECHSTE_INSPEKTION_SENSOR", "label": "Nächste Inspektion (Datum)", "gruppe": "uebersicht", + "hinweis": "Vom Fahrzeug selbst gemeldete Fälligkeit.", + "domains": ["sensor"], "device_classes": ["date", "timestamp"], "units": [], "liste": False, "pflicht": False, + "stichworte": ["inspektion", "inspection", "service"]}, + {"key": "INSPEKTION_STRECKE_SENSOR", "label": "Nächste Inspektion (Restkilometer)", "gruppe": "uebersicht", + "hinweis": "Meldet die Integration oft als negativen Countdown - die App zeigt nur den Betrag.", + "domains": ["sensor"], "device_classes": ["distance"], "units": ["km", "mi"], "liste": False, "pflicht": False, + "stichworte": ["inspektion", "inspection", "distance", "strecke"]}, + {"key": "STANDORT_TRACKER", "label": "GPS-Standort", "gruppe": "standort", + "hinweis": "Braucht latitude/longitude-Attribute (HA-Konvention für device_tracker).", + "domains": ["device_tracker"], "device_classes": [], "units": [], "liste": False, "pflicht": False, + "stichworte": ["standort", "gps", "tracker", "position", "ortung"]}, +] + +# Mehrere @state_trigger(f"...")-Dekoratoren (fahrterkennung.py, +# fahrtabschluss.py, tankerkennung.py, reifenzaehler.py) backen diese drei +# Felder beim Modul-Laden ein (siehe Kopfkommentar) - im Setup-Menü als +# "wirkt erst nach Neustart"-Hinweis markiert. +_TRIGGER_GEBUNDEN = {"ZUENDUNG_SENSOR", "KM_SENSOR", "TANK_SENSOR"} +for _feld in FELDER: + _feld["trigger_gebunden"] = _feld["key"] in _TRIGGER_GEBUNDEN + +_SCHLUESSEL = {feld["key"] for feld in FELDER} + + +# --------------------------------------------------------------- JSON-I/O --- +# Gleiches Muster wie profil.profil_lesen()/profil_schreiben(): io.open nur +# über task.executor (pyscript-Einschränkung, siehe Kopfkommentar dort), +# atomares Schreiben über eine .tmp-Datei + os.replace. + +def overrides_lesen(): + if not os.path.exists(OVERRIDES_PFAD): + return {} + f = task.executor(io.open, OVERRIDES_PFAD, "r") + inhalt = f.read() + f.close() + return json.loads(inhalt) if inhalt.strip() else {} + + +def overrides_schreiben(mapping): + """Schreibt nur bekannte Schlüssel (siehe FELDER); unbekannte werden + stillschweigend verworfen, damit ein späterer Katalog-Umbau keine + Datenleichen in entitaeten.json hinterlässt.""" + bereinigt = {k: v for k, v in mapping.items() if k in _SCHLUESSEL} + os.makedirs(BASIS, exist_ok=True) + tmp = OVERRIDES_PFAD + ".tmp" + text = json.dumps(bereinigt, ensure_ascii=False, indent=2) + f = task.executor(io.open, tmp, "w") + f.write(text) + f.close() + os.replace(tmp, OVERRIDES_PFAD) + + +# ------------------------------------------------------- Anwenden/Lesen --- + +def overrides_anwenden(): + """setattr(einstellungen, key, wert) für jeden in entitaeten.json + hinterlegten, nicht-leeren Wert - siehe Kopfkommentar, warum das + ausreicht, damit alle bestehenden Verbraucher den neuen Wert sofort + sehen (Ausnahme: die drei TRIGGER_GEBUNDEN-Felder, dort erst nach + Neustart).""" + overrides = overrides_lesen() + for key, wert in overrides.items(): + if key not in _SCHLUESSEL: + continue + if wert in (None, "", []): + continue + setattr(einstellungen, key, wert) + + +def aktueller_stand(): + """Katalog + aktuell wirksamer Wert je Feld (aus dem einstellungen-Modul + selbst gelesen, nicht direkt aus der JSON-Datei) - zeigt damit sowohl + bereits angewendete Overrides als auch die eingebauten Standardwerte + einheitlich. Grundlage für pyscript.audi_dashboard_entitaeten.""" + werte = {feld["key"]: getattr(einstellungen, feld["key"], None) for feld in FELDER} + return {"katalog": FELDER, "werte": werte} diff --git a/homeassistant/pyscript/modules/frontend_veroeffentlichung.py b/homeassistant/pyscript/modules/frontend_veroeffentlichung.py index acfc1c7..a5be5b5 100644 --- a/homeassistant/pyscript/modules/frontend_veroeffentlichung.py +++ b/homeassistant/pyscript/modules/frontend_veroeffentlichung.py @@ -20,6 +20,7 @@ tankvorgaenge_veroeffentlichen() auf einen Zeitraum eingeschränkt werden """ import einstellungen +import entitaeten import profil @@ -39,6 +40,12 @@ def batterieverlauf_veroeffentlichen(): state.set("pyscript.audi_dashboard_batterieverlauf", "aktuell", new_attributes={"daten": profil.batterieverlauf_lesen()}) +def entitaeten_veroeffentlichen(): + """Katalog + aktuell wirksame Zuordnung fürs Setup-Menü (Einstellungen -> + Fahrzeug einrichten -> Setup) - siehe entitaeten.py.""" + state.set("pyscript.audi_dashboard_entitaeten", "aktuell", new_attributes={"daten": entitaeten.aktueller_stand()}) + + def beleg_ergebnis_veroeffentlichen(daten): """Ergebnis eines Beleg-Uploads (geparste Werte fürs Ausfüllen eines Entwurfs, oder ein Fehler wie 'Falsches Belegdatum') fürs Frontend, nach @@ -190,3 +197,4 @@ def alles_veroeffentlichen(): tankvorgaenge_veroeffentlichen() fahrzeugstatus_veroeffentlichen() batterieverlauf_veroeffentlichen() + entitaeten_veroeffentlichen() diff --git a/homeassistant/pyscript/reifenzaehler.py b/homeassistant/pyscript/reifenzaehler.py index 95c62f6..b415c3c 100644 --- a/homeassistant/pyscript/reifenzaehler.py +++ b/homeassistant/pyscript/reifenzaehler.py @@ -85,9 +85,13 @@ def beim_start(): _fortschreiben_und_veroeffentlichen(_aktueller_km()) -@state_trigger(f"{einstellungen.KM_SENSOR}") -def kilometerstand_geaendert(value=None): - _fortschreiben_und_veroeffentlichen(_aktueller_km()) +# Ohne konfigurierten Kilometerstand-Sensor keinen Trigger registrieren - +# eine leere Entity-ID als @state_trigger-Ausdruck ist ungetestetes +# Verhalten, siehe gleiches Muster in fahrterkennung.py. +if einstellungen.KM_SENSOR: + @state_trigger(f"{einstellungen.KM_SENSOR}") + def kilometerstand_geaendert(value=None): + _fortschreiben_und_veroeffentlichen(_aktueller_km()) @service diff --git a/homeassistant/pyscript/tankerkennung.py b/homeassistant/pyscript/tankerkennung.py index 23c9889..56ecd71 100644 --- a/homeassistant/pyscript/tankerkennung.py +++ b/homeassistant/pyscript/tankerkennung.py @@ -67,26 +67,30 @@ def beim_start(): profil.ordner_sicherstellen() -@state_trigger(f"{einstellungen.TANK_SENSOR}") -def tankfuellstand_geaendert(value=None, old_value=None): - global _tiefststand_pct +# Ohne konfigurierten Tankfüllstand-Sensor keinen Trigger registrieren - +# eine leere Entity-ID als @state_trigger-Ausdruck ist ungetestetes +# Verhalten, siehe gleiches Muster in fahrterkennung.py. +if einstellungen.TANK_SENSOR: + @state_trigger(f"{einstellungen.TANK_SENSOR}") + def tankfuellstand_geaendert(value=None, old_value=None): + global _tiefststand_pct - aktuell = _als_zahl(value) - if aktuell is None: - return + aktuell = _als_zahl(value) + if aktuell is None: + return - if _tiefststand_pct is None: - _tiefststand_pct = aktuell - return + if _tiefststand_pct is None: + _tiefststand_pct = aktuell + return - if aktuell <= _tiefststand_pct: - _tiefststand_pct = aktuell - return + if aktuell <= _tiefststand_pct: + _tiefststand_pct = aktuell + return - anstieg = aktuell - _tiefststand_pct - if anstieg >= _schwelle_prozent(): - _tankvorgang_automatisch_anlegen(anstieg) - _tiefststand_pct = aktuell + anstieg = aktuell - _tiefststand_pct + if anstieg >= _schwelle_prozent(): + _tankvorgang_automatisch_anlegen(anstieg) + _tiefststand_pct = aktuell def _tankvorgang_automatisch_anlegen(anstieg_pct): diff --git a/homeassistant/www/audi-dashboard-app.js b/homeassistant/www/audi-dashboard-app.js index 3be45a8..b2a1221 100644 --- a/homeassistant/www/audi-dashboard-app.js +++ b/homeassistant/www/audi-dashboard-app.js @@ -37,6 +37,7 @@ let CAR = null; let TRIPS = []; let FILLS = []; let BATTERIEVERLAUF = []; +let ENTITAETEN = null; // {katalog, werte} aus pyscript.audi_dashboard_entitaeten (Setup-Menü) let DATEN_GELADEN = false; // Zeitpunkt (ms) des letzten echten Fahrzeugstatus-Updates vom Backend - // null, solange keiner bekannt ist (siehe standAlterText()). @@ -88,7 +89,6 @@ function profilZuConfig(p) { nachtBis: p.einstellungen.nacht_bis, pauseMin: p.einstellungen.fahrten_pausenzeit_min, markenname: p.einstellungen.kraftstoffanbieter, - wlanName: p.fahrzeug.wlan_name, backupIntervall: p.einstellungen.backup_intervall || "aus", letztesBackup: p.einstellungen.letztes_backup || null, ausfuehrung: p.fahrzeug.ausfuehrung || "", @@ -156,7 +156,6 @@ function configCarZuProfil(vorherigesProfil) { p.fahrzeug.zusatz = CONFIG.fahrzeugzusatz; p.fahrzeug.untertitel = CONFIG.fahrzeuguntertitel; p.fahrzeug.kennzeichen = CONFIG.kennzeichen; - p.fahrzeug.wlan_name = CONFIG.wlanName; p.fahrzeug.hauptuntersuchung_faellig = CAR.hu; p.fahrzeug.fin = CAR.fin; p.fahrzeug.erstzulassung = CAR.erstzulassung; @@ -1754,10 +1753,8 @@ function vEinst() {
Fahrten werden erkannt, sobald sich das Telefon mit dem WLAN des Fahrzeugs verbindet (${esc(CONFIG.wlanName || "noch nicht eingerichtet")}). Bis dahin lässt sich jede Fahrt von Hand erfassen.
+Fahrten werden erkannt, sobald der Motor gestartet wird. Bis dahin lässt sich jede Fahrt von Hand erfassen.