Fuenf gemeldete Bugs behoben: Haubenschloss/Tuerschloesser entfernt, Bestaetigungsdialog-Transparenz, geteilte FMM003-Koordinaten, Laedt-Haenger, generischer Beleg-Parser

Entfernt Haubenschloss-/Tuerschloss-Erkennung dauerhaft aus Setup-Katalog und
Sicherheitscheck (auf ausdruecklichen Wunsch, nicht nur leer/unzuordenbar wie
der Rest der abgeloesten VAG-Integration). Behebt eine durchscheinende
Bestaetigungs-Sheet ("Doppelt zugeordnet" u.a.) - derselbe --tile-2-
Transparenz-Fehler, der fuer das Setup-Popup schon behoben war, hier
nachgezogen. Ergaenzt einen zweiten GPS-Pfad (STANDORT_LAT_SENSOR/
STANDORT_LON_SENSOR) fuer Integrationen wie flespi, die Breiten-/Laengengrad
als zwei eigene Sensoren statt als device_tracker-Attribute liefern. Haertet
den bekannten "Laedt ..."-Haenger beim App-Start zusaetzlich ab: Tab-Klicks
pruefen jetzt aktiv nach, ob Daten inzwischen da sind. Ergaenzt
shell_beleg_parser.py um einen stationsunabhaengigen Fallback fuer
Tankbelege unbekannter Formate (Adresse/Gesamtbetrag/Menge/Rabatt-Herleitung
wie vom Nutzer vorgegeben) - die bestehende Shell-Erkennung bleibt
unveraendert und weiterhin durch die zehn echten Testbelege abgedeckt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 11:32:15 +02:00
parent 440ff22bda
commit 25d1cebd12
10 changed files with 287 additions and 78 deletions
+70 -5
View File
@@ -1,10 +1,13 @@
# AGENTS.md — Project state, review findings, open items, and working rules
**Last updated: 2026-08-13** (merged the `umsetzung-datametric360` branch — companion app phases
110 done, see `UMSETZUNGSPLAN.md`; later the same day: cleaned up remaining EU Data Act residue,
fixed oversized toggle switches, and fixed a desktop-layout audit (settings button / rings logo /
popups overflowing past the capped content column) — see `DESIGN_AUDIT_2026-08-13.md` and section
C). This file is the entry point for every new agent
**Last updated: 2026-08-16** (merged the `umsetzung-datametric360` branch — companion app phases
110 done, see `UMSETZUNGSPLAN.md`; 2026-08-13: cleaned up remaining EU Data Act residue, fixed
oversized toggle switches, and fixed a desktop-layout audit (settings button / rings logo / popups
overflowing past the capped content column) — see `DESIGN_AUDIT_2026-08-13.md`; 2026-08-16: fixed
five user-reported bugs — removed Türschloss/Haubenschloss checks, fixed a translucent confirm-
sheet, added a flespi-style split lat/lon location source, hardened the "Lädt …" bootstrap race,
and added a generic (station-independent) fuel-receipt parser fallback — see section C). 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.
@@ -601,6 +604,68 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed
diffing across all 5 tabs in both themes, a temporary 2.5x `transform:scale()` zoom on the
live swipe-row DOM) but could not be reproduced — documented in the audit file as open,
pending a screenshot from the real device.
- [x] Fix five user-reported bugs (2026-08-16):
(1) **Türschloss-/Haubenschloss-Erkennung entfernt** — user asked for these two checks to be
permanently removed (not just left blank/unmapped like the rest of the retired VAG-
integration fields). Removed `TUERSCHLOSS_SENSOREN`/`HAUBENSCHLOSS_SENSOR` from
`entitaeten.py`'s `FELDER` catalog (no longer offered in the Setup menu) and from
`einstellungen.py` (attributes deleted, not just blanked), and removed their entries from
`_sicherheitscheck()` in `frontend_veroeffentlichung.py` (no longer listed under "Geprüfte
Punkte"). `HECKKLAPPENSCHLOSS_SENSOR` (tailgate lock) was deliberately left untouched — user
named only the two door/hood locks. Also removed the now-orphaned
`input_boolean.test_entriegelt` helper and the four `test_audi_lock_*` template sensors from
`testumgebung/konfiguration.yaml` (door-open/window fixtures for `TUER_SENSOREN`/
`FENSTER_SENSOREN` are untouched), and updated `testumgebung/README.md` accordingly.
(2) **"Doppelt zugeordnet" (and every other confirm-sheet) was see-through** — `.sheet-gruppe`/
`.sheet-abbrechen` in `audi-dashboard.css` used `--tile-2`, which the iOS overlay's night
theme redefines as `rgba(255,255,255,.10)` (translucent, meant for tile-on-tile layering) —
the same root cause already fixed for `.setup-popup`/`.standortmenu` on 2026-08-13, just not
carried over to the generic confirm-sheet used by `bestaetigen()`/`hinweis()`. Switched both
to `var(--tile-deckend, var(--canvas))`, matching the existing fallback pattern already used
elsewhere in this file (base CSS must not go blank if the purely-additive iOS overlay is ever
reverted, since `--tile-deckend` is an overlay-only token).
(3) **FMM003 coordinates arrive as two separate sensors, not one device_tracker** — user
reported "coordinates are split into latitude and longitude". Root cause: `_standort()` in
`frontend_veroeffentlichung.py` only ever read `latitude`/`longitude` as *attributes of a
single `STANDORT_TRACKER` device_tracker entity* (HA's usual convention) — but some
integrations (confirmed live in `audi_ha_test`'s own simulated FMM003 device, which exposes
separate "Latitude coordinate value"/"Longitude coordinate value" sensors) publish them as
two independent `sensor` entities instead, which `_standort()` had no way to consume. Added
`STANDORT_LAT_SENSOR`/`STANDORT_LON_SENSOR` (new optional fields in `einstellungen.py` +
`entitaeten.py`'s Setup-menu catalog, group "standort") as a second path: `_standort()` now
tries `STANDORT_TRACKER` first (unchanged behavior when it works), and falls back to reading
the two plain sensors' states directly when the tracker is unset or has no coordinates.
(4) **App can show only "Lädt …" until a tab is clicked** — known, already-mitigated race
(see the `nachladeAnstossen` header comment, `audi-dashboard-app.js`): on a slow/cold backend
start, `render()` no-ops until `DATEN_GELADEN` flips true, and the previously observed
manual fix was a HA panel remount. Root cause of *why* the click helps was not fully
reproducible in `audi_ha_test` (this is a genuine uncertainty, flagged rather than guessed
around) — but as a safe, low-risk hardening, `go()` (the tab/menu click handler) now calls
`datenLaden(false)` proactively if `DATEN_GELADEN` is still false, so any click that happens
to occur after data has actually become available is guaranteed to pick it up immediately
instead of depending on timing.
(5) **Fuel-receipt parser only understood Shell's exact layout** — user asked for a generic
algorithm for other/unknown fuel stations: find the address, find the total ("Gesamt"/
"Absolut"), find the liters ("Menge"/"Amount"), divide to get the paid price/liter, compare
against a printed "Preis/Liter" — a mismatch means a discount, found on the receipt as a
minus-marked amount. Implemented exactly this as `_parsen_generisch()` in
`homeassistant/data/shell_beleg_parser.py`, wired as a fallback in `main()` (Shell-specific
`_parsen()` tried first, unchanged — verified the existing 10-receipt regression suite still
passes byte-for-byte; `_parsen_generisch()` verified against a synthetic non-Shell receipt
with a known discount, all fields and the discount math correct). No separate "SmartDeal"
flag was needed — the frontend already treats any populated `discount` as SmartDeal-eligible
(gated only by the user's own "SmartDeal aktiv" switch, not by station name), so a generically
parsed Shell receipt behaves identically to one parsed via the strict path. Receipt-key
generation uses `hashlib.sha1` (not the built-in `hash()`, which is randomly salted per
process and would have broken the §7.7 duplicate-detection dedup across the parser's
per-receipt subprocess invocations).
All five deployed and verified live in `audi_ha_test` (version `1786752000`): Sicherheit-
Liste and Setup-Katalog confirmed to no longer offer Türschloss/Haubenschloss; the new
Breitengrad/Längengrad Setup fields confirmed present and correctly auto-suggesting the
container's real split lat/lon sensors; "Doppelt zugeordnet" confirmed rendering on an opaque
card by deliberately triggering it (this container's limited entity set causes a real
duplicate auto-suggestion); pyscript reload clean, no new console errors beyond the
pre-existing placeholder-image 404s. Synced to `installationspaket/`.
- [ ] Fix remaining documentation drift (statistics claim, README gaps, obsolete TODO comment) —
text-only changes; INSTALL.md's WLAN/TommiG1 drift and stale variable names were fixed
2026-08-12 (see section B); `DESIGN_REVIEW_2026-08-13.md` and `REVIEW_main_2026-08-13.md`
+125 -2
View File
@@ -50,6 +50,7 @@ Aufbau eines Shell-eReceipts (aus dem echten Beispiel, Reihenfolge fix):
"""
import datetime
import hashlib
import json
import re
import sys
@@ -198,6 +199,118 @@ def _parsen(text):
}
def _parsen_generisch(text):
"""Stationsunabhängiger Fallback für Belege, die keinem der oben fest an
Shells Beleg-Struktur verankerten Muster entsprechen (_parsen() wirft in
dem Fall ValueError - siehe main()). Arbeitet nach demselben Verfahren,
das an einem echten Beleg einer bislang unbekannten Tankstelle von Hand
nachvollzogen wurde: Adresse suchen, Gesamtbetrag suchen ("Gesamt.../
Absolut..."), Literangabe suchen ("Menge"/"Amount" oder eine an "l"/
"Liter" hängende Zahl), daraus den tatsächlich bezahlten Preis/Liter
errechnen (Gesamtbetrag / Liter). Steht daneben auch ein aufgedruckter
Preis/Liter auf dem Beleg und weicht er vom errechneten ab, wurde ein
Rabatt gewährt - auf dem Beleg meist als eigener Betrag mit einem Minus
davor erkennbar, danach wird zuerst gesucht, sonst aus der Preisdifferenz
hergeleitet. Ob ein so gefundener Rabatt als "SmartDeal" behandelt wird,
entscheidet einzig station_name (Shell-Logo/-Label im Frontend, siehe
frontend_veroeffentlichung.py) - kein Sonderfall hier nötig, die Frontend-
Anzeige hängt nur an einem gesetzten discount, nicht an einem eigenen
Kennzeichen.
Liefert deutlich weniger Felder als _parsen() (kein Bon-Kleingedrucktes
wie TSE-Nummer/Zahlungszeitstempel) - unproblematisch, denn
belegverarbeitung.py liest nur receipt_key als echtes Pflichtfeld,
alles andere per .get()."""
def suchen(muster, flags=re.IGNORECASE | re.MULTILINE):
return re.search(muster, text, flags)
# ---- Adresse: Name-/Straßen-/PLZ-Ort-Zeile hintereinander - wie bei
# Shell (_parsen() oben), aber ohne Ankerzeile "Obj.-Nr." danach, also
# direkt an der PLZ-Ort-Form (5 Ziffern + Text) erkannt. Nicht Pflicht.
adresse = suchen(r"([^\n]{2,60})\n([^\n]{2,60})\n(\d{5}\s+[^\n]{2,40})")
station_name = adresse.group(1).strip() if adresse else None
station_address = (
f"{adresse.group(2).strip()}, {adresse.group(3).strip()}" if adresse else None
)
# ---- Gesamtbetrag ------------------------------------------------------
gesamt = suchen(r"(?:Gesamt\w*|Absolut\w*|Endbetrag|Summe|Total)\D{0,20}?(" + _ZAHL + r")\s*(?:€|EUR)?")
if not gesamt:
raise ValueError("Kein Gesamtbetrag gefunden (weder 'Gesamt' noch 'Absolut')")
fuel_total_eur = _de_zahl(gesamt.group(1))
# ---- Menge in Litern ----------------------------------------------------
menge = suchen(r"(?:Menge|Amount)\D{0,15}?(" + _ZAHL + r")\s*(?:l\b|L\b|Liter|Ltr\.?)")
if not menge:
menge = suchen(r"(" + _ZAHL + r")\s*(?:Liter|Ltr\.?|l)\b")
if not menge:
raise ValueError("Keine Literangabe gefunden (weder 'Menge'/'Amount' noch 'X l')")
liters = _de_zahl(menge.group(1))
if not liters:
raise ValueError("Literangabe ist 0 oder unlesbar")
berechneter_preis_l = round(fuel_total_eur / liters, 3)
# ---- Aufgedruckter Preis/Liter - Abweichung verrät einen Rabatt -------
aufgedruckt = suchen(
r"(?:Preis\s*/?\s*(?:je\s*)?Liter|Price\s*/?\s*L(?:iter)?|€\s*/\s*l|EUR\s*/\s*l)\D{0,10}?(" + _ZAHL + r")"
)
list_price_per_l = _de_zahl(aufgedruckt.group(1)) if aufgedruckt else berechneter_preis_l
discount = None
discount_per_l = None
if aufgedruckt and abs(list_price_per_l - berechneter_preis_l) > 0.005:
minus = suchen(r"-\s*(" + _ZAHL + r")\s*(?:€|EUR)")
if minus:
discount = _de_zahl(minus.group(1))
discount_per_l = round(discount / liters, 3)
else:
discount_per_l = round(list_price_per_l - berechneter_preis_l, 3)
discount = round(discount_per_l * liters, 2)
# ---- Zeitstempel: irgendein Datum+Uhrzeit auf dem Beleg ----------------
zeit = suchen(r"(\d{2})\.(\d{2})\.(\d{2,4})\D{0,10}?(\d{2}):(\d{2})")
ts = _iso(zeit.group(1), zeit.group(2), zeit.group(3), zeit.group(4), zeit.group(5)) if zeit else None
# ---- Belegnummer, sonst deterministischer Ersatzschlüssel --------------
# Wichtig: kein eingebautes hash() (pro Prozess zufällig gesalzen, siehe
# PYTHONHASHSEED) - der Parser läuft für jeden Beleg als eigener Prozess
# (Kopfkommentar oben), ein instabiler Schlüssel würde den §7.7-Regel-3-
# Dedup-Vergleich in belegverarbeitung.py unbrauchbar machen.
beleg_nr = suchen(
r"(?:Beleg-?Nr\.?|Bon-?Nr\.?|Rechnungs-?Nr\.?|Receipt\s*No\.?|Trans(?:aktions)?-?Nr\.?)\D{0,5}?([\dA-Za-z/\-]+)"
)
receipt_no = beleg_nr.group(1) if beleg_nr else None
schluessel_basis = "|".join(
str(teil) for teil in (receipt_no, ts, station_address, fuel_total_eur, liters) if teil
) or text[:80]
receipt_key = "generisch_" + hashlib.sha1(schluessel_basis.encode("utf-8")).hexdigest()[:16]
return {
"receipt_key": receipt_key,
"receipt_no": receipt_no,
"tse_beleg_nr": None,
"ts": ts,
"ts_payment": None,
"ts_tse": None,
"station_id": None,
"station_name": station_name,
"station_address": station_address,
"article_no": None,
"product_name": None,
"fuel_type": None,
"liters": liters,
"list_price_per_l": list_price_per_l,
"discount": discount,
"discount_per_l": discount_per_l,
"fuel_total_eur": fuel_total_eur,
"receipt_total_eur": fuel_total_eur,
"net_eur": None,
"vat_eur": None,
}
def main():
if len(sys.argv) != 2:
print("Aufruf: python3 shell_beleg_parser.py <pfad-zur-pdf>", file=sys.stderr)
@@ -205,10 +318,20 @@ def main():
pdf_pfad = sys.argv[1]
try:
text = _text_aus_pdf(pdf_pfad)
ergebnis = _parsen(text)
except Exception as exc:
print(f"Beleg konnte nicht geparst werden ({type(exc).__name__}): {exc}", file=sys.stderr)
print(f"PDF konnte nicht gelesen werden ({type(exc).__name__}): {exc}", file=sys.stderr)
sys.exit(1)
try:
ergebnis = _parsen(text)
except Exception:
# Kein Shell-Beleg im bekannten Format - Versuch mit dem
# stationsunabhängigen Fallback (_parsen_generisch() oben), bevor
# endgültig aufgegeben wird.
try:
ergebnis = _parsen_generisch(text)
except Exception as exc:
print(f"Beleg konnte nicht geparst werden ({type(exc).__name__}): {exc}", file=sys.stderr)
sys.exit(1)
print(json.dumps(ergebnis, ensure_ascii=False))
@@ -65,14 +65,10 @@ TUER_SENSOREN = []
# Fenster (§4.1) - keine Quelle mehr vorhanden.
FENSTER_SENSOREN = []
# Verriegelung - keine Quelle mehr vorhanden.
TUERSCHLOSS_SENSOREN = []
# 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) - keine Quelle mehr vorhanden.
@@ -87,6 +83,13 @@ INSPEKTION_STRECKE_SENSOR = ""
# vom FMM003 geliefert.
STANDORT_TRACKER = "device_tracker.testzone_fmm003"
# Alternative zu STANDORT_TRACKER: manche Integrationen (z. B. flespi) liefern
# Breiten-/Längengrad nicht als Attribute einer einzelnen device_tracker-
# Entity, sondern als zwei eigene sensor-Entities. _standort() nutzt diese
# beiden nur, wenn STANDORT_TRACKER leer ist oder keine Koordinaten liefert.
STANDORT_LAT_SENSOR = ""
STANDORT_LON_SENSOR = ""
# Update-Funktion (Einstellungen -> "Update suchen", siehe
# updateverwaltung.py): Git-Repository, in das dieses Projekt gepflegt wird -
# z. B. ein privates GitHub-Repo, genau wie für die HACS-Integration bereits
+8 -8
View File
@@ -81,10 +81,6 @@ FELDER = [
"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,
@@ -97,10 +93,6 @@ FELDER = [
"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,
@@ -121,6 +113,14 @@ FELDER = [
"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"]},
{"key": "STANDORT_LAT_SENSOR", "label": "GPS-Standort: Breitengrad", "gruppe": "standort",
"hinweis": "Nur nötig, falls die Integration Breiten-/Längengrad als zwei eigene Sensoren statt als Attribute einer GPS-Standort-Entity liefert (z. B. flespi).",
"domains": ["sensor"], "device_classes": [], "units": [], "liste": False, "pflicht": False,
"stichworte": ["breitengrad", "latitude", "lat"]},
{"key": "STANDORT_LON_SENSOR", "label": "GPS-Standort: Längengrad", "gruppe": "standort",
"hinweis": "Nur nötig, falls die Integration Breiten-/Längengrad als zwei eigene Sensoren statt als Attribute einer GPS-Standort-Entity liefert (z. B. flespi).",
"domains": ["sensor"], "device_classes": [], "units": [], "liste": False, "pflicht": False,
"stichworte": ["längengrad", "longitude", "lon", "lng"]},
]
# Mehrere @state_trigger(f"...")-Dekoratoren (fahrterkennung.py,
@@ -88,37 +88,59 @@ def _abs_zahl(wert):
def _standort():
"""Live-GPS-Position des Fahrzeugs (Übersicht -> Standort-Kachel) - liest
latitude/longitude/gps_accuracy von der in STANDORT_TRACKER hinterlegten
device_tracker-Entity (HA-Konvention). Fehlt die Entity-ID oder ist sie
(noch) nicht verfügbar, liefert diese Funktion durchgehend None statt
eines geratenen Werts - genau das zustand_oder_none()-Muster von oben,
hier als eigene Funktion, weil zusätzlich zum Zustand noch Attribute und
der Aktualisierungszeitpunkt gebraucht werden: state.get() liefert dafür
ein StateVal (str-Unterklasse) mit .last_updated als datetime-Attribut -
kein eigenständiges state.last_updated(), das gibt es in pyscript nicht."""
entity_id = einstellungen.STANDORT_TRACKER
"""Live-GPS-Position des Fahrzeugs (Übersicht -> Standort-Kachel). Zwei
mögliche Quellen, in dieser Reihenfolge versucht: (1) STANDORT_TRACKER,
eine device_tracker-Entity mit latitude/longitude/gps_accuracy-Attributen
(HA-Konvention) - der Normalfall; (2) STANDORT_LAT_SENSOR/
STANDORT_LON_SENSOR, zwei eigene sensor-Entities für Breiten-/Längengrad -
manche Integrationen (z. B. flespi) liefern Koordinaten so, statt als
Attribute einer einzelnen Tracker-Entity. Fehlt beides oder ist (noch)
nicht verfügbar, liefert diese Funktion durchgehend None statt eines
geratenen Werts - genau das zustand_oder_none()-Muster von oben, hier als
eigene Funktion, weil zusätzlich zum Zustand noch Attribute und der
Aktualisierungszeitpunkt gebraucht werden: state.get() liefert dafür ein
StateVal (str-Unterklasse) mit .last_updated als datetime-Attribut - kein
eigenständiges state.last_updated(), das gibt es in pyscript nicht."""
leer = {"lat": None, "lon": None, "genauigkeit_m": None, "zeit": None}
if not entity_id:
return leer
try:
wert = state.get(entity_id)
attribute = state.getattr(entity_id)
except NameError:
return leer
if not attribute or attribute.get("latitude") is None:
return leer
zeit = None
try:
zeit = wert.last_updated.isoformat()
except AttributeError:
pass
return {
"lat": _zu_zahl(attribute.get("latitude")),
"lon": _zu_zahl(attribute.get("longitude")),
"genauigkeit_m": _zu_zahl(attribute.get("gps_accuracy")),
"zeit": zeit,
}
tracker_id = einstellungen.STANDORT_TRACKER
if tracker_id:
try:
wert = state.get(tracker_id)
attribute = state.getattr(tracker_id)
except NameError:
attribute = None
if attribute and attribute.get("latitude") is not None:
zeit = None
try:
zeit = wert.last_updated.isoformat()
except AttributeError:
pass
return {
"lat": _zu_zahl(attribute.get("latitude")),
"lon": _zu_zahl(attribute.get("longitude")),
"genauigkeit_m": _zu_zahl(attribute.get("gps_accuracy")),
"zeit": zeit,
}
lat_id, lon_id = einstellungen.STANDORT_LAT_SENSOR, einstellungen.STANDORT_LON_SENSOR
if lat_id and lon_id:
try:
lat_wert = state.get(lat_id)
lon_wert = state.get(lon_id)
except NameError:
return leer
lat, lon = _zu_zahl(lat_wert), _zu_zahl(lon_wert)
if lat is None or lon is None:
return leer
zeit = None
try:
zeit = lat_wert.last_updated.isoformat()
except AttributeError:
pass
return {"lat": lat, "lon": lon, "genauigkeit_m": None, "zeit": zeit}
return leer
POSITIONEN = ["vorne links", "vorne rechts", "hinten links", "hinten rechts"]
@@ -137,14 +159,10 @@ def _sicherheitscheck():
for pos, sensor in zip(POSITIONEN, einstellungen.FENSTER_SENSOREN):
w = zustand_oder_none(sensor)
eintraege.append({"label": f"Fenster {pos}", "ok": None if w is None else w == "off"})
for pos, sensor in zip(POSITIONEN, einstellungen.TUERSCHLOSS_SENSOREN):
w = zustand_oder_none(sensor)
eintraege.append({"label": f"Türschloss {pos}", "ok": None if w is None else w == "off"})
for label, sensor in [
("Heckklappe", einstellungen.HECKKLAPPE_SENSOR),
("Heckklappenschloss", einstellungen.HECKKLAPPENSCHLOSS_SENSOR),
("Motorhaube", einstellungen.HAUBE_SENSOR),
("Haubenschloss", einstellungen.HAUBENSCHLOSS_SENSOR),
]:
w = zustand_oder_none(sensor)
eintraege.append({"label": label, "ok": None if w is None else w == "off"})
+9
View File
@@ -2992,6 +2992,15 @@ function render() {
});
}
const go = (name, id) => {
// Bekanntes Race (siehe Kopfkommentar bei nachladeAnstossen): auf einem
// langsam startenden Backend kann die App auf "Lädt …" hängen bleiben,
// obwohl die Daten inzwischen längst da wären - der bislang einzige
// beobachtete Ausweg war ein Tabwechsel, der zufällig einen frischen
// datenLaden()-Durchlauf auslöste. Hier deshalb bei jedem Tab-/Menüklick
// aktiv nachschauen, statt nur auf den nächsten hass-Push zu warten -
// no-op, falls die Daten längst da sind (datenLaden() rendert dann
// ohnehin nichts doppelt Sichtbares).
if (!DATEN_GELADEN) datenLaden(false);
// Ein unbestätigter Servicebuch-Entwurf (siehe data-sbneu) verlässt die
// Anwendung nie ungespeichert - wird die Eintragsseite ohne Klick auf
// "Speichern" verlassen, verwerfen wir ihn hier statt ihn liegen zu lassen
@@ -1 +1 @@
{"version": 1786744000}
{"version": 1786752000}
+10 -2
View File
@@ -853,7 +853,15 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
@keyframes sheet-fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes sheet-rein { from { transform: translateY(18px); opacity: 0 } to { transform: none; opacity: 1 } }
.sheet-gruppe {
background: var(--tile-2);
/* --tile-2 is translucent in the iOS overlay's night theme (rgba white,
meant for tile-on-tile layering) - a confirm sheet sits directly over
whatever page content triggered it, so a see-through card made titles
like "Doppelt zugeordnet" hard to read against the busy background
bleeding through. Same fix already applied to .setup-popup/.standortmenu
for the same reason - opaque surface token, with the --canvas fallback
this file already uses elsewhere for when the (purely additive, in
principle removable) overlay isn't loaded. */
background: var(--tile-deckend, var(--canvas));
border-radius: 16px;
overflow: hidden;
display: flex; flex-direction: column;
@@ -877,7 +885,7 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
.sheet-gruppe button:active { background: var(--shade); }
.sheet-gruppe button.loeschen { color: var(--bad); }
.sheet-abbrechen {
background: var(--tile-2); border: none; border-radius: 16px;
background: var(--tile-deckend, var(--canvas)); border: none; border-radius: 16px;
font-family: inherit; font-size: 16px; color: var(--fg);
padding: 16px; min-height: 52px; cursor: pointer;
}
+3 -2
View File
@@ -41,12 +41,13 @@ lassen sich die `@state_trigger`-Abläufe wirklich auslösen:
| `input_number.test_reichweite` | `sensor.audi_rs_4_avant_range_primary` | Übersichtsanzeige (nur bei manueller Zuordnung, `RANGE_SENSOR` ist per Default leer) |
| `input_boolean.test_tuer_vl` | Tür vorne links | „sicher abgestellt" (nur bei manueller Zuordnung) |
| `input_boolean.test_fenster_vl` | Fenster vorne links | „sicher abgestellt" (nur bei manueller Zuordnung) |
| `input_boolean.test_entriegelt` | Türschloss vorne links | „sicher abgestellt" (nur bei manueller Zuordnung) |
Die Kilometerstand-/Tankfüllstand-/Reichweite- und Tür-/Fenster-/Schloss-Sensoren
Die Kilometerstand-/Tankfüllstand-/Reichweite- und Tür-/Fenster-Sensoren
bilden die Entitäten der abgelösten HACS-Integration `TommiG1/HA_VAG-EU-Data-Act`
nach (siehe `einstellungen.py`) — für den Standardbetrieb ohne Bedeutung, aber
weiterhin nützlich, um die manuelle Zuordnung über das Setup-Menü zu testen.
(Türschloss- und Haubenschloss-Erkennung wurden 2026-08-16 ersatzlos aus der
App entfernt, siehe `AGENTS.md`.)
Beispiel — Fahrt auslösen (Zündung/ACC an, dann aus):
+2 -20
View File
@@ -2,8 +2,8 @@
# Bildet zwei Datenquellen nach: den FMM003-Zuendungssensor (Fahrterkennung,
# aktiv per Standard in einstellungen.py) sowie die Entitaeten der
# abgeloesten HACS-Integration TommiG1/HA_VAG-EU-Data-Act (Kilometerstand,
# Tankfuellstand, Tueren/Fenster/Schloesser - in einstellungen.py per Default
# leer, aber ueber das Setup-Menue weiterhin manuell zuordenbar). Ueber die
# Tankfuellstand, Tueren/Fenster - in einstellungen.py per Default leer,
# aber ueber das Setup-Menue weiterhin manuell zuordenbar). Ueber die
# input_*-Helfer sind die Werte zur Laufzeit aenderbar (Fahrterkennung,
# Reifenzaehler, Tankerkennung haengen an @state_trigger).
@@ -61,8 +61,6 @@ input_boolean:
name: Test Tuer vorne links
test_fenster_vl:
name: Test Fenster vorne links
test_entriegelt:
name: Test entriegelt
template:
- binary_sensor:
@@ -132,22 +130,6 @@ template:
unique_id: test_audi_win_rr
state: "off"
device_class: window
- name: "Audi RS 4 Avant Front Left Door Lock"
unique_id: test_audi_lock_fl
state: "{{ is_state('input_boolean.test_entriegelt', 'on') }}"
device_class: lock
- name: "Audi RS 4 Avant Front Right Door Lock"
unique_id: test_audi_lock_fr
state: "off"
device_class: lock
- name: "Audi RS 4 Avant Rear Left Door Lock"
unique_id: test_audi_lock_rl
state: "off"
device_class: lock
- name: "Audi RS 4 Avant Rear Right Door Lock"
unique_id: test_audi_lock_rr
state: "off"
device_class: lock
- name: "Audi RS 4 Avant Tailgate"
unique_id: test_audi_tailgate
state: "off"