diff --git a/AGENTS.md b/AGENTS.md index b8f1639..f28c3c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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) +`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 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, diff --git a/ha_install.md b/ha_install.md new file mode 100644 index 0000000..f817c48 --- /dev/null +++ b/ha_install.md @@ -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.