Kleinere Befunde und Dokumentation aus dem Review

Frontend, kleinere Befunde:
- Das Standort-Menue liess sich nur wischen oder ueber den Kartenmarker
  oeffnen. Der Griff ist jetzt ein Knopf mit aria-expanded, damit auch per
  Tastatur erreichbar; der Umschalter Strasse/Satellit hat aria-pressed und
  ein Label, das den Zustand nennt.
- Der Filterschalter "Nur passende Sensoren anzeigen" wirkte nicht, solange
  eine Auswahlliste offen war: der Klick-Handler ersetzte das Overlay-DOM,
  bevor das change-Ereignis des Kontrollkaestchens ausgeliefert wurde. Er
  wird jetzt vor dem Schliessen der Liste behandelt.
- Der Theme-Wechsel zeichnete nicht neu, die Leaflet-Kacheln blieben bis zum
  naechsten Backend-Update im alten Stil - seit der Dauerkarte auf der
  Uebersicht deutlich sichtbar.
- Toter Code entfernt (fahrzeugGlyphPfade, .dot.neutral) und zwei Kommentare
  berichtigt, die Gegenteiliges behaupteten.

Dokumentation:
- AGENTS.md widersprach sich an sechs Stellen. Berichtigt: Fahrterkennung
  laeuft ueber die Zuendung, nicht ueber WLAN; Datenweg ist flespi, nicht
  MQTT; Modulzahl und Zeilenzahl stimmen wieder; Entity-IDs werden im
  Setup-Menue zugeordnet, nicht in einstellungen.py; STANDORT_TRACKER ist
  belegt, nicht leer.
- SPECIFICATION.md beschreibt durchgehend den Stand vor der FMM003-
  Umstellung. Statt es zu Teilen umzuschreiben und dabei Ungenauigkeiten zu
  riskieren, steht jetzt ein datierter Hinweis am Anfang, der die drei
  geaenderten Punkte benennt und auf AGENTS.md verweist. Alles Uebrige des
  Dokuments gilt unveraendert weiter.
- INSTALL.md: Die zirkulaere Schrittfolge (Schritt 4 verweist auf 9, Schritt
  7 auf 4) ist als solche benannt und aufgeloest. Die Override-Datei ist mit
  Pfad genannt, samt dem Hinweis, dass sie gesichert wird. Und die drei mit
  Test-Entitaeten der Entwicklungsinstanz vorbelegten Rollen sind erwaehnt -
  auf einer frischen Installation zeigen sie ins Leere.
- README, Profilvorlage: WLAN-Reste entfernt, zwei Falschaussagen aus dem
  ersten Review nachgezogen (Statistik rechnet echt, die
  97-Prozent-Volltankungsregel wurde entfernt), Dateiuebersicht um die
  fuenf fehlenden pyscript-Dateien und entitaeten.py ergaenzt.
- update.ps1 liefert jetzt auch shell_beleg_parser.py aus. Die Datei liegt in
  data/, ist aber Code - Aenderungen am Belegleser, dem einzigen getesteten
  Teil des Projekts, kamen bisher auf keiner Instanz an.
- design/README: Zwei Dateien der Inhaltstabelle liegen gar nicht in dem
  Ordner, weshalb das Board aus der Repo-Kopie heraus leer bleibt - jetzt
  vermerkt. Ausserdem die Behauptung berichtigt, die Auflage ruehre
  audi-dashboard-app.js nicht an: der Stylesheet-Loader wurde dort ergaenzt.

Geprueft: Panel laedt, alle neun pyscript-Entitaeten werden veroeffentlicht,
Zuordnen und Zuruecksetzen funktionieren, keine neuen Fehler im Protokoll.
This commit is contained in:
Paul Nothaft
2026-08-13 16:10:19 +02:00
parent bc2834e867
commit e1180f8e34
9 changed files with 133 additions and 47 deletions
+17 -13
View File
@@ -91,14 +91,16 @@ Root files: `dashboard-muster*.html` = original static prototype (superseded, re
`bauauftrag.md`/`.html` = original build brief (historical), `DESIGN_BRIEF_DATAMETRIC360.md` = the `bauauftrag.md`/`.html` = original build brief (historical), `DESIGN_BRIEF_DATAMETRIC360.md` = the
prompt for the Claude Design project. prompt for the Claude Design project.
**`homeassistant/` in one paragraph:** 10 pyscript scripts + 4 modules (`pyscript/modules/`); trip **`homeassistant/` in one paragraph:** 10 pyscript scripts + 5 modules (`pyscript/modules/`); trip
detection via the iPhone WLAN sensor with pause tolerance, two-stage trip completion via HA history detection via the FMM003 ignition sensor with pause tolerance (the iPhone WLAN sensor is gone — see
screening (odometer often updates only on the next trip), fill-up detection on fuel-level rise, section B), two-stage trip completion via HA history screening (odometer often updates only on the
Shell PDF parser (`data/shell_beleg_parser.py`, subprocess, the only tested part of the repo), tire next trip), fill-up detection on fuel-level rise, Shell PDF parser (`data/shell_beleg_parser.py`,
km counter, backup, self-update, image management. Frontend: one file `www/audi-dashboard-app.js` subprocess, the only tested part of the repo), tire km counter, backup, self-update, image
(3,130 lines, custom element, no framework/bundler), 5 tabs + ~19 detail routes, cache-busting via management. Frontend: one file `www/audi-dashboard-app.js`
`audi-dashboard-version.json` + loader stub. Entity IDs configured centrally in (~4,000 lines, custom element, no framework/bundler), 5 tabs + ~19 detail routes, cache-busting via
`pyscript/modules/einstellungen.py` (the one file edited before install). Deploy: `update.ps1` `audi-dashboard-version.json` + loader stub. Entity IDs are assigned **in the UI** (Settings →
Fahrzeug einrichten → Setup), stored as overrides in `data/entitaeten.json`;
`pyscript/modules/einstellungen.py` holds only the built-in defaults. Deploy: `update.ps1`
(robocopy to Samba share) or — still inactive — self-update from git. (robocopy to Samba share) or — still inactive — self-update from git.
**`companion-app/` — what exists:** dependency-free, strictly typed TS data layer (`src/api/`): **`companion-app/` — what exists:** dependency-free, strictly typed TS data layer (`src/api/`):
@@ -111,8 +113,10 @@ capacitor/iframe/browser (`umgebung.ts`), types + entity table (`types.ts`), fac
**DataMetric360 architecture (short — details in `COMPANION_APP_ARCHITECTURE.md`):** **DataMetric360 architecture (short — details in `COMPANION_APP_ARCHITECTURE.md`):**
- Future data source: **Teltonika FMM003** on the CAN bus, fully replacing the iPhone WLAN sensor - Future data source: **Teltonika FMM003** on the CAN bus, fully replacing the iPhone WLAN sensor
and the VAG integration. and the VAG integration.
- **Current data path (since 2026-08-11): Codec JSON → MQTT/TLS → Mosquitto add-on → HA.** Traccar - **Current data path (since 2026-08-12): FMM003 → flespi (native Codec8/TCP, IMEI auth) → HA.**
is **dropped** (the decision log deliberately remains in the architecture doc, marked ÜBERHOLT). Both earlier directions are dropped: Traccar first, then the self-hosted MQTT/TLS broker — the
router's port-forward turned out to be unusable. The decision logs deliberately remain in the
architecture doc, marked ÜBERHOLT. Details and open steps in section B below.
- Frontend access: HA REST + WebSocket with a long-lived access token (no `hass` object). - Frontend access: HA REST + WebSocket with a long-lived access token (no `hass` object).
- External access: Cloudflare Tunnel + reverse proxy with a path allowlist; HA itself stays - External access: Cloudflare Tunnel + reverse proxy with a path allowlist; HA itself stays
unreachable. Domain **`datametric360.app`** registered (all-inkl, 2026-08-11). unreachable. Domain **`datametric360.app`** registered (all-inkl, 2026-08-11).
@@ -317,9 +321,9 @@ capacitor/iframe/browser (`umgebung.ts`), types + entity table (`types.ts`), fac
Parkplatz" state, fuel/range, and Route (Google Maps deep link) / Teilen (Web Share API) Parkplatz" state, fuel/range, and Route (Google Maps deep link) / Teilen (Web Share API)
actions. Backend: new `STANDORT_TRACKER` entity-ID setting in `einstellungen.py` (device_ actions. Backend: new `STANDORT_TRACKER` entity-ID setting in `einstellungen.py` (device_
tracker with lat/lon attributes), published via a new `_standort()` helper in tracker with lat/lon attributes), published via a new `_standort()` helper in
`frontend_veroeffentlichung.py`**left empty/unconfigured**, no live vehicle GPS source `frontend_veroeffentlichung.py`. The built-in default points at the author's test device
exists yet (same FMM003/flespi gap as section B); the tile shows "Kein GPS-Signal vom (`device_tracker.testzone_fmm003`); until the FMM003 actually delivers positions (section B),
Fahrzeug" until that's wired up. Verified end-to-end in `audi_ha_test` with a manually that entity does not exist and the tile shows "Kein GPS-Signal vom Fahrzeug". Verified end-to-end in `audi_ha_test` with a manually
created `device_tracker.test_fahrzeug` test entity (tile, fullscreen map, and menu all created `device_tracker.test_fahrzeug` test entity (tile, fullscreen map, and menu all
confirmed rendering/populating correctly); the device's own location (browser Geolocation confirmed rendering/populating correctly); the device's own location (browser Geolocation
API) and the Route/Teilen deep links work independently of the vehicle-GPS gap. Pushed to API) and the Route/Teilen deep links work independently of the vehicle-GPS gap. Pushed to
+16
View File
@@ -1,5 +1,21 @@
# Audi Dashboard — Technical Specification # Audi Dashboard — Technical Specification
> ## ⚠️ Teilweise überholt (Stand 2026-08-13)
>
> Dieses Dokument beschreibt den Stand **vor** der FMM003-Umstellung. Drei Dinge haben sich seither
> geändert und gelten in diesem Dokument durchgehend als veraltet:
>
> 1. **Fahrterkennung** läuft über den Zündungssensor des FMM003, nicht mehr über den
> iPhone-WLAN-Sensor. `sensor.iphone_wifi_connection` und `fahrzeug.wlan_name` sind entfernt.
> 2. **Datenquelle** ist der FMM003 über flespi (Codec8/TCP), nicht mehr die HACS-Integration
> `TommiG1/HA_VAG-EU-Data-Act`.
> 3. **Entity-IDs** werden im Setup-Menü der App zugeordnet (Einstellungen → Fahrzeug einrichten →
> Setup) und in `data/entitaeten.json` abgelegt. `einstellungen.py` enthält nur noch die
> eingebauten Vorgabewerte und ist keine Pflicht-Bearbeitung mehr vor der Installation.
>
> Alles Übrige — Datenmodell, Geschäftsregeln, Frontend-Aufbau, §7 „Known Gaps" — gilt weiterhin.
> Den aktuellen Stand führt `AGENTS.md`, Abschnitt B.
**Audience:** an AI coding agent picking up this codebase with no prior context. **Audience:** an AI coding agent picking up this codebase with no prior context.
**Scope:** the Home Assistant panel (`homeassistant/`) — the finished, in-use app. A separate sibling project, `design-system/`, is a standalone React component-library replica of this app's visual language, built solely to feed Claude Design (`claude.ai/design`) via the `/design-sync` skill; it is **not** part of this app and deliberately excludes Audi brand assets. `COMPANION_APP_ARCHITECTURE.md` documents a third, **not-yet-built** sibling project — an iOS/Android/HA-iframe companion app fed by a Teltonika FMM003 tracker instead of the WLAN-sensor/VAG-integration data sources described below; its design is decided but no code exists yet. Don't confuse any of the three. **Scope:** the Home Assistant panel (`homeassistant/`) — the finished, in-use app. A separate sibling project, `design-system/`, is a standalone React component-library replica of this app's visual language, built solely to feed Claude Design (`claude.ai/design`) via the `/design-sync` skill; it is **not** part of this app and deliberately excludes Audi brand assets. `COMPANION_APP_ARCHITECTURE.md` documents a third, **not-yet-built** sibling project — an iOS/Android/HA-iframe companion app fed by a Teltonika FMM003 tracker instead of the WLAN-sensor/VAG-integration data sources described below; its design is decided but no code exists yet. Don't confuse any of the three.
+2 -2
View File
@@ -14,9 +14,9 @@ Das ist der **Entwurf**, nicht der lauffähige Code. Umgesetzt wird er später i
| `DataMetric360.dc.html` | Einstiegsdatei des Entwurfs | | `DataMetric360.dc.html` | Einstiegsdatei des Entwurfs |
| `DM360.dc.html` | Die eigentlichen Bildschirme und Zustände | | `DM360.dc.html` | Die eigentlichen Bildschirme und Zustände |
| `DataMetric360 Board.dc.html` | Vergleichsansicht: dieselbe App gleichzeitig als Smartphone- und Großbildschirm-Rahmen nebeneinander, mit Bildschirm-/Theme-Umschalter. Steuert beide Rahmen per `postMessage`. | | `DataMetric360 Board.dc.html` | Vergleichsansicht: dieselbe App gleichzeitig als Smartphone- und Großbildschirm-Rahmen nebeneinander, mit Bildschirm-/Theme-Umschalter. Steuert beide Rahmen per `postMessage`. |
| `dm360-host.html` | Lädt `audi-dashboard-app.js` in einem eigenen `<audi-dashboard-panel>`, hängt danach `audi-dashboard-ios.css` in dessen Shadow-Root - der eigentliche Träger hinter dem Board. | | `dm360-host.html` | Lädt `audi-dashboard-app.js` in einem eigenen `<audi-dashboard-panel>`, hängt danach `audi-dashboard-ios.css` in dessen Shadow-Root - der eigentliche Träger hinter dem Board. **Achtung: beide Dateien liegen nicht in diesem Ordner.** Aus der Repo-Kopie heraus bleibt das Board deshalb leer; es läuft nur im Claude-Design-Projekt, wo sie mit hochgeladen sind. |
| `ios-frame.jsx` | iOS-26-Gerätekachel (Statusleiste, Dynamic Island, Home-Indikator) - reine Optik, keine App-Logik. | | `ios-frame.jsx` | iOS-26-Gerätekachel (Statusleiste, Dynamic Island, Home-Indikator) - reine Optik, keine App-Logik. |
| `audi-dashboard-ios.css` | **Die eigentliche Neuerung dieses Exports.** Optik-Auflage über `audi-dashboard.css`: iOS-natives Erscheinungsbild (44-px-Zeilen, transluzente Tab-Leiste, iOS-Schalter) plus - wichtig - die responsive Großbildschirm-Umsetzung per `@container (min-width:860px)` (Tab-Leiste wird zur seitlichen Navigation). **Seit 2026-08-11 in `homeassistant/www/` scharf geschaltet und im Docker-Testcontainer bei 375px und 1280px verifiziert** - rührt `audi-dashboard-app.js` nicht an, wie behauptet. | | `audi-dashboard-ios.css` *(liegt in `../homeassistant/www/`, nicht in diesem Ordner)* | **Die eigentliche Neuerung dieses Exports.** Optik-Auflage über `audi-dashboard.css`: iOS-natives Erscheinungsbild (44-px-Zeilen, transluzente Tab-Leiste, iOS-Schalter) plus - wichtig - die responsive Großbildschirm-Umsetzung per `@container (min-width:860px)` (Tab-Leiste wird zur seitlichen Navigation). **Seit 2026-08-11 in `homeassistant/www/` scharf geschaltet und im Docker-Testcontainer bei 375px und 1280px verifiziert.** Die Auflage selbst ist rein additiv; `audi-dashboard-app.js` wurde nur um den Stylesheet-Loader ergänzt, der sie nachlädt (siehe unten). |
| `datametric360-ios.css` | Dieselbe Optik-Auflage, aber für die `@audi-dash/ui`-Komponentenklassen (`.ads-*`) statt für die Panel-eigenen Klassen - für später, wenn `companion-app/` eine UI bekommt. | | `datametric360-ios.css` | Dieselbe Optik-Auflage, aber für die `@audi-dash/ui`-Komponentenklassen (`.ads-*`) statt für die Panel-eigenen Klassen - für später, wenn `companion-app/` eine UI bekommt. |
| `support.js` | Hilfsfunktionen des Entwurfs | | `support.js` | Hilfsfunktionen des Entwurfs |
| `assets/` | Icons, Logos, Typenschilder und Schriften, die der Entwurf verwendet | | `assets/` | Icons, Logos, Typenschilder und Schriften, die der Entwurf verwendet |
+20 -4
View File
@@ -93,8 +93,13 @@ Anders als früher wird dafür **nicht** mehr `pyscript/modules/
einstellungen.py` von Hand bearbeitet — das übernimmt ein grafisches einstellungen.py` von Hand bearbeitet — das übernimmt ein grafisches
Setup-Menü direkt in der App: **Mein Audi → Einstellungen → Fahrzeug Setup-Menü direkt in der App: **Mein Audi → Einstellungen → Fahrzeug
einrichten → Einrichten → „Setup — Sensoren zuordnen"** (letzter Punkt, einrichten → Einrichten → „Setup — Sensoren zuordnen"** (letzter Punkt,
erscheint erst nach Klick auf „Einrichten"). Dafür muss die App bereits erscheint erst nach Klick auf „Einrichten").
laufen, siehe Schritt 9 zuerst.
> **Reihenfolge beachten:** Dieser Schritt braucht eine bereits laufende App.
> Arbeite deshalb erst die Schritte 5 bis 9 ab (Token, Neustart, Prüfung,
> Restarbeiten, Frontend) und komm dann hierher zurück. Schritt 7 verweist
> seinerseits auf die hier vorgenommene Zuordnung — das ist kein Widerspruch,
> sondern schlicht die Reihenfolge: erst starten, dann zuordnen, dann prüfen.
Das Setup-Menü listet jede Sensor-Rolle, die die App kennt (Zündung/ Das Setup-Menü listet jede Sensor-Rolle, die die App kennt (Zündung/
Fahrterkennung, Kilometerstand, Tankfüllstand, Standort, Batteriespannung, Fahrterkennung, Kilometerstand, Tankfüllstand, Standort, Batteriespannung,
@@ -104,6 +109,16 @@ anzeigen" grenzt auf die erwartete Domäne/Einheit ein) und schreibt die
Auswahl direkt in eine Override-Datei — `einstellungen.py` selbst bleibt Auswahl direkt in eine Override-Datei — `einstellungen.py` selbst bleibt
unverändert. unverändert.
Die Override-Datei ist `/config/audi_dashboard/entitaeten.json`. Sie wird von
der automatischen Sicherung und vom Backup-Export mit erfasst; ein
Wiederherstellen spielt die Zuordnung also mit zurück.
**Hinweis zu den Vorgabewerten:** Drei Rollen sind in `einstellungen.py` mit
den Test-Entitäten der Entwicklungsinstanz vorbelegt (Zündung, Batterie-
spannung, Standort — jeweils `*_testzone_fmm003`). Auf einer frischen
Installation zeigen sie ins Leere; das Setup-Menü meldet dann „Entität nicht
gefunden". Einfach die eigenen Entitäten zuordnen, damit ist es erledigt.
**Zwingend, damit die Fahrterkennung läuft:** **Zwingend, damit die Fahrterkennung läuft:**
- **Zündung/ACC-Status** (`ZUENDUNG_SENSOR`) — ein `binary_sensor`, `on` - **Zündung/ACC-Status** (`ZUENDUNG_SENSOR`) — ein `binary_sensor`, `on`
= Fahrt läuft. Kommt vom Teltonika FMM003 (z. B. = Fahrt läuft. Kommt vom Teltonika FMM003 (z. B.
@@ -226,8 +241,9 @@ Der `panel_custom`-Eintrag aus Schritt 3 zeigt auf
Prototyp. Das Gerät braucht dafür zusätzlich zur Tailscale-Verbindung Prototyp. Das Gerät braucht dafür zusätzlich zur Tailscale-Verbindung
normalen Internetzugang — sonst bleibt die Karte auf der Fahrt- und normalen Internetzugang — sonst bleibt die Karte auf der Fahrt- und
Tankvorgang-Detailseite leer. Tankvorgang-Detailseite leer.
- **Statistik-Seite** zeigt weiterhin Beispielzahlen aus dem Prototyp, - **Statistik-Seite** wertet die eigenen Fahrten und Tankvorgänge echt aus
keine echte Auswertung der eigenen Fahrten — eigenes Arbeitspaket. (Zeiträume, Verbrauch, Tag/Nacht, privat/Arbeitsweg). Sie bleibt nur so
lange leer, wie noch keine Fahrten und Tankungen erfasst sind.
## Schritt 10 — künftige Updates einspielen, ohne die App neu zu bauen ## Schritt 10 — künftige Updates einspielen, ohne die App neu zu bauen
+16 -6
View File
@@ -96,22 +96,26 @@ Datendateien — nie App-Code oder -Konfiguration.
`fahrterkennung.py`. `fahrterkennung.py`.
- **Bilder fehlen** (§7a) — Ordner `www/bilder/` ist noch leer. Layout - **Bilder fehlen** (§7a) — Ordner `www/bilder/` ist noch leer. Layout
springt nicht (Maße sind reserviert), aber die Flächen bleiben leer. springt nicht (Maße sind reserviert), aber die Flächen bleiben leer.
- **Statistik-Seite** zeigt weiterhin Beispielzahlen aus dem Prototyp, keine
echte Auswertung der erfassten Fahrten.
- **Leaflet lädt per CDN**, wie im Prototyp — braucht zusätzlich zu - **Leaflet lädt per CDN**, wie im Prototyp — braucht zusätzlich zu
Tailscale echten Internetzugang auf dem Gerät. Tailscale echten Internetzugang auf dem Gerät.
## Was schon funktioniert (getestet) ## Was schon funktioniert (getestet)
- Fahrterkennung über WLAN mit Pausenregel (`task.unique` + `task.sleep`) - Fahrterkennung über den Zündungssensor mit Pausenregel (`task.unique` + `task.sleep`);
bis 2026-08-12 lief sie über den WLAN-Sensor des iPhones
- Zweistufiger Fahrtabschluss per Recorder-Historie-Screening, inklusive - Zweistufiger Fahrtabschluss per Recorder-Historie-Screening, inklusive
Verkettung direkt anschließender Fahrten Verkettung direkt anschließender Fahrten
- Reifenzähler per direkter Subtraktion, kein `utility_meter` - Reifenzähler per direkter Subtraktion, kein `utility_meter`
- Belegverarbeitung inklusive 97-%-Volltankungsregel und Duplikatserkennung - Belegverarbeitung mit Duplikatserkennung über `receipt_key` (die zunächst
über `receipt_key` geplante 97-%-Volltankungsregel wurde per Änderungswunsch entfernt, siehe
Kopfkommentar in `pyscript/belegverarbeitung.py`)
- Manuelle Fallback-Wege für Fahrten und Tankvorgänge ohne Beleg - Manuelle Fallback-Wege für Fahrten und Tankvorgänge ohne Beleg
- Frontend: Übersicht, Fahrtenliste, Navigation zwischen allen Ansichten, - Frontend: Übersicht, Fahrtenliste, Navigation zwischen allen Ansichten,
Datenadapter Profil→Oberfläche, Schreibaktionen über `hass.callService` Datenadapter Profil→Oberfläche, Schreibaktionen über `hass.callService`
- Statistik-Seite mit echter Auswertung aus Fahrten und Tankvorgängen
(Zeiträume, Verbrauch, Tag/Nacht, privat/Arbeitsweg)
- Setup-Menü für die Sensor-Zuordnung, inklusive Sicherung in
`data/entitaeten.json`
## Dateiübersicht ## Dateiübersicht
@@ -129,12 +133,18 @@ homeassistant/
│ ├── modules/ │ ├── modules/
│ │ ├── einstellungen.py zentrale Entity-ID-Konfiguration (Schritt 4) │ │ ├── einstellungen.py zentrale Entity-ID-Konfiguration (Schritt 4)
│ │ ├── profil.py Datenzugriff (§6) │ │ ├── profil.py Datenzugriff (§6)
│ │ ├── entitaeten.py Sensor-Zuordnung aus dem Setup-Menü
│ │ ├── fahrtabschluss_logik.py Screening-Logik (§7.2) │ │ ├── fahrtabschluss_logik.py Screening-Logik (§7.2)
│ │ └── frontend_veroeffentlichung.py Zustände fürs Frontend (§10 Punkt 7 Ersatz) │ │ └── frontend_veroeffentlichung.py Zustände fürs Frontend (§10 Punkt 7 Ersatz)
│ ├── fahrterkennung.py §7.1 │ ├── fahrterkennung.py §7.1 (Zündungssensor, früher WLAN)
│ ├── fahrtabschluss.py §7.2 (Trigger-Registrierung) │ ├── fahrtabschluss.py §7.2 (Trigger-Registrierung)
│ ├── tankerkennung.py §7.4 (automatische Erkennung am Füllstandsanstieg)
│ ├── reifenzaehler.py §7.5 │ ├── reifenzaehler.py §7.5
│ ├── belegverarbeitung.py §7.4, §7.7 │ ├── belegverarbeitung.py §7.4, §7.7
│ ├── batterieverlauf.py 12-V-Spannung, Tagesminimum/-maximum
│ ├── bilderverwaltung.py Upload/Löschen der Fahrzeugfotos
│ ├── backup.py tägliche Sicherung und Wiederherstellung
│ ├── updateverwaltung.py Update suchen und installieren
│ └── frontend_api.py Lese-/Schreib-Anbindung fürs Frontend │ └── frontend_api.py Lese-/Schreib-Anbindung fürs Frontend
├── www/ ├── www/
│ ├── audi-dashboard-panel.js Lade-Stub (zeigt configuration.yaml hierher), ändert sich kaum │ ├── audi-dashboard-panel.js Lade-Stub (zeigt configuration.yaml hierher), ändert sich kaum
@@ -1,5 +1,5 @@
{ {
"_hinweis": "Vorlage ohne echte Fahrzeug-/Personendaten. Vor der Installation nach fahrzeugprofil.json kopieren und die Platzhalter unten ersetzen (VIN, Kennzeichen, Erstzulassung, HU-Termin, WLAN-Name, Versicherung, Werkstatt, Servicebuch) - am einfachsten über den Button 'Einrichten' in den Einstellungen der App, sobald sie einmal läuft. Diese Datei selbst bleibt bewusst im Repo (siehe .gitignore), die echte fahrzeugprofil.json nicht.", "_hinweis": "Vorlage ohne echte Fahrzeug-/Personendaten. Vor der Installation nach fahrzeugprofil.json kopieren und die Platzhalter unten ersetzen (VIN, Kennzeichen, Erstzulassung, HU-Termin, Versicherung, Werkstatt, Servicebuch) - am einfachsten über den Button 'Einrichten' in den Einstellungen der App, sobald sie einmal läuft. Diese Datei selbst bleibt bewusst im Repo (siehe .gitignore), die echte fahrzeugprofil.json nicht.",
"fahrzeug": { "fahrzeug": {
"fin": "", "fin": "",
+17 -4
View File
@@ -21,10 +21,16 @@
ohne Cache und lädt den eigentlichen Code darüber immer frisch nach ohne Cache und lädt den eigentlichen Code darüber immer frisch nach
(siehe Kopfkommentar in www/audi-dashboard-panel.js). (siehe Kopfkommentar in www/audi-dashboard-panel.js).
Absichtlich NICHT kopiert: data/ - das würde ein bereits laufendes Absichtlich NICHT kopiert: die Datenbestände in data/ - das würde ein
Fahrzeugprofil, die Fahrten- und Tankvorgänge-Archive überschreiben. Die bereits laufendes Fahrzeugprofil, die Fahrten- und Tankvorgänge-Archive
data/-Dateien sind nur die Erstbefüllung für die einmalige Installation sowie die Sensor-Zuordnung überschreiben. Diese Dateien sind nur die
(siehe INSTALL.md Schritt 2). Erstbefüllung für die einmalige Installation (siehe INSTALL.md Schritt 2).
Eine Ausnahme davon ist shell_beleg_parser.py: die Datei liegt zwar in
data/, ist aber Code, kein Datenbestand - belegverarbeitung.py ruft sie
als Unterprozess aus /config/audi_dashboard/ auf. Ohne diese Ausnahme
käme jede Änderung am Belegleser (dem einzigen getesteten Teil des
Projekts) auf keiner Instanz an.
.PARAMETER Ziel .PARAMETER Ziel
Pfad zum config-Verzeichnis von Home Assistant, z. B. ein per Samba Pfad zum config-Verzeichnis von Home Assistant, z. B. ein per Samba
@@ -55,6 +61,13 @@ Copy-Item "$hier\www\audi-dashboard-panel.js" "$Ziel\www\audi-dashboard-panel.js
Copy-Item "$hier\www\audi-dashboard.css" "$Ziel\www\audi-dashboard.css" -Force Copy-Item "$hier\www\audi-dashboard.css" "$Ziel\www\audi-dashboard.css" -Force
Copy-Item "$hier\www\audi-dashboard-ios.css" "$Ziel\www\audi-dashboard-ios.css" -Force Copy-Item "$hier\www\audi-dashboard-ios.css" "$Ziel\www\audi-dashboard-ios.css" -Force
# Code, der in data/ liegt (siehe Kopfkommentar) - der Ordner selbst wird
# bewusst nicht gespiegelt, diese eine Datei aber schon.
if (Test-Path "$Ziel\audi_dashboard") {
Copy-Item "$hier\data\shell_beleg_parser.py" "$Ziel\audi_dashboard\shell_beleg_parser.py" -Force
Write-Host " Belegleser aktualisiert"
}
if (Test-Path "$hier\www\bilder") { if (Test-Path "$hier\www\bilder") {
Write-Host "Kopiere www/bilder/ ..." -ForegroundColor Cyan Write-Host "Kopiere www/bilder/ ..." -ForegroundColor Cyan
New-Item -ItemType Directory -Force -Path "$Ziel\www\bilder" | Out-Null New-Item -ItemType Directory -Force -Path "$Ziel\www\bilder" | Out-Null
+31 -15
View File
@@ -500,13 +500,6 @@ const STANDORT_PEEK_PX = 96; // sichtbarer Anteil im geschlossenen Zustand, muss
function standortBekannt() { return CAR.standortLat != null && CAR.standortLon != null; } function standortBekannt() { return CAR.standortLat != null && CAR.standortLon != null; }
/* Fahrzeug-Symbol im myAudi-Stil: einfaches Top-down-Glyph, noch fuer
Vorschauen in Gebrauch; die Kartenmarkierung nutzt poi-car (Audi CI). */
function fahrzeugGlyphPfade() {
return `<path d="M5 15.3 6.4 9.9a2 2 0 0 1 1.9-1.5h7.4a2 2 0 0 1 1.9 1.5l1.4 5.4"/>
<path d="M4.6 15.3h14.8a.9.9 0 0 1 .9.9v1.1a.9.9 0 0 1-.9.9H4.6a.9.9 0 0 1-.9-.9v-1.1a.9.9 0 0 1 .9-.9Z"/>
<path d="M7.4 11.4h9.2"/>`;
}
/* Kartenmarkierung: poi-car aus dem Audi-CI-Satz (assets/icons/poi-car.svg). /* Kartenmarkierung: poi-car aus dem Audi-CI-Satz (assets/icons/poi-car.svg).
Die Spitze sitzt unten, deshalb ankert Leaflet am unteren Rand. */ Die Spitze sitzt unten, deshalb ankert Leaflet am unteren Rand. */
function fahrzeugMarkerSVG(px = 34) { function fahrzeugMarkerSVG(px = 34) {
@@ -514,12 +507,14 @@ function fahrzeugMarkerSVG(px = 34) {
} }
/* Fahrzustand fürs Menü: "fährt" solange die aktuelle Fahrt noch offen ist /* Fahrzustand fürs Menü: "fährt" solange die aktuelle Fahrt noch offen ist
(siehe fahrterkennung.py), sonst "steht" - erst ab der in "Fahrten (siehe fahrterkennung.py), sonst "steht". Ohne echtes Bewegungssignal vom
zusammenfassen" hinterlegten Pausenzeit (CONFIG.pauseMin) gilt der Fahrzeug (siehe AGENTS.md, FMM003/flespi offen) ist das die beste Näherung
Stillstand als endgültig genug für "Letzter Parkplatz" statt eines aus den bereits vorhandenen Fahrtdaten.
bloßen Zwischenstopps. Ohne echtes Bewegungssignal vom Fahrzeug (siehe
AGENTS.md, FMM003/flespi offen) ist das die beste Näherung aus den parkplatz unterscheidet einen endgültigen Stillstand (laenger als die in
bereits vorhandenen Fahrtdaten. */ "Fahrten zusammenfassen" hinterlegte Pausenzeit) von einem blossen
Zwischenstopp. Das Feld ist vorbereitet, wird aber derzeit von keiner
Ansicht gelesen - eine "Letzter Parkplatz"-Anzeige gibt es noch nicht. */
function standortZustand() { function standortZustand() {
const t = TRIPS[0]; const t = TRIPS[0];
if (t && t.status === "offen") return { faehrt: true, seitTs: null, parkplatz: false }; if (t && t.status === "offen") return { faehrt: true, seitTs: null, parkplatz: false };
@@ -812,7 +807,8 @@ function vStandort() {
<div class="standort-vollbild"> <div class="standort-vollbild">
<div id="smap" class="standort-karte"></div> <div id="smap" class="standort-karte"></div>
<div class="kartensteuerung"> <div class="kartensteuerung">
<button type="button" data-standort-layer aria-label="Kartendarstellung wählen"> <button type="button" data-standort-layer aria-pressed="${SMAP_STIL === "satellit"}"
aria-label="${SMAP_STIL === "satellit" ? "Satellitenansicht, zur Straßenkarte wechseln" : "Straßenkarte, zur Satellitenansicht wechseln"}">
${ciSVG(CI.mapLayer, 22)} ${ciSVG(CI.mapLayer, 22)}
</button> </button>
<button type="button" data-standort-fahrzeug aria-label="Fahrzeug auf der Karte suchen"> <button type="button" data-standort-fahrzeug aria-label="Fahrzeug auf der Karte suchen">
@@ -827,7 +823,12 @@ function vStandort() {
</div> </div>
<div class="standortmenu" id="standortmenu"> <div class="standortmenu" id="standortmenu">
<div class="standortmenu-griffzone"> <div class="standortmenu-griffzone">
<div class="standortmenu-griff"></div> <!-- Als Knopf, nicht als reines div: sonst laesst sich das Menue
ausschliesslich per Wischgeste oder Klick auf den Kartenmarker
oeffnen und ist mit Tastatur gar nicht erreichbar. -->
<button type="button" class="standortmenu-griff" data-standort-umschalten
aria-expanded="${STANDORTMENU_OFFEN}" aria-controls="standortmenu"
aria-label="Fahrzeugdetails ${STANDORTMENU_OFFEN ? "einklappen" : "aufklappen"}"></button>
<button type="button" class="standortmenu-schliessen" data-standort-schliessen aria-label="Schließen"> <button type="button" class="standortmenu-schliessen" data-standort-schliessen aria-label="Schließen">
<svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M5 5l10 10M15 5 5 15"/></svg> <svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M5 5l10 10M15 5 5 15"/></svg>
</button> </button>
@@ -3095,6 +3096,12 @@ function ereignisseVerdrahten() {
ROOT.getElementById("theme").addEventListener("click", () => { ROOT.getElementById("theme").addEventListener("click", () => {
themeSetzen(HOST.dataset.theme === "nacht" ? "tag" : "nacht"); themeSetzen(HOST.dataset.theme === "nacht" ? "tag" : "nacht");
themeIcon(); themeIcon();
/* Neu zeichnen, damit auch die Leaflet-Kacheln den Stil wechseln: die
Kachel-URL wird beim Aufbau aus TILES[theme] gewaehlt und nicht
nachtraeglich aktualisiert. Ohne dies blieb die Karte bis zum naechsten
Backend-Update im alten Stil - seit der Dauerkarte auf der Uebersicht
deutlich sichtbar. */
render();
}); });
// Wischgeste über Pointer Events (nicht Touch Events), damit dieselbe // Wischgeste über Pointer Events (nicht Touch Events), damit dieselbe
// Logik auch mit der Maus funktioniert (z. B. beim Testen im Browser). // Logik auch mit der Maus funktioniert (z. B. beim Testen im Browser).
@@ -3316,6 +3323,14 @@ function ereignisseVerdrahten() {
// Klick irgendwo sonst im Popup (aber nicht auf den Catcher, der schon // Klick irgendwo sonst im Popup (aber nicht auf den Catcher, der schon
// oben behandelt wurde) schließt ein offenes Dropdown, ohne das ganze // oben behandelt wurde) schließt ein offenes Dropdown, ohne das ganze
// Popup zu verlassen. // Popup zu verlassen.
/* Den Filterschalter VOR dem Schliessen der Auswahlliste behandeln.
Sonst ersetzt render() unten das Overlay-DOM, bevor das
change-Ereignis des Kontrollkaestchens ausgeliefert wird - der
Schalter sprang dann beim naechsten Zeichnen einfach zurueck. */
if (e.target.closest("[data-setup-nurpassend]") || e.target.closest(".switch")) {
const kasten = ROOT.querySelector("[data-setup-nurpassend]");
if (kasten) { setupNurPassend = !setupNurPassend; render(); return; }
}
if (setupSucheOffen && !e.target.closest(".entitaet-combo")) { setupSucheOffen = null; setupSuchtext = ""; render(); return; } if (setupSucheOffen && !e.target.closest(".entitaet-combo")) { setupSucheOffen = null; setupSuchtext = ""; render(); return; }
return; return;
} }
@@ -3590,6 +3605,7 @@ function ereignisseVerdrahten() {
if (art) { const tr = TRIPS.find((x) => x.trip_id === art.dataset.art); tr.art = tr.art === "arbeitsweg" ? "privat" : "arbeitsweg"; render(); return; } if (art) { const tr = TRIPS.find((x) => x.trip_id === art.dataset.art); tr.art = tr.art === "arbeitsweg" ? "privat" : "arbeitsweg"; render(); return; }
// Standort (Vollbild) - Karten-/Menü-Bedienung manipuliert SMAP/das Menü- // Standort (Vollbild) - Karten-/Menü-Bedienung manipuliert SMAP/das Menü-
// DOM direkt statt über render() (siehe Kopfkommentar Standort-Abschnitt). // DOM direkt statt über render() (siehe Kopfkommentar Standort-Abschnitt).
if (e.target.closest("[data-standort-umschalten]")) { standortMenuUmschalten(); return; }
if (e.target.closest("[data-standort-layer]")) { SMAP_STIL = SMAP_STIL === "satellit" ? "strasse" : "satellit"; standortTiles(); return; } if (e.target.closest("[data-standort-layer]")) { SMAP_STIL = SMAP_STIL === "satellit" ? "strasse" : "satellit"; standortTiles(); return; }
if (e.target.closest("[data-standort-fahrzeug]")) { standortAufFahrzeug(); return; } if (e.target.closest("[data-standort-fahrzeug]")) { standortAufFahrzeug(); return; }
if (e.target.closest("[data-standort-user]")) { standortAufUser(); return; } if (e.target.closest("[data-standort-user]")) { standortAufUser(); return; }
+13 -2
View File
@@ -749,7 +749,19 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
} }
.standortmenu.offen { transform: translateY(0); } .standortmenu.offen { transform: translateY(0); }
.standortmenu-griffzone { padding: 10px 0 14px; touch-action: none; cursor: grab; position: relative; } .standortmenu-griffzone { padding: 10px 0 14px; touch-action: none; cursor: grab; position: relative; }
.standortmenu-griff { width: 36px; height: 4px; border-radius: 2px; background: var(--line-strong); margin: 0 auto; } /* Der Griff ist jetzt ein Knopf (Tastaturzugang zum Menue). Die Knopf-
Voreinstellungen des Browsers werden deshalb zurueckgesetzt; die
Trefferflaeche waechst per Pseudo-Element auf 44px, ohne den sichtbaren
Strich zu vergroessern. */
.standortmenu-griff {
position: relative; display: block; width: 36px; height: 4px;
border: 0; padding: 0; border-radius: 2px;
background: var(--line-strong); margin: 0 auto; cursor: pointer;
}
.standortmenu-griff::after {
content: ""; position: absolute; left: 50%; top: 50%;
transform: translate(-50%, -50%); width: 88px; height: 44px;
}
.standortmenu-schliessen { .standortmenu-schliessen {
position: absolute; top: 14px; right: 0; width: 32px; height: 32px; border-radius: 50%; position: absolute; top: 14px; right: 0; width: 32px; height: 32px; border-radius: 50%;
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;
@@ -762,7 +774,6 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; }
.standortmenu-adresse { font-size: 13.5px; color: var(--fg2); margin-top: 20px; } .standortmenu-adresse { font-size: 13.5px; color: var(--fg2); margin-top: 20px; }
.standortmenu-status { display: flex; align-items: center; gap: 9px; margin-top: 8px; font-size: 13.5px; color: var(--fg2); } .standortmenu-status { display: flex; align-items: center; gap: 9px; margin-top: 8px; font-size: 13.5px; color: var(--fg2); }
.standortmenu-status svg { color: var(--fg3); flex: 0 0 auto; } .standortmenu-status svg { color: var(--fg3); flex: 0 0 auto; }
.dot.neutral { background: var(--fg3); }
.standortmenu-werte { display: flex; align-items: center; gap: 10px; margin-top: 8px; font-size: 13.5px; color: var(--fg2); } .standortmenu-werte { display: flex; align-items: center; gap: 10px; margin-top: 8px; font-size: 13.5px; color: var(--fg2); }
.standortmenu-werte svg { color: var(--fg3); flex: 0 0 auto; } .standortmenu-werte svg { color: var(--fg3); flex: 0 0 auto; }
.standortmenu-aktionen { display: flex; gap: 10px; margin-top: 18px; padding-bottom: 4px; } .standortmenu-aktionen { display: flex; gap: 10px; margin-top: 18px; padding-bottom: 4px; }