Plan fuer die native HA-Integration als Dokument ablegen

Umstellung von pyscript auf eine eigene Integration mit
Konfigurationsdialog: Einrichtung komplett ueber die Oberflaeche, kein YAML,
Updates als Knopfdruck. Alle genannten Schnittstellen sind an der laufenden
2026.8.1-Instanz geprueft, nicht aus der Dokumentation uebernommen -
inklusive Signaturen.

Zur Verteilung festgehalten: HACS kann ausschliesslich GitHub und scheidet
fuer das Gitea-Repository aus; App-Repositories akzeptieren zwar beliebige
Git-URLs, Apps sind aber Docker-Container und damit der falsche Behaelter
fuer eine Integration. Nativ bleibt die eigene UpdateEntity, deren Logik in
updateverwaltung.py bereits zu grossen Teilen existiert.

Dazu festgehalten, welche Befunde aus dem main-Review die Umstellung
konstruktionsbedingt aufloest - vor allem die drei Setup-Menue-Fehler und
die Token-Datei im Klartext.
This commit is contained in:
Paul Nothaft
2026-08-13 13:40:31 +02:00
parent 509de6ef97
commit b1f8b71104
2 changed files with 252 additions and 0 deletions
+5
View File
@@ -219,6 +219,11 @@ vendored MIT library served from Home Assistant itself, which satisfies the actu
## Pending: this branch has diverged from `main` (noted 2026-08-13) ## Pending: this branch has diverged from `main` (noted 2026-08-13)
`ha_install.md` (root) plans the move from pyscript to a **native HA integration** with a config
flow — UI-only setup, no YAML, and updates via a self-reporting `UpdateEntity` against the Gitea
repo (HACS is GitHub-only, so it is not an option). All APIs in it were verified against the
running 2026.8.1 instance. It also records which review findings that move eliminates by design.
A full review of `main`'s 18 new commits is in `REVIEW_main_2026-08-13.md` — 15 findings, the A full review of `main`'s 18 new commits is in `REVIEW_main_2026-08-13.md` — 15 findings, the
three most serious in the new setup menu (saving with an unloaded catalogue wipes the whole three most serious in the new setup menu (saving with an unloaded catalogue wipes the whole
mapping; "reset" has no effect on 15 of 17 fields; all four list positions get the same sensor, mapping; "reset" has no effect on 15 of 17 fields; all four list positions get the same sensor,
+247
View File
@@ -0,0 +1,247 @@
# DataMetric360 als native Home-Assistant-Integration
**Stand: 2026-08-13.** Vorhaben: Das HA-Backend von „pyscript-Skripte plus Dateien kopieren" auf
eine **eigene Integration mit Konfigurationsdialog** umstellen — Einrichtung vollständig über die
Oberfläche, kein YAML, Updates als Knopfdruck in HAs eigener Update-Liste.
Alle unten genannten Schnittstellen sind **an der laufenden Instanz geprüft** (Home Assistant
2026.8.1 im Container `audi_ha_test`), nicht aus der Dokumentation abgeschrieben. Die geprüften
Signaturen stehen jeweils dabei.
Verwandte Dokumente: `homeassistant/INSTALL.md` (heutiger, manueller Weg — bleibt gültig, bis diese
Umstellung steht), `UMSETZUNGSPLAN.md`, `REVIEW_main_2026-08-13.md` (die Fehler, die hier
konstruktionsbedingt verschwinden).
---
## 1. Was heute nötig ist — und was davon bleibt
| Heute | Nach der Umstellung |
|---|---|
| HACS installieren, darüber pyscript | entfällt |
| drei Ordner nach `/config/` kopieren (`pyscript/`, `data/`, `www/`) | einmalig **ein** Ordner nach `custom_components/` |
| zwei Blöcke in `configuration.yaml` einfügen | entfällt vollständig |
| Entity-IDs zuordnen | bleibt, aber als HA-Konfigurationsdialog statt als selbstgebautes Menü |
| langlebiges Token in `audi_dashboard/ha_token.txt` ablegen | **entfällt ersatzlos** |
| `pip install pypdf` von Hand | entfällt (`manifest.json``requirements`) |
| Neustart | einmalig, wie bei jeder Integration |
| Updates über `update.ps1` per Samba | Knopfdruck in *Einstellungen → System → Updates* |
## 2. Die Bausteine, die HA dafür mitbringt
Alle in 2026.8.1 vorhanden und geprüft:
| Baustein | Ersetzt |
|---|---|
| `EntitySelector` mit `domain`, `device_class`, `multiple`, `reorder`, `exclude_entities` | das gesamte Setup-Menü (`entitaeten.py` + ~800 Zeilen JS) |
| `panel_custom.async_register_panel(...)` | den `panel_custom`-Block in `configuration.yaml` |
| `hass.http.async_register_static_paths([StaticPathConfig(url_path, path, cache_headers)])` | Kopieren nach `/config/www/` samt Cache-Busting-Stub |
| `ConfigEntry.add_update_listener(...)` | den „wirkt erst nach Neustart"-Hinweis bei den drei triggergebundenen Feldern |
| `async_track_state_change_event` / `async_track_time_interval` / `async_track_time_change` | `@state_trigger` / `@time_trigger("period")` / `@time_trigger("cron")` |
| `recorder.history.get_significant_states(...)` | den HTTP-Aufruf auf `/api/history/period` **samt Token-Datei** |
| `hass.async_add_executor_job(...)` | `task.executor(io.open, ...)` |
| `Store` (`homeassistant.helpers.storage`) | handgeschriebenes JSON-I/O (optional, siehe §6) |
| `HomeAssistantView` / `websocket_api.async_register_command` | die 16-KB-Attributgrenze für Fahrten und Tankvorgänge |
| `UpdateEntity` (`INSTALL`, `PROGRESS`, `BACKUP`, `RELEASE_NOTES`) | `updateverwaltung.py` als sichtbare Update-Entität |
Der wichtigste Einzelfund: **`EntitySelector` kann `exclude_entities`.** Die Duplikat-Prüfung wäre
damit keine wegklickbare Warnung mehr, sondern schlicht nicht anwählbar.
## 3. Aufbau
```
custom_components/datametric360/
├── manifest.json Abhängigkeiten, "config_flow": true, Version
├── const.py Rollen-Tabelle (heute FELDER in entitaeten.py)
├── config_flow.py Ersteinrichtung + Optionen (die 17 Sensor-Rollen)
├── __init__.py Dienste, Horcher, Zeitgeber, Panel, statische Dateien
├── update.py UpdateEntity gegen das eigene Gitea
├── api.py HomeAssistantView für die Companion-App (später)
└── www/ audi-dashboard-app.js, .css, badges/ — mitgeliefert
```
### Konfigurationsdialog
Statt einer eigenen Katalogverwaltung reicht ein Schema. Skizze für zwei der 17 Rollen:
```python
vol.Schema({
vol.Required("zuendung_sensor"): EntitySelector(
EntitySelectorConfig(domain=["binary_sensor"])
),
vol.Optional("tuer_sensoren", default=[]): EntitySelector(
EntitySelectorConfig(domain=["binary_sensor"], device_class=["door"],
multiple=True, reorder=True)
),
})
```
Suche, Übersetzung, Gerätezuordnung und Mehrfachauswahl kommen von HA. Die vier Türpositionen
werden zu einer sortierbaren Mehrfachauswahl — die Reihenfolge ersetzt die heutigen festen
Positionsfelder.
### Panel und Dateien
```python
await hass.http.async_register_static_paths([
StaticPathConfig("/datametric360", hass.config.path("custom_components/datametric360/www"),
cache_headers=False),
])
panel_custom.async_register_panel(
hass, frontend_url_path="datametric360", webcomponent_name="audi-dashboard-panel",
sidebar_title="Mein Audi", sidebar_icon="mdi:car-sports",
module_url=f"/datametric360/audi-dashboard-app.js?v={VERSION}",
)
```
Die Versionsnummer in der URL macht den Lade-Stub `audi-dashboard-panel.js` und
`audi-dashboard-version.json` überflüssig — das Cache-Problem löst sich, weil die Integration ihre
eigene Version kennt.
**Das Frontend selbst bleibt unangetastet.** Alle 3.873 Zeilen `audi-dashboard-app.js` laufen
weiter; nur das Setup-Menü darin wird überflüssig.
## 4. Übersetzung der pyscript-Eigenheiten
Mechanisch, kein Neuentwurf. Die Fachlogik — Fahrterkennung, Tankerkennung, Beleg-Parser,
Reifenzähler, Prognose — geht nahezu unverändert mit.
| pyscript | Integration |
|---|---|
| `@service def audi_dashboard_x()` | `hass.services.async_register(DOMAIN, "x", handler, schema)` |
| `@state_trigger(f"{SENSOR}")` | `async_track_state_change_event(hass, [sensor], handler)` |
| `@time_trigger("period(now, 20 seconds)")` | `async_track_time_interval(hass, handler, timedelta(seconds=20))` |
| `@time_trigger("cron(0 4 * * *)")` | `async_track_time_change(hass, handler, hour=4, minute=0, second=0)` |
| `task.unique()` + `task.sleep()` | `asyncio.Task` merken und bei Bedarf `cancel()` |
| `task.executor(io.open, …)` | `hass.async_add_executor_job(...)` |
| `state.set("pyscript.x", …)` | eigene Entität oder `HomeAssistantView` (siehe §6) |
| `subprocess.run(shell_beleg_parser.py)` | `pypdf` direkt importieren, in einem Executor-Job |
| `urllib.request` auf `/api/history/period` | `recorder.history.get_significant_states(...)` |
Die letzte Zeile ist die wichtigste: Sie beseitigt **zwei** Befunde auf einmal — den blockierenden
Aufruf, den HA seit 2026.8 abbricht (siehe `REVIEW_main_2026-08-13.md`, und derselbe Fehler wurde
im Branch `umsetzung-datametric360` bereits behoben), **und** die Token-Datei im Klartext.
## 5. Installation und Updates
Hier liegt der eigentliche Knackpunkt, deshalb ausführlich.
### HACS scheidet aus
HACS spricht ausschließlich mit GitHub. Anfragen, auch GitLab, Gitea oder Codeberg zuzulassen, gibt
es seit Jahren; umgesetzt sind sie nicht. Das Repository liegt auf `gitea.nothaft.cloud` — HACS ist
damit kein Weg, außer man spiegelt nach GitHub.
### „App" ist der falsche Behälter
App-Repositories (früher Add-ons) akzeptieren **beliebige Git-URLs**, auch selbst gehostete; nötig
ist nur eine `repository.yaml` im Wurzelverzeichnis. Das ist die einzige Stelle, an der Gitea
nativ mitspielen würde.
Inhaltlich passt es trotzdem nicht: Apps sind **Docker-Container** neben Home Assistant. Eine
Integration muss *im* HA-Prozess laufen, um Entitäten anzulegen, Dienste zu registrieren und ein
Panel einzuhängen. Eine App kann das nicht.
*(Es gäbe den Trick, eine winzige App zu bauen, die nichts weiter tut, als die Integration nach
`custom_components/` zu schreiben — dann käme auch die Erstinstallation aus dem eigenen Gitea. Ein
Docker-Container für einen einmaligen Kopiervorgang ist das aus meiner Sicht nicht wert; der
Vollständigkeit halber notiert.)*
### Der native Weg: eigene UpdateEntity
`UpdateEntity` lässt die Integration ihre Updates selbst melden — sichtbar in **Einstellungen →
System → Updates**, derselben Liste wie HA-Kern und Apps. Geprüfte Möglichkeiten:
```
UpdateEntityFeature: INSTALL, SPECIFIC_VERSION, PROGRESS, BACKUP, RELEASE_NOTES
async_install(version: str | None, backup: bool, **kwargs) -> None
```
`BACKUP` gibt es genau für das, was `updateverwaltung.py` heute von Hand macht: vor dem Einspielen
den alten Code sichern.
**Die Logik ist bereits vorhanden.** `updateverwaltung.py` klont flach, vergleicht Versionen,
sichert nach `code_backups/` und ersetzt die Dateien. Das wandert nahezu unverändert in
`async_install()`. Neu sind nur zwei Eigenschaften:
```python
@property
def installed_version(self) -> str: return VERSION # aus manifest.json
@property
def latest_version(self) -> str | None: return self._neueste # aus Gitea
```
Für `latest_version` empfiehlt sich statt der heutigen Zeitstempel-Zahl aus
`audi-dashboard-version.json` ein **Git-Tag**. Gitea bietet eine GitHub-kompatible Schnittstelle:
```
GET https://gitea.nothaft.cloud/api/v1/repos/paul/audi-app/releases/latest
```
Ein einzelner HTTP-Aufruf, kein Klonen. In HA steht dann „1.4.0 → 1.5.0" statt einer nichtssagenden
Zahl, und die Release-Beschreibung lässt sich über `release_notes` direkt anzeigen.
### Was bleibt
**Die Erstinstallation bleibt ein einmaliges Kopieren** des Ordners nach `custom_components/`. Für
Integrationen aus Nicht-GitHub-Quellen gibt es keinen nativen Installationsweg. Jedes weitere
Update ist danach ein Knopfdruck.
## 6. Datenhaltung — bewusst zu entscheiden
Zwei Möglichkeiten, beide vertretbar:
- **`Store`** (`.storage/datametric360`): atomar, versioniert, mit Migrationspfad. Nachteil: für
Menschen nicht mehr lesbar, und die heutige Sicherung über `backup.py` müsste neu gedacht werden.
- **JSON-Dateien behalten** unter `/config/audi_dashboard/`, gelesen über Executor-Jobs. Nachteil:
kein Migrationspfad. Vorteil: Fahrten und Tankvorgänge bleiben von Hand einsehbar und mit den
bestehenden Sicherungen kompatibel.
**Empfehlung:** Dateien behalten. Bei einem Fahrzeugprofil, einem Fahrtenbuch und einer
Tankhistorie ist Lesbarkeit mehr wert als Migrationskomfort — und die vorhandene Sicherungslogik
bleibt gültig.
Unabhängig davon: Fahrten und Tankvorgänge gehören **nicht** in Entitäts-Attribute (16-KB-Grenze,
siehe `frontend_veroeffentlichung.py`), sondern hinter einen `HomeAssistantView`. Das ist zugleich
die saubere Schnittstelle für die Companion-App, die heute Attribut-Blobs auslesen muss.
## 7. Was dabei konstruktionsbedingt verschwindet
Aus `REVIEW_main_2026-08-13.md`:
- **Befund 1** (Speichern mit leerem Katalog löscht alle Zuordnungen) — der Konfigurationsdialog
validiert serverseitig, ein leeres Formular kommt gar nicht durch.
- **Befunde 2 und 5** (Zurücksetzen wirkungslos bzw. zu stark) — die `setattr`-Override-Ebene
entfällt komplett; HA verwaltet Optionen selbst.
- **Befunde 3 und 4** (falsche Vorbelegung, beliebige Sensoren) — HAs Entity-Picker schlägt nichts
vor, der Nutzer wählt; `exclude_entities` verhindert Doppelbelegung.
- **Befund 7** (kaputte `entitaeten.json` legt das Panel lahm) — die Datei existiert nicht mehr.
- Der Neustart-Zwang bei den drei triggergebundenen Feldern — `add_update_listener` lädt die
Integration neu und registriert die Horcher dabei mit den neuen Entity-IDs.
Nicht gelöst werden dadurch: die Frontend-Befunde (Standort-Dauerfeuer, Barrierefreiheit,
`.navmarke`) — das Frontend bleibt ja unverändert.
## 8. Aufwand und Reihenfolge
Realistisch einige Tage, nicht Wochen — der Großteil ist Umschreiben von Dekoratoren.
1. **Zuerst die drei Setup-Fehler roh fixen** (etwa eine Stunde). Sonst läuft die Instanz bis zur
Umstellung mit einem Datenverlust-Pfad.
2. **Gerüst aufsetzen:** `manifest.json`, `config_flow.py`, Panel- und Dateiregistrierung. Die
pyscript-Skripte laufen dabei zunächst unverändert weiter.
3. **Logik Datei für Datei herüberziehen**, beginnend mit `fahrtabschluss_logik.py` — dort
verschwinden Token und blockierender Aufruf zuerst.
4. **`update.py`** gegen das Gitea-Repository.
5. **`api.py`** für die Companion-App, sobald diese so weit ist.
### Was dabei zu beachten ist
- **Neuladen im laufenden Betrieb entfällt.** pyscript lädt bei jeder Dateiänderung neu; eine
Integration braucht einen Neustart oder ein Neuladen. Beim Entwickeln spürbar, im Betrieb egal.
- **Die Companion-App liest heute `pyscript.audi_dashboard_*`.** Werden daraus eigene Entitäten
oder ein eigener Endpunkt, ändert sich die Tabelle `ENTITAETEN` in
`companion-app/src/api/types.ts` — eine Datei, überschaubar.
- **`shell_beleg_parser.py` wandert mit in die Integration.** Heute liegt er unter
`/config/audi_dashboard/` und wird von keinem Update-Pfad ausgeliefert (siehe Review) — als Teil
des Integrationsordners löst sich das mit.