4d4890034a
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>
94 lines
4.0 KiB
Python
94 lines
4.0 KiB
Python
"""Einrichtung über die Oberfläche (Einstellungen -> Geräte & Dienste ->
|
|
Integration hinzufügen -> Audi Dashboard).
|
|
|
|
Bewusst ohne Eingabefelder im Ersteinrichtungs-Schritt. Es gäbe genau eine
|
|
Sache zu fragen - welche Entität welche Rolle im Fahrzeug spielt -, und die
|
|
gehört nicht hierher: die Zuordnung wird im Setup-Menü der App selbst
|
|
vorgenommen, wo neben jedem Feld steht, wofür es gebraucht wird und was
|
|
passiert, wenn es leer bleibt. Sie hier abzufragen hieße, dieselbe Auswahl an
|
|
zwei Stellen zu pflegen - und die schlechtere von beiden zuerst zu zeigen.
|
|
|
|
Eine einzige Instanz, erzwungen über single_config_entry in manifest.json:
|
|
die App gehört zu genau einem Fahrzeug, und alle Bestände liegen unter einem
|
|
festen Pfad.
|
|
|
|
Der OptionsFlow unten ist die einzige Ausnahme: ein Gitea-Zugriffstoken für
|
|
das Selbst-Update (siehe aktualisierung.py) - ein echtes Geheimnis, das
|
|
deshalb in HAs eigenem, verschlüsseltem Config-Entry-Speicher landet
|
|
(entry.options), nicht in configuration.yaml.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult, OptionsFlow
|
|
from homeassistant.core import callback
|
|
from homeassistant.helpers import selector
|
|
|
|
from .const import CONF_FLESPI_GERAET, CONF_FLESPI_TOKEN, CONF_GITEA_TOKEN, DOMAIN
|
|
|
|
|
|
class AudiDashboardConfigFlow(ConfigFlow, domain=DOMAIN):
|
|
"""Ein Bestätigungsschritt, mehr braucht es nicht."""
|
|
|
|
VERSION = 1
|
|
|
|
async def async_step_user(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
if self._async_current_entries():
|
|
return self.async_abort(reason="single_instance_allowed")
|
|
if user_input is None:
|
|
return self.async_show_form(step_id="user")
|
|
return self.async_create_entry(title="Audi Dashboard", data={})
|
|
|
|
@staticmethod
|
|
@callback
|
|
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
|
|
return AudiDashboardOptionsFlow()
|
|
|
|
|
|
class AudiDashboardOptionsFlow(OptionsFlow):
|
|
"""Einstellungen -> Geräte & Dienste -> Audi Dashboard -> Konfigurieren.
|
|
|
|
Zwei Felder, beide freiwillig: der Gitea-Token fürs Selbst-Update und der
|
|
flespi-Token fürs Auslesen der Geräte-Konfiguration. Leer lassen schaltet
|
|
die jeweilige Funktion ab (aktualisierung.py verlangt einen nicht-leeren
|
|
Token, sonst gibt es eine verständliche Fehlermeldung statt eines
|
|
unbehandelten Fehlers).
|
|
|
|
Dieser Weg bleibt der Rückfall neben der Kachel „Zugänge" in App und Panel
|
|
(siehe zugaenge.py): er ist der einzige, der auch dann trägt, wenn die App
|
|
den Server gerade nicht erreicht - genau der Fall, in dem ein abgelaufener
|
|
Token auffällt. Beide schreiben in dieselben entry.options, es gibt keinen
|
|
zweiten Wahrheitsort."""
|
|
|
|
async def async_step_init(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
if user_input is not None:
|
|
return self.async_create_entry(title="", data=user_input)
|
|
|
|
return self.async_show_form(
|
|
step_id="init",
|
|
data_schema=self.add_suggested_values_to_schema(
|
|
vol.Schema({
|
|
vol.Optional(CONF_GITEA_TOKEN, default=""): selector.TextSelector(
|
|
selector.TextSelectorConfig(type=selector.TextSelectorType.PASSWORD)
|
|
),
|
|
vol.Optional(CONF_FLESPI_TOKEN, default=""): selector.TextSelector(
|
|
selector.TextSelectorConfig(type=selector.TextSelectorType.PASSWORD)
|
|
),
|
|
# Leer lassen, solange der Token genau ein Gerät sieht - dann
|
|
# ermittelt flespi.geraet_finden() die Nummer selbst. Sieht er
|
|
# mehrere, wird bewusst nicht geraten und die Nummer gehört
|
|
# hierher.
|
|
vol.Optional(CONF_FLESPI_GERAET, default=""): selector.TextSelector(),
|
|
}),
|
|
self.config_entry.options,
|
|
),
|
|
)
|