Phase 2: Doku an Codestand angleichen, profil_lesen haerten
Doku-Drift behoben: die Statistik-Seite rechnet laengst echt (Behauptung stand an drei Stellen), INSTALL.md nannte in Schritt 4 vier Variablennamen, die es nie gab, README erwaehnte die entfernte 97-Prozent-Volltankungsregel und liess fuenf pyscript-Dateien in der Uebersicht aus. Obsoleter TODO-Kommentar in belegverarbeitung.py entfernt. profil_lesen() gibt bei fehlender oder beschaedigter Profildatei None zurueck statt zu werfen; alle sieben Aufrufstellen fangen den Fall ab. An der Testinstanz geprueft: Datei entfernt, es folgt eine verstaendliche Fehlermeldung mit Verweis auf INSTALL.md statt eines Tracebacks pro Trigger-Durchlauf, Home Assistant laeuft normal weiter.
This commit is contained in:
@@ -103,10 +103,18 @@ zeigt die Oberfläche „unbekannt" statt eines Werts — kein Absturz):
|
|||||||
|
|
||||||
3. Ebenfalls in Entwicklerwerkzeuge → Zustände suchen:
|
3. Ebenfalls in Entwicklerwerkzeuge → Zustände suchen:
|
||||||
- Reichweite → `RANGE_SENSOR`
|
- Reichweite → `RANGE_SENSOR`
|
||||||
- Türstatus (falls als ein Sammel-Sensor vorhanden) → `DOORS_SENSOR`
|
- Türstatus → `TUER_SENSOREN` (Liste mit vier Einzelsensoren:
|
||||||
- Fensterstatus → `WINDOWS_SENSOR`
|
vorne links/rechts, hinten links/rechts)
|
||||||
- Verriegelung (Domäne `lock.`) → `LOCK_ENTITY`
|
- Fensterstatus → `FENSTER_SENSOREN` (ebenfalls vier Einzelsensoren)
|
||||||
- Batteriespannung, falls überhaupt vorhanden → `BATTERY_VOLTAGE_SENSOR`
|
- Verriegelung → `TUERSCHLOSS_SENSOREN` (vier Einzelsensoren; die
|
||||||
|
Integration liefert kein `lock.`-Entity)
|
||||||
|
- Heckklappe und Motorhaube → `HECKKLAPPE_SENSOR`,
|
||||||
|
`HECKKLAPPENSCHLOSS_SENSOR`, `HAUBE_SENSOR`, `HAUBENSCHLOSS_SENSOR`
|
||||||
|
- Service-Fälligkeit → `NAECHSTER_OELWECHSEL_SENSOR`,
|
||||||
|
`OELWECHSEL_STRECKE_SENSOR`, `NAECHSTE_INSPEKTION_SENSOR`,
|
||||||
|
`INSPEKTION_STRECKE_SENSOR`
|
||||||
|
- Batteriespannung, falls überhaupt vorhanden → `BATTERIE_SENSOR`
|
||||||
|
(bei der aktuellen Integration nicht vorhanden, bleibt leer)
|
||||||
|
|
||||||
4. Datei `pyscript/modules/einstellungen.py` öffnen (Samba: direkt im
|
4. Datei `pyscript/modules/einstellungen.py` öffnen (Samba: direkt im
|
||||||
Explorer, Studio Code Server: im Editor) und alle gefundenen Werte
|
Explorer, Studio Code Server: im Editor) und alle gefundenen Werte
|
||||||
@@ -201,8 +209,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
|
||||||
|
|
||||||
|
|||||||
+10
-4
@@ -96,8 +96,6 @@ 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.
|
||||||
|
|
||||||
@@ -107,11 +105,14 @@ Datendateien — nie App-Code oder -Konfiguration.
|
|||||||
- 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)
|
||||||
|
|
||||||
## Dateiübersicht
|
## Dateiübersicht
|
||||||
|
|
||||||
@@ -133,8 +134,13 @@ homeassistant/
|
|||||||
│ │ └── 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
|
||||||
│ ├── 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
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ def _backup_jetzt():
|
|||||||
|
|
||||||
def _letztes_backup_vermerken():
|
def _letztes_backup_vermerken():
|
||||||
p = profil.profil_lesen()
|
p = profil.profil_lesen()
|
||||||
|
if p is None:
|
||||||
|
return
|
||||||
p.setdefault("einstellungen", {})["letztes_backup"] = datetime.datetime.now().isoformat()
|
p.setdefault("einstellungen", {})["letztes_backup"] = datetime.datetime.now().isoformat()
|
||||||
profil.profil_schreiben(p)
|
profil.profil_schreiben(p)
|
||||||
frontend_veroeffentlichung.profil_veroeffentlichen()
|
frontend_veroeffentlichung.profil_veroeffentlichen()
|
||||||
@@ -60,6 +62,8 @@ def audi_dashboard_backup_jetzt():
|
|||||||
@time_trigger("cron(0 4 * * *)")
|
@time_trigger("cron(0 4 * * *)")
|
||||||
def _automatisches_backup():
|
def _automatisches_backup():
|
||||||
p = profil.profil_lesen()
|
p = profil.profil_lesen()
|
||||||
|
if p is None:
|
||||||
|
return
|
||||||
intervall = p.get("einstellungen", {}).get("backup_intervall", "aus")
|
intervall = p.get("einstellungen", {}).get("backup_intervall", "aus")
|
||||||
heute = datetime.date.today()
|
heute = datetime.date.today()
|
||||||
faellig = (
|
faellig = (
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import einstellungen
|
|||||||
import frontend_veroeffentlichung
|
import frontend_veroeffentlichung
|
||||||
import profil
|
import profil
|
||||||
|
|
||||||
PARSER_SKRIPT = "/config/audi_dashboard/shell_beleg_parser.py" # TODO: Datei ablegen
|
PARSER_SKRIPT = "/config/audi_dashboard/shell_beleg_parser.py"
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------- Hilfsfunktionen
|
# --------------------------------------------------------------- Hilfsfunktionen
|
||||||
|
|||||||
@@ -34,11 +34,15 @@ _fahrt_start_ts = None
|
|||||||
|
|
||||||
def _profil_wlan_name():
|
def _profil_wlan_name():
|
||||||
p = profil.profil_lesen()
|
p = profil.profil_lesen()
|
||||||
|
if p is None:
|
||||||
|
return None
|
||||||
return p.get("fahrzeug", {}).get("wlan_name")
|
return p.get("fahrzeug", {}).get("wlan_name")
|
||||||
|
|
||||||
|
|
||||||
def _pausenzeit_sekunden():
|
def _pausenzeit_sekunden():
|
||||||
p = profil.profil_lesen()
|
p = profil.profil_lesen()
|
||||||
|
if p is None:
|
||||||
|
return 15 * 60
|
||||||
return p.get("einstellungen", {}).get("fahrten_pausenzeit_min", 15) * 60
|
return p.get("einstellungen", {}).get("fahrten_pausenzeit_min", 15) * 60
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ import profil
|
|||||||
|
|
||||||
|
|
||||||
def profil_veroeffentlichen():
|
def profil_veroeffentlichen():
|
||||||
state.set("pyscript.audi_dashboard_profil", "aktuell", new_attributes={"daten": profil.profil_lesen()})
|
daten = profil.profil_lesen()
|
||||||
|
if daten is None:
|
||||||
|
return
|
||||||
|
state.set("pyscript.audi_dashboard_profil", "aktuell", new_attributes={"daten": daten})
|
||||||
|
|
||||||
|
|
||||||
def fahrten_veroeffentlichen():
|
def fahrten_veroeffentlichen():
|
||||||
|
|||||||
@@ -49,10 +49,29 @@ def ordner_sicherstellen():
|
|||||||
# ----------------------------------------------------------- Fahrzeugprofil
|
# ----------------------------------------------------------- Fahrzeugprofil
|
||||||
|
|
||||||
def profil_lesen():
|
def profil_lesen():
|
||||||
|
"""Liest das Fahrzeugprofil, oder None wenn es fehlt bzw. beschädigt ist.
|
||||||
|
|
||||||
|
Ohne diese Prüfung reißt eine fehlende Datei (Installation unvollständig,
|
||||||
|
siehe INSTALL.md Schritt 2) jeden Trigger und jeden Service mit, der das
|
||||||
|
Profil braucht — bei laufenden Zeittriggern also im Minutentakt. Jeder
|
||||||
|
Aufrufer muss den None-Fall abfangen."""
|
||||||
|
if not os.path.exists(PROFIL_PFAD):
|
||||||
|
log.error(
|
||||||
|
f"audi_dashboard: {PROFIL_PFAD} fehlt. Siehe INSTALL.md Schritt 2 — "
|
||||||
|
"bis dahin bleiben alle Funktionen aus, die das Profil brauchen."
|
||||||
|
)
|
||||||
|
return None
|
||||||
f = task.executor(io.open, PROFIL_PFAD, "r")
|
f = task.executor(io.open, PROFIL_PFAD, "r")
|
||||||
inhalt = f.read()
|
inhalt = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return json.loads(inhalt)
|
try:
|
||||||
|
return json.loads(inhalt)
|
||||||
|
except ValueError as fehler:
|
||||||
|
log.error(
|
||||||
|
f"audi_dashboard: {PROFIL_PFAD} ist kein gültiges JSON ({fehler}). "
|
||||||
|
"Letztes Backup aus audi_dashboard/backups/ zurückspielen."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def profil_schreiben(profil):
|
def profil_schreiben(profil):
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ def _fortschreiben_und_veroeffentlichen(neuer_km, neuer_aktiv=None):
|
|||||||
Reihenfolge, damit beim Wechseln selbst keine km dem neuen Satz
|
Reihenfolge, damit beim Wechseln selbst keine km dem neuen Satz
|
||||||
zugerechnet werden, die eigentlich noch auf dem alten gefahren wurden."""
|
zugerechnet werden, die eigentlich noch auf dem alten gefahren wurden."""
|
||||||
p = profil.profil_lesen()
|
p = profil.profil_lesen()
|
||||||
|
if p is None:
|
||||||
|
return
|
||||||
reifen = p.setdefault("reifen", {})
|
reifen = p.setdefault("reifen", {})
|
||||||
saetze = reifen.setdefault("saetze", {})
|
saetze = reifen.setdefault("saetze", {})
|
||||||
aktiv = reifen.get("aktiv", "sommer")
|
aktiv = reifen.get("aktiv", "sommer")
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ def _als_zahl(wert):
|
|||||||
|
|
||||||
def _schwelle_prozent():
|
def _schwelle_prozent():
|
||||||
p = profil.profil_lesen()
|
p = profil.profil_lesen()
|
||||||
tankvolumen = p.get("fahrzeug", {}).get("tankvolumen_liter") or STANDARD_TANKVOLUMEN_LITER
|
if p is None:
|
||||||
|
tankvolumen = STANDARD_TANKVOLUMEN_LITER
|
||||||
|
else:
|
||||||
|
tankvolumen = p.get("fahrzeug", {}).get("tankvolumen_liter") or STANDARD_TANKVOLUMEN_LITER
|
||||||
liter_als_prozent = (LITER_SCHWELLE / tankvolumen) * 100
|
liter_als_prozent = (LITER_SCHWELLE / tankvolumen) * 100
|
||||||
return min(liter_als_prozent, PROZENT_SCHWELLE)
|
return min(liter_als_prozent, PROZENT_SCHWELLE)
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
DOM-Anbindung (Shadow DOM statt document, siehe $ weiter unten).
|
DOM-Anbindung (Shadow DOM statt document, siehe $ weiter unten).
|
||||||
|
|
||||||
Bekannte Lücken in diesem Stand:
|
Bekannte Lücken in diesem Stand:
|
||||||
- Statistik-Seite (vStat/ST) zeigt weiterhin Beispielzahlen, keine echte
|
|
||||||
Auswertung aus TRIPS/FILLS - eigenes Arbeitspaket.
|
|
||||||
- Kein Bild-Ordner vorhanden; Bilder fallen auf die im Prototyp bereits
|
- Kein Bild-Ordner vorhanden; Bilder fallen auf die im Prototyp bereits
|
||||||
vorgesehenen Platzhalter-Maße zurück (§7a), erscheinen aber als
|
vorgesehenen Platzhalter-Maße zurück (§7a), erscheinen aber als
|
||||||
gebrochene Bilder, bis bilder/ tatsächlich befüllt ist.
|
gebrochene Bilder, bis bilder/ tatsächlich befüllt ist.
|
||||||
|
|||||||
Reference in New Issue
Block a user