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:
@@ -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
|
||||
1–10 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
|
||||
1–10 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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"})
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
Reference in New Issue
Block a user