Files
audi-app/custom_components/audi_dashboard/zugaenge.py
T
tobias 4d4890034a Regler stellt den Schlaf-Timeout des Dongles (2026.9.3.10)
Der Regler "Fahrt beenden" ist jetzt eine Zahl fuer zwei Dinge: unsere eigene
Wartezeit bis zum Fahrtende und der Schlaf-Timeout des FMM003 (103). So lange
bleibt der Dongle wach und wartet darauf, dass es gleich weitergeht; danach
schlaeft er. Die 180 s Ignition OFF Delay laufen davor und bleiben unsichtbar -
sie werden nur von der Fahrtdauer abgezogen. Der Regler geht deshalb von 1 bis
60 in Einerschritten; das Geraet kennt kein Timeout 0.

flespi.py liest und schreibt die Geraete-Konfiguration. Drei Annahmen sind an
der echten API gefallen und stehen als Messung im Modulkopf: der Wert liegt in
current (nicht value), die Einstellung heisst sleep_mode mit dem Wert eine Ebene
tiefer unter mode, und ein PUT braucht {"properties": ..., "address": ...}.

Die eigene Auftrags-Warteschlange ist wieder entfallen. Sie war fuer "das Geraet
schlaeft" gebaut - das gilt aber nur fuer das Geraet, nicht fuer die
Schnittstelle: die vollstaendige Konfiguration kam herein, waehrend das Fahrzeug
seit dem Vortag stand. flespi puffert selbst (pending + address=connection), ein
zweites Auftragsbuch waere eine zweite Warteschlange fuer dieselbe Aufgabe.

Gegengelesen wird nach jedem Schreiben; als Erfolg zaehlt current ODER pending -
letzteres ist bei stehendem Fahrzeug der Normalfall.

Live gegen den echten Account belegt: Token sieht genau ein Geraet, 127
Einstellungen bei parkendem Fahrzeug gelesen, Regler 15 geschrieben und als
pending bestaetigt (Nachbarfelder unveraendert), zweiter Neustart ohne erneute
Anfrage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 20:00:39 +02:00

126 lines
4.8 KiB
Python

"""Die Dienste-Token dieser Installation - lesen, setzen, löschen.
WARUM DAS EINE EIGENE DATEI IST
-------------------------------
Es gibt für die Token längst einen Ort: `entry.options`, geschrieben vom
Options-Flow unter Einstellungen → Geräte & Dienste → Audi Dashboard →
Konfigurieren. Dieser Weg bleibt und ist der Rückfall, wenn die App den Server
gerade nicht erreicht - genau der Fall, in dem ein abgelaufener Token auffällt.
Er ist aber **nur über Home Assistants eigene Weboberfläche erreichbar**. Die
iOS-App zeigt keine HA-Konfigurationsdialoge; läuft dort unterwegs ein Token
ab, müsste man einen Browser öffnen. Diese Datei ist die Bedienfläche dafür -
kein zweiter Speicherort, sondern derselbe `entry.options`, nur von aussen
erreichbar.
DER WERT KOMMT NIE ZURÜCK
-------------------------
`stand()` gibt ausschliesslich "gesetzt/nicht gesetzt" und die letzten vier
Zeichen heraus. Ein Token, das einmal drin ist, verlässt die Integration nicht
wieder - weder über die veröffentlichte Entität noch über einen Dienst. Wer
ihn ersetzen will, trägt einen neuen ein; wer ihn loswerden will, löscht ihn.
Der letzte Rest reicht zum Wiedererkennen ("ist das der neue oder noch der
alte?") und ist für sich genommen nutzlos.
WARUM DIE TOKEN NICHT INS FAHRZEUGPROFIL DÜRFEN
-----------------------------------------------
`sicherung.py` sichert fünf Dateien, darunter `fahrzeugprofil.json`, und
"Backup exportieren" lädt Profil, Fahrten und Tankvorgänge als eine Datei auf
das Gerät des Nutzers. Läge ein Token im Profil, wanderte er in **jede**
Sicherung und in jede exportierte Datei. Er bleibt deshalb dort, wo Home
Assistant Geheimnisse ohnehin hält.
"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from .const import CONF_FLESPI_TOKEN, CONF_GITEA_TOKEN
if TYPE_CHECKING:
from .koordinator import Koordinator
_LOGGER = logging.getLogger(__name__)
# Die Token, die diese Installation kennt. Die Reihenfolge ist die der Anzeige.
#
# "erforderlich" sagt, ob die App ohne ihn nicht läuft. Beide sind es NICHT:
# ohne Gitea-Token entfällt nur die Update-Prüfung, ohne Flespi-Token nur das
# Auslesen der Geräte-Konfiguration. Ein Token, den die App zum Starten
# bräuchte, wäre hier ohnehin fehl am Platz - er müsste eingetragen sein, bevor
# jemand diese Oberfläche überhaupt öffnen kann.
DIENSTE: dict[str, dict] = {
"gitea": {
"schluessel": CONF_GITEA_TOKEN,
"titel": "Gitea",
"zweck": "Update-Prüfung und Selbst-Update der Integration.",
"erforderlich": False,
},
"flespi": {
"schluessel": CONF_FLESPI_TOKEN,
"titel": "flespi",
"zweck": (
"Liest die Konfiguration des Dongles und schreibt den Schlaf-Timeout. "
"Der Token braucht dafür Lese- und Schreibrecht auf die "
"Geräte-Konfiguration."
),
"erforderlich": False,
},
}
def _rest(token: str) -> str | None:
"""Die letzten vier Zeichen - zum Wiedererkennen, nicht zum Benutzen.
Bei einem sehr kurzen Token wäre das schon ein nennenswerter Teil davon;
dann lieber gar nichts zeigen.
"""
return token[-4:] if len(token) >= 12 else None
def stand(k: Koordinator) -> list[dict]:
"""Was die Oberflächen zu sehen bekommen - niemals der Token selbst."""
return [
{
"dienst": name,
"titel": eintrag["titel"],
"zweck": eintrag["zweck"],
"erforderlich": eintrag["erforderlich"],
"gesetzt": bool(k.entry.options.get(eintrag["schluessel"], "")),
"rest": _rest(k.entry.options.get(eintrag["schluessel"], "")),
}
for name, eintrag in DIENSTE.items()
]
async def setzen(k: Koordinator, dienst: str, token: str) -> None:
"""Trägt einen Token ein oder löscht ihn (leerer Text = löschen).
Geschrieben wird in denselben `entry.options`, die auch der Options-Flow
benutzt - es gibt bewusst keinen zweiten Wahrheitsort. `async_update_entry`
lädt den Eintrag NICHT neu; das ist hier richtig, weil beide Leser
(`aktualisierung.py` und der künftige Flespi-Weg) die Option bei jedem
Aufruf frisch aus `entry.options` holen.
"""
eintrag = DIENSTE.get(dienst)
if eintrag is None:
_LOGGER.warning("Unbekannter Dienst %s - nichts geändert", dienst)
return
token = token.strip()
optionen = dict(k.entry.options)
if token:
optionen[eintrag["schluessel"]] = token
else:
optionen.pop(eintrag["schluessel"], None)
k.hass.config_entries.async_update_entry(k.entry, options=optionen)
# Ausdrücklich ohne den Wert und ohne seinen Rest: ein Protokoll wandert in
# Fehlerberichte und Sicherungen.
_LOGGER.info(
"Token für %s %s", eintrag["titel"], "eingetragen" if token else "gelöscht"
)
await k.app_version_veroeffentlichen()