Installationsskript: eine Zeile statt dreissig Minuten Handarbeit
homeassistant/install.sh richtet das Panel samt Backend auf einer Home-Assistant-Instanz ein und verdrahtet dabei die Selbstaktualisierung, so dass danach alles Weitere ueber die Weboberflaeche laeuft: Sensoren zuordnen, nach Updates suchen, einspielen. Gedacht fuer das Add-on "Terminal & SSH" direkt auf der Instanz: bash <(curl -fsSL <ROH-URL>/homeassistant/install.sh) Alternativ von einem Rechner aus mit --ziel gegen ein eingebundenes config-Verzeichnis, oder mit --von gegen eine lokale Arbeitskopie. Was es tut: pyscript installieren (neueste Version von GitHub, falls nicht vorhanden), pyscript/ und www/ einspielen, den Belegleser mitliefern (er liegt in data/, ist aber Code), fehlende Datenbestaende aus der Vorlage anlegen, den Abschnitt in die configuration.yaml eintragen und UPDATE_REPO_URL setzen. Drei Eigenschaften, auf die es dabei ankommt: - Mehrfach ausfuehrbar. Der Abschnitt in der configuration.yaml steht zwischen Markern und wird beim zweiten Lauf ersetzt statt angehaengt. Stehen pyscript oder panel_custom bereits ausserhalb dieses Abschnitts, weist das Skript darauf hin, statt einen doppelten Schluessel zu erzeugen. - Vorhandene Daten bleiben unangetastet. Fahrzeugprofil, Fahrten, Tankvorgaenge und ein bereits hinterlegtes Token werden nie ueberschrieben, nur fehlende Dateien angelegt. - Die configuration.yaml wird vor jeder Aenderung mit Zeitstempel gesichert. Geprueft gegen eine frische Home-Assistant-Instanz im Container: HA startet ohne Konfigurationsfehler, alle neun pyscript-Entitaeten werden veroeffentlicht, das Panel ist als "Mein Audi" mit mdi:car-sports unter /audi-dashboard-panel registriert und rendert im Browser mit fuenf Tabs. Ein zweiter Durchlauf laesst Profil und Token unveraendert und den Konfigurationsabschnitt genau einmal stehen. Ausserdem den blockierenden Verlaufsabruf in fahrtabschluss_logik.py uebernommen: urlopen lief direkt statt ueber task.executor, was Home Assistant seit 2026.8 abbricht - jede Fahrt waere ohne Strecke geblieben. Der Fix lag bisher nur im Branch umsetzung-datametric360; eine frische Installation haette den Fehler sonst mitgebracht. INSTALL.md nennt den schnellen Weg jetzt vor der ausfuehrlichen Anleitung.
This commit is contained in:
@@ -34,6 +34,45 @@ Zeitaufwand: ca. 30–40 Minuten, größtenteils Warten auf Neustarts.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Der schnelle Weg: install.sh
|
||||||
|
|
||||||
|
Für eine neue Instanz gibt es ein Skript, das die Schritte 1 bis 3 und 5
|
||||||
|
zusammen erledigt. Am einfachsten direkt auf der HA-Instanz im Add-on
|
||||||
|
**Terminal & SSH**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash <(curl -fsSL https://gitea.nothaft.cloud/paul/audi-app/raw/branch/main/homeassistant/install.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
Liegt das Repository privat (Standard), braucht der Aufruf ein Zugangstoken:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL -H "Authorization: token <DEIN-GITEA-TOKEN>" \
|
||||||
|
https://gitea.nothaft.cloud/paul/audi-app/raw/branch/main/homeassistant/install.sh -o install.sh
|
||||||
|
bash install.sh --repo https://<nutzer>:<token>@gitea.nothaft.cloud/paul/audi-app.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternativ von einem Rechner aus gegen ein eingebundenes config-Verzeichnis:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh --ziel /Volumes/config --von ~/Development/audi-app
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Skript installiert pyscript, spielt Backend und Oberfläche ein, legt das
|
||||||
|
Fahrzeugprofil aus der Vorlage an (**vorhandene Daten bleiben unangetastet**),
|
||||||
|
trägt den Abschnitt in die `configuration.yaml` ein — mit Sicherungskopie und
|
||||||
|
so, dass ein zweiter Lauf ihn ersetzt statt anhängt — und verdrahtet die
|
||||||
|
Selbstaktualisierung, damit „Update suchen" in der App funktioniert.
|
||||||
|
`--hilfe` zeigt alle Optionen.
|
||||||
|
|
||||||
|
Danach bleiben nur noch drei Dinge, alle in der Weboberfläche: neu starten,
|
||||||
|
Sensoren zuordnen (Schritt 4 unten), Fahrzeugdaten eintragen.
|
||||||
|
|
||||||
|
Wer lieber jeden Schritt selbst nachvollzieht, folgt der ausführlichen
|
||||||
|
Anleitung ab hier.
|
||||||
|
|
||||||
|
|
||||||
## Schritt 1 — pyscript installieren
|
## Schritt 1 — pyscript installieren
|
||||||
|
|
||||||
1. In Home Assistant: **HACS → Integrationen → Explore & Download Repositories**
|
1. In Home Assistant: **HACS → Integrationen → Explore & Download Repositories**
|
||||||
|
|||||||
Executable
+293
@@ -0,0 +1,293 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# DataMetric360 — Installation in Home Assistant
|
||||||
|
#
|
||||||
|
# Richtet das Panel samt Backend auf einer Home-Assistant-Instanz ein und
|
||||||
|
# verdrahtet die Selbstaktualisierung, damit danach alles Weitere über die
|
||||||
|
# Weboberfläche läuft: Sensoren zuordnen, nach Updates suchen, einspielen.
|
||||||
|
#
|
||||||
|
# Wo ausführen:
|
||||||
|
# Am einfachsten direkt auf der HA-Instanz, im Add-on "Terminal & SSH":
|
||||||
|
# bash <(curl -fsSL <ROH-URL dieser Datei>)
|
||||||
|
# Alternativ von einem Rechner aus gegen ein eingebundenes config-Verzeichnis:
|
||||||
|
# ./install.sh --ziel /Volumes/config
|
||||||
|
#
|
||||||
|
# Mehrfach ausführbar: bereits vorhandene Fahrzeugdaten werden nie
|
||||||
|
# überschrieben, und der Abschnitt in der configuration.yaml wird beim
|
||||||
|
# erneuten Lauf ersetzt statt ein zweites Mal angehängt.
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ------------------------------------------------------------ Voreinstellungen
|
||||||
|
|
||||||
|
REPO_STANDARD="https://gitea.nothaft.cloud/paul/audi-app.git"
|
||||||
|
BRANCH="main"
|
||||||
|
ZIEL="/config"
|
||||||
|
QUELLE=""
|
||||||
|
TOKEN=""
|
||||||
|
REPO="$REPO_STANDARD"
|
||||||
|
PYSCRIPT_INSTALLIEREN="ja"
|
||||||
|
INTERAKTIV="ja"
|
||||||
|
|
||||||
|
MARKE_START="# >>> DataMetric360 (von install.sh verwaltet) >>>"
|
||||||
|
MARKE_ENDE="# <<< DataMetric360 <<<"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------- Ausgabe
|
||||||
|
|
||||||
|
rot() { printf '\033[31m%s\033[0m\n' "$*"; }
|
||||||
|
gruen(){ printf '\033[32m%s\033[0m\n' "$*"; }
|
||||||
|
grau() { printf '\033[90m%s\033[0m\n' "$*"; }
|
||||||
|
schritt(){ printf '\n\033[1m==> %s\033[0m\n' "$*"; }
|
||||||
|
fehler(){ rot "FEHLER: $*"; exit 1; }
|
||||||
|
|
||||||
|
hilfe() {
|
||||||
|
cat <<'ENDE'
|
||||||
|
DataMetric360 — Installation in Home Assistant
|
||||||
|
|
||||||
|
--ziel <pfad> config-Verzeichnis von Home Assistant (Standard: /config)
|
||||||
|
--von <pfad> lokale Arbeitskopie benutzen statt zu klonen
|
||||||
|
--repo <url> Repository zum Klonen und für die Selbstaktualisierung
|
||||||
|
--branch <name> Branch (Standard: main)
|
||||||
|
--token <wert> langlebiges Zugriffstoken für die Kilometerstand-Historie
|
||||||
|
--ohne-pyscript pyscript nicht mitinstallieren (wenn schon vorhanden)
|
||||||
|
--still keine Rückfragen stellen (für unbeaufsichtigte Läufe)
|
||||||
|
--hilfe diese Übersicht
|
||||||
|
|
||||||
|
Beispiele
|
||||||
|
bash install.sh # auf der HA-Instanz, klont selbst
|
||||||
|
bash install.sh --von ~/audi-app # aus einer lokalen Arbeitskopie
|
||||||
|
bash install.sh --ziel /Volumes/config # gegen ein eingebundenes Laufwerk
|
||||||
|
ENDE
|
||||||
|
}
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--ziel) ZIEL="${2:-}"; shift 2 ;;
|
||||||
|
--von) QUELLE="${2:-}"; shift 2 ;;
|
||||||
|
--repo) REPO="${2:-}"; shift 2 ;;
|
||||||
|
--branch) BRANCH="${2:-}"; shift 2 ;;
|
||||||
|
--token) TOKEN="${2:-}"; shift 2 ;;
|
||||||
|
--ohne-pyscript) PYSCRIPT_INSTALLIEREN="nein"; shift ;;
|
||||||
|
--still) INTERAKTIV="nein"; shift ;;
|
||||||
|
--hilfe|-h) hilfe; exit 0 ;;
|
||||||
|
*) fehler "Unbekannte Option: $1 (--hilfe zeigt alle)" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# --------------------------------------------------------------- Vorprüfungen
|
||||||
|
|
||||||
|
schritt "Umgebung prüfen"
|
||||||
|
|
||||||
|
for werkzeug in python3 curl unzip; do
|
||||||
|
command -v "$werkzeug" >/dev/null 2>&1 || fehler "$werkzeug wird gebraucht, ist aber nicht vorhanden."
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -d "$ZIEL" ] || fehler "Das Zielverzeichnis $ZIEL gibt es nicht. Mit --ziel den richtigen Pfad angeben."
|
||||||
|
[ -w "$ZIEL" ] || fehler "Keine Schreibrechte auf $ZIEL."
|
||||||
|
|
||||||
|
if [ ! -f "$ZIEL/configuration.yaml" ]; then
|
||||||
|
rot "In $ZIEL liegt keine configuration.yaml."
|
||||||
|
fehler "Das sieht nicht nach einem Home-Assistant-Verzeichnis aus. Pfad mit --ziel prüfen."
|
||||||
|
fi
|
||||||
|
grau " Ziel: $ZIEL"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------ Quelle besorgen
|
||||||
|
|
||||||
|
AUFRAEUMEN=""
|
||||||
|
saeubern() { [ -n "$AUFRAEUMEN" ] && rm -rf "$AUFRAEUMEN" || true; }
|
||||||
|
trap saeubern EXIT
|
||||||
|
|
||||||
|
if [ -n "$QUELLE" ]; then
|
||||||
|
schritt "Lokale Arbeitskopie benutzen"
|
||||||
|
[ -d "$QUELLE/homeassistant" ] || fehler "In $QUELLE gibt es keinen Ordner homeassistant/."
|
||||||
|
HA_QUELLE="$QUELLE/homeassistant"
|
||||||
|
grau " $HA_QUELLE"
|
||||||
|
else
|
||||||
|
schritt "Projekt herunterladen"
|
||||||
|
command -v git >/dev/null 2>&1 || fehler "git wird zum Klonen gebraucht (oder --von <pfad> benutzen)."
|
||||||
|
AUFRAEUMEN="$(mktemp -d)"
|
||||||
|
if ! git clone --depth 1 --branch "$BRANCH" "$REPO" "$AUFRAEUMEN/quelle" >/dev/null 2>&1; then
|
||||||
|
rot "Klonen von $REPO fehlgeschlagen."
|
||||||
|
grau " Bei einem privaten Repository ein Zugangstoken in die URL aufnehmen, etwa:"
|
||||||
|
grau " --repo https://<nutzer>:<token>@gitea.example/paul/audi-app.git"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
HA_QUELLE="$AUFRAEUMEN/quelle/homeassistant"
|
||||||
|
grau " $REPO ($BRANCH)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -d "$HA_QUELLE/pyscript" ] || fehler "Im Projekt fehlt homeassistant/pyscript/."
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------- pyscript
|
||||||
|
|
||||||
|
if [ "$PYSCRIPT_INSTALLIEREN" = "ja" ]; then
|
||||||
|
if [ -d "$ZIEL/custom_components/pyscript" ]; then
|
||||||
|
schritt "pyscript ist bereits vorhanden"
|
||||||
|
grau " $ZIEL/custom_components/pyscript"
|
||||||
|
else
|
||||||
|
schritt "pyscript installieren"
|
||||||
|
VERSION="$(curl -fsSL https://api.github.com/repos/custom-components/pyscript/releases/latest \
|
||||||
|
| python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])' 2>/dev/null || true)"
|
||||||
|
[ -n "$VERSION" ] || fehler "Konnte die neueste pyscript-Version nicht ermitteln (kein Internet?)."
|
||||||
|
TMPPY="$(mktemp -d)"
|
||||||
|
curl -fsSL -o "$TMPPY/pyscript.zip" \
|
||||||
|
"https://github.com/custom-components/pyscript/archive/refs/tags/${VERSION}.zip"
|
||||||
|
unzip -oq "$TMPPY/pyscript.zip" -d "$TMPPY"
|
||||||
|
mkdir -p "$ZIEL/custom_components"
|
||||||
|
cp -r "$TMPPY/pyscript-${VERSION}/custom_components/pyscript" "$ZIEL/custom_components/"
|
||||||
|
rm -rf "$TMPPY"
|
||||||
|
gruen " pyscript $VERSION installiert"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --------------------------------------------------------- Dateien einspielen
|
||||||
|
|
||||||
|
schritt "Backend und Oberfläche einspielen"
|
||||||
|
|
||||||
|
mkdir -p "$ZIEL/pyscript" "$ZIEL/www" "$ZIEL/audi_dashboard"
|
||||||
|
|
||||||
|
# pyscript und www sind Code - vollständig ersetzen.
|
||||||
|
cp -r "$HA_QUELLE/pyscript/." "$ZIEL/pyscript/"
|
||||||
|
cp -r "$HA_QUELLE/www/." "$ZIEL/www/"
|
||||||
|
grau " pyscript/ und www/ aktualisiert"
|
||||||
|
|
||||||
|
# Der Belegleser liegt in data/, ist aber Code (belegverarbeitung.py ruft ihn
|
||||||
|
# als Unterprozess auf) - deshalb hier mit, anders als die Datenbestände.
|
||||||
|
cp "$HA_QUELLE/data/shell_beleg_parser.py" "$ZIEL/audi_dashboard/shell_beleg_parser.py"
|
||||||
|
|
||||||
|
# Datenbestände: nur anlegen, was fehlt. Vorhandenes nie überschreiben.
|
||||||
|
if [ ! -f "$ZIEL/audi_dashboard/fahrzeugprofil.json" ]; then
|
||||||
|
cp "$HA_QUELLE/data/fahrzeugprofil.example.json" "$ZIEL/audi_dashboard/fahrzeugprofil.json"
|
||||||
|
gruen " Fahrzeugprofil aus der Vorlage angelegt"
|
||||||
|
else
|
||||||
|
grau " Fahrzeugprofil vorhanden - unangetastet"
|
||||||
|
fi
|
||||||
|
for datei in fahrten.jsonl tankvorgaenge.jsonl; do
|
||||||
|
[ -f "$ZIEL/audi_dashboard/$datei" ] || : > "$ZIEL/audi_dashboard/$datei"
|
||||||
|
done
|
||||||
|
mkdir -p "$ZIEL/audi_dashboard/belege"
|
||||||
|
|
||||||
|
# --------------------------------------------------- Selbstaktualisierung
|
||||||
|
|
||||||
|
schritt "Selbstaktualisierung verdrahten"
|
||||||
|
|
||||||
|
# UPDATE_REPO_URL im ausgelieferten einstellungen.py setzen, damit
|
||||||
|
# "Update suchen" in den Einstellungen der App funktioniert.
|
||||||
|
python3 - "$ZIEL/pyscript/modules/einstellungen.py" "$REPO" "$BRANCH" <<'PYENDE'
|
||||||
|
import re, sys
|
||||||
|
pfad, repo, branch = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
|
with open(pfad, encoding="utf-8") as f:
|
||||||
|
inhalt = f.read()
|
||||||
|
inhalt = re.sub(r'^UPDATE_REPO_URL\s*=.*$', f'UPDATE_REPO_URL = "{repo}"', inhalt, count=1, flags=re.M)
|
||||||
|
inhalt = re.sub(r'^UPDATE_BRANCH\s*=.*$', f'UPDATE_BRANCH = "{branch}"', inhalt, count=1, flags=re.M)
|
||||||
|
with open(pfad, "w", encoding="utf-8") as f:
|
||||||
|
f.write(inhalt)
|
||||||
|
PYENDE
|
||||||
|
grau " Updatequelle: $REPO ($BRANCH)"
|
||||||
|
grau " Danach in der App unter Einstellungen → Version → \"Update suchen\""
|
||||||
|
|
||||||
|
# ---------------------------------------------------------- configuration.yaml
|
||||||
|
|
||||||
|
schritt "configuration.yaml ergänzen"
|
||||||
|
|
||||||
|
KONFIG="$ZIEL/configuration.yaml"
|
||||||
|
SICHERUNG="$KONFIG.vor-datametric360-$(date +%Y%m%d_%H%M%S)"
|
||||||
|
cp "$KONFIG" "$SICHERUNG"
|
||||||
|
grau " Sicherung: $(basename "$SICHERUNG")"
|
||||||
|
|
||||||
|
python3 - "$KONFIG" "$MARKE_START" "$MARKE_ENDE" <<'PYENDE'
|
||||||
|
import re, sys
|
||||||
|
pfad, start, ende = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
|
with open(pfad, encoding="utf-8") as f:
|
||||||
|
inhalt = f.read()
|
||||||
|
|
||||||
|
block = f"""{start}
|
||||||
|
# Von install.sh eingefügt. Aenderungen innerhalb dieses Abschnitts werden bei
|
||||||
|
# der naechsten Installation ersetzt - Eigenes bitte ausserhalb notieren.
|
||||||
|
pyscript:
|
||||||
|
allow_all_imports: true
|
||||||
|
|
||||||
|
panel_custom:
|
||||||
|
- name: audi-dashboard-panel
|
||||||
|
sidebar_title: Mein Audi
|
||||||
|
sidebar_icon: mdi:car-sports
|
||||||
|
module_url: /local/audi-dashboard-panel.js?v=1
|
||||||
|
embed_iframe: false
|
||||||
|
trust_external_script: false
|
||||||
|
{ende}"""
|
||||||
|
|
||||||
|
muster = re.compile(re.escape(start) + r".*?" + re.escape(ende), re.S)
|
||||||
|
if muster.search(inhalt):
|
||||||
|
inhalt = muster.sub(block, inhalt)
|
||||||
|
print(" Abschnitt ersetzt")
|
||||||
|
else:
|
||||||
|
for schluessel in ("pyscript:", "panel_custom:"):
|
||||||
|
if re.search(rf"^{schluessel}", inhalt, re.M):
|
||||||
|
print(f" ACHTUNG: '{schluessel}' steht bereits ausserhalb des verwalteten Abschnitts.")
|
||||||
|
print(" Der Abschnitt wird trotzdem angehaengt - Home Assistant meldet dann")
|
||||||
|
print(" einen doppelten Schluessel. Bitte den alten Eintrag von Hand entfernen.")
|
||||||
|
if not inhalt.endswith("\n"):
|
||||||
|
inhalt += "\n"
|
||||||
|
inhalt += "\n" + block + "\n"
|
||||||
|
print(" Abschnitt angehaengt")
|
||||||
|
|
||||||
|
with open(pfad, "w", encoding="utf-8") as f:
|
||||||
|
f.write(inhalt)
|
||||||
|
PYENDE
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------- Token
|
||||||
|
|
||||||
|
schritt "Zugriffstoken für die Kilometerstand-Historie"
|
||||||
|
|
||||||
|
TOKENDATEI="$ZIEL/audi_dashboard/ha_token.txt"
|
||||||
|
if [ -n "$TOKEN" ]; then
|
||||||
|
printf '%s' "$TOKEN" > "$TOKENDATEI"
|
||||||
|
chmod 600 "$TOKENDATEI" 2>/dev/null || true
|
||||||
|
gruen " Token gespeichert"
|
||||||
|
elif [ -s "$TOKENDATEI" ]; then
|
||||||
|
grau " Vorhandenes Token bleibt unangetastet"
|
||||||
|
elif [ "$INTERAKTIV" = "ja" ] && [ -t 0 ]; then
|
||||||
|
echo " Der Fahrtabschluss liest den Kilometerstand aus dem Verlauf und braucht dafür"
|
||||||
|
echo " ein langlebiges Zugriffstoken (Profil → Sicherheit → Langlebige Zugriffstoken)."
|
||||||
|
echo " Leer lassen und später nachtragen ist möglich - Fahrten bleiben dann ohne Strecke."
|
||||||
|
printf " Token (Eingabe bleibt verborgen): "
|
||||||
|
read -rs EINGABE || EINGABE=""
|
||||||
|
echo
|
||||||
|
if [ -n "$EINGABE" ]; then
|
||||||
|
printf '%s' "$EINGABE" > "$TOKENDATEI"
|
||||||
|
chmod 600 "$TOKENDATEI" 2>/dev/null || true
|
||||||
|
gruen " Token gespeichert"
|
||||||
|
else
|
||||||
|
rot " Übersprungen - später in $TOKENDATEI ablegen."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
rot " Kein Token angegeben. Später in $TOKENDATEI ablegen,"
|
||||||
|
rot " sonst bleiben Fahrten ohne Streckenangabe."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ Abschluss
|
||||||
|
|
||||||
|
schritt "Fertig"
|
||||||
|
cat <<ENDE
|
||||||
|
|
||||||
|
Noch zu tun, alles in der Weboberfläche:
|
||||||
|
|
||||||
|
1. Home Assistant neu starten
|
||||||
|
Einstellungen → System → oben rechts → Neu starten
|
||||||
|
|
||||||
|
2. Sensoren zuordnen
|
||||||
|
Mein Audi → Einstellungen → Fahrzeug einrichten → Einrichten
|
||||||
|
→ "Setup — Sensoren zuordnen"
|
||||||
|
Pflicht ist nur die Zündung; alles Weitere ist optional.
|
||||||
|
|
||||||
|
3. Fahrzeugdaten eintragen
|
||||||
|
Im selben Menü unter "Einrichten": Kennzeichen, FIN, Versicherung, …
|
||||||
|
|
||||||
|
Ab dann läuft alles über die Oberfläche - auch Updates:
|
||||||
|
Mein Audi → Einstellungen → Version → "Update suchen"
|
||||||
|
|
||||||
|
Sicherung der configuration.yaml:
|
||||||
|
$SICHERUNG
|
||||||
|
|
||||||
|
ENDE
|
||||||
|
gruen "Installation abgeschlossen."
|
||||||
@@ -51,10 +51,19 @@ def _token_lesen():
|
|||||||
|
|
||||||
|
|
||||||
def historie_abfragen(entity_id, start_iso, end_iso):
|
def historie_abfragen(entity_id, start_iso, end_iso):
|
||||||
"""urllib.request.urlopen läuft direkt, nicht über task.executor - anders
|
"""Fragt die Verlaufsdaten des Kilometerstand-Sensors über die REST-API ab.
|
||||||
als ein bloßes open() ist das ein modulqualifizierter Aufruf einer echten
|
|
||||||
externen Funktion, der an der Testinstanz ohne Fehler durchlief. Blockiert
|
Beide blockierenden Schritte laufen über task.executor. Ein früherer Stand
|
||||||
kurz, läuft nur beim Fahrtabschluss-Screening, nicht bei jedem Tick."""
|
rief urlopen() direkt auf - das lief jahrelang durch, bis Home Assistant
|
||||||
|
anfing, blockierende Aufrufe im Ereignisschleifen-Thread zu erkennen und
|
||||||
|
abzubrechen (real beobachtet ab 2026.8: "Caught blocking call to
|
||||||
|
putrequest ... inside the event loop"). Seitdem schlug jedes Screening
|
||||||
|
fehl, und manuell angelegte Fahrten blieben ohne Strecke - sichtbar nur
|
||||||
|
als Warnung im Protokoll, weil der Aufrufer den Fehler bewusst abfängt.
|
||||||
|
|
||||||
|
task.executor braucht echte externe Funktionen; urllib.request.urlopen und
|
||||||
|
die read-Methode der Antwort sind genau das (dieselbe Einschränkung wie bei
|
||||||
|
io.open in profil.py)."""
|
||||||
token = _token_lesen()
|
token = _token_lesen()
|
||||||
start_enc = urllib.parse.quote(start_iso)
|
start_enc = urllib.parse.quote(start_iso)
|
||||||
end_enc = urllib.parse.quote(end_iso)
|
end_enc = urllib.parse.quote(end_iso)
|
||||||
@@ -63,10 +72,10 @@ def historie_abfragen(entity_id, start_iso, end_iso):
|
|||||||
f"?filter_entity_id={entity_id}&end_time={end_enc}"
|
f"?filter_entity_id={entity_id}&end_time={end_enc}"
|
||||||
)
|
)
|
||||||
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
|
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
|
||||||
resp = urllib.request.urlopen(req, timeout=10)
|
resp = task.executor(urllib.request.urlopen, req, timeout=10)
|
||||||
rohtext = resp.read().decode("utf-8")
|
rohdaten = task.executor(resp.read)
|
||||||
resp.close()
|
resp.close()
|
||||||
daten = json.loads(rohtext)
|
daten = json.loads(rohdaten.decode("utf-8"))
|
||||||
return daten[0] if daten else []
|
return daten[0] if daten else []
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user