pyscript-Backend zur echten HA-Integration umgebaut (HACS-fähig)
Das Backend liegt jetzt als custom_components/audi_dashboard/ vor - eine normale Home-Assistant-Integration mit Config-Flow, einer sensor-Plattform und 18 Diensten. Damit ist die App über HACS installierbar; bis das Repo auf GitHub gespiegelt ist (HACS spricht ausschließlich mit GitHub), installiert homeassistant/installationspaket/install.ps1 denselben Ordner ohne HACS. Fünf Installationsschritte entfallen ersatzlos: der pyscript:-Block, der panel_custom:-Block, das Kopieren der Oberfläche nach www/, das langlebige Zugriffstoken (der Verlauf wird direkt über die recorder-API gelesen) und "pip install pypdf" (steht in manifest.json). Das Fahrzeugprofil legt die Integration beim ersten Start aus ihrer Vorlage an. Drei alte Schwächen sind dabei mit erledigt: - Die Nutzlast landet nicht mehr in der Recorder-Datenbank (_unrecorded_attributes - das kann nur eine echte Entität). - Eine laufende Fahrt überlebt einen Neustart (Store statt Arbeitsspeicher); fiel sie während eines Ausfalls ins Ende, schließt nach_neustart_fortsetzen() sie beim letzten aufgezeichneten Zeitpunkt. - Sensor-Zuordnungen wirken sofort - die Zustandsbeobachter werden neu gebunden, der Neustart-Hinweis und der Neustart-Dienst sind weg. Namensvertrag geändert, beide Oberflächen mitgezogen: pyscript.audi_dashboard_x -> sensor.audi_dashboard_x, pyscript.audi_dashboard_y -> audi_dashboard.y. Eine Companion-App vom alten Stand findet nach dem Umstieg nichts mehr und muss neu gebaut werden; das Panel liegt in der Integration und kann nicht driften. Der selbstgebaute Updater entfällt - HACS ist die Update-Mechanik, die Home Assistant kennt. Die Versionierung schrumpft auf eine Quelle: manifest.json. Geprüft am laufenden Testcontainer (Container byteweise identisch mit dem Repo): alle 18 Dienste, Panel, Config-Entry neu laden, Historienimport, echter Shell-Beleg in-process, Neuinstallation im Wegwerf-Container blank mit automatisch nachinstalliertem pypdf. Companion-App: tsc sauber, 112/112 Tests, beide Rauchtests gegen das laufende Backend grün. Belegparser 8/8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ export * from "./live.ts";
|
||||
export * from "./warteschlange.ts";
|
||||
export * from "./ablageNativ.ts";
|
||||
|
||||
import { ENTITAETEN } from "./types.ts";
|
||||
import { DIENST_DOMAIN, ENTITAETEN } from "./types.ts";
|
||||
import type { Fahrt, Fahrzeugstatus, ImportErgebnis, Profil, Tankvorgang } from "./types.ts";
|
||||
import { HassRest } from "./rest.ts";
|
||||
import { HassLive } from "./live.ts";
|
||||
@@ -34,7 +34,7 @@ export interface TankvorgangFelder {
|
||||
}
|
||||
|
||||
/** Eingabefelder für Fahrten - Namen wie beim Backend (fahrterkennung.py:
|
||||
audi_dashboard_fahrt_manuell_anlegen/audi_dashboard_fahrt_aktualisieren).
|
||||
fahrt_manuell_anlegen/fahrt_aktualisieren).
|
||||
Alles außer den Zeitpunkten ist optional: leer bleibt es liegen, bis das
|
||||
Kilometerstand-Screening (§7.2) oder eine spätere Bearbeitung es füllt. */
|
||||
export interface FahrtFelder {
|
||||
@@ -92,8 +92,8 @@ export class DataMetricApi {
|
||||
|
||||
profilSchreiben(profil: Profil): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
"pyscript",
|
||||
"audi_dashboard_profil_schreiben",
|
||||
DIENST_DOMAIN,
|
||||
"profil_schreiben",
|
||||
{ profil_json: JSON.stringify(profil) },
|
||||
"Fahrzeugdaten speichern",
|
||||
);
|
||||
@@ -101,8 +101,8 @@ export class DataMetricApi {
|
||||
|
||||
belegHochladen(pdfBase64: string, dateiname: string, tankId?: string): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
"pyscript",
|
||||
"audi_dashboard_beleg_hochladen",
|
||||
DIENST_DOMAIN,
|
||||
"beleg_hochladen",
|
||||
{ pdf_base64: pdfBase64, dateiname, ...(tankId ? { tank_id: tankId } : {}) },
|
||||
`Beleg „${dateiname}" hochladen`,
|
||||
);
|
||||
@@ -111,8 +111,8 @@ export class DataMetricApi {
|
||||
/** Legt eine Fahrt von Hand an — für Fahrten ohne automatische Erkennung. */
|
||||
fahrtAnlegen(felder: FahrtFelder): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
"pyscript",
|
||||
"audi_dashboard_fahrt_manuell_anlegen",
|
||||
DIENST_DOMAIN,
|
||||
"fahrt_manuell_anlegen",
|
||||
{ ...felder },
|
||||
"Fahrt eintragen",
|
||||
);
|
||||
@@ -122,8 +122,8 @@ export class DataMetricApi {
|
||||
Hand angelegt. */
|
||||
fahrtAktualisieren(tripId: string, felder: FahrtFelder): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
"pyscript",
|
||||
"audi_dashboard_fahrt_aktualisieren",
|
||||
DIENST_DOMAIN,
|
||||
"fahrt_aktualisieren",
|
||||
{ trip_id: tripId, ...felder },
|
||||
"Fahrt ändern",
|
||||
);
|
||||
@@ -131,8 +131,8 @@ export class DataMetricApi {
|
||||
|
||||
fahrtLoeschen(tripId: string): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
"pyscript",
|
||||
"audi_dashboard_fahrt_loeschen",
|
||||
DIENST_DOMAIN,
|
||||
"fahrt_loeschen",
|
||||
{ trip_id: tripId },
|
||||
"Fahrt löschen",
|
||||
);
|
||||
@@ -142,8 +142,8 @@ export class DataMetricApi {
|
||||
Parameternamen — belegverarbeitung.py). */
|
||||
tankvorgangAnlegen(felder: TankvorgangFelder): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
"pyscript",
|
||||
"audi_dashboard_tankvorgang_manuell",
|
||||
DIENST_DOMAIN,
|
||||
"tankvorgang_manuell",
|
||||
{ ...felder },
|
||||
"Tankvorgang eintragen",
|
||||
);
|
||||
@@ -151,8 +151,8 @@ export class DataMetricApi {
|
||||
|
||||
tankvorgangAktualisieren(tankId: string, felder: TankvorgangFelder): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
"pyscript",
|
||||
"audi_dashboard_tankvorgang_aktualisieren",
|
||||
DIENST_DOMAIN,
|
||||
"tankvorgang_aktualisieren",
|
||||
{ tank_id: tankId, ...felder },
|
||||
"Tankvorgang ändern",
|
||||
);
|
||||
@@ -160,8 +160,8 @@ export class DataMetricApi {
|
||||
|
||||
tankvorgangLoeschen(tankId: string): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
"pyscript",
|
||||
"audi_dashboard_tankvorgang_loeschen",
|
||||
DIENST_DOMAIN,
|
||||
"tankvorgang_loeschen",
|
||||
{ tank_id: tankId },
|
||||
"Tankvorgang löschen",
|
||||
);
|
||||
@@ -169,10 +169,10 @@ export class DataMetricApi {
|
||||
|
||||
/** Stößt eine sofortige Aktualisierung im Backend an (Pull-to-refresh). */
|
||||
jetztAktualisieren(): Promise<unknown> {
|
||||
return this.rest.dienstAufrufen("pyscript", "audi_dashboard_jetzt_aktualisieren");
|
||||
return this.rest.dienstAufrufen(DIENST_DOMAIN, "jetzt_aktualisieren");
|
||||
}
|
||||
|
||||
/** Welchen Oberflächen-Stand das Backend ausliefert (Datei VERSION, siehe
|
||||
/** Welchen Oberflächen-Stand das Backend ausliefert (Version der Integration, siehe
|
||||
VERSIONIERUNG.md). `null`, wenn die Entität fehlt — etwa weil das Backend
|
||||
älter ist als diese Funktion; dann wird nichts verglichen und nichts
|
||||
gemeldet, statt einen Fehlalarm auszulösen. */
|
||||
@@ -198,7 +198,7 @@ export class DataMetricApi {
|
||||
/** Startet den Import. Kehrt zurück, sobald das Backend den Auftrag
|
||||
angenommen hat — nicht, wenn er fertig ist; dafür importStatusLesen(). */
|
||||
historieImportieren(start: string, ende: string): Promise<unknown> {
|
||||
return this.rest.dienstAufrufen("pyscript", "audi_dashboard_historie_importieren", {
|
||||
return this.rest.dienstAufrufen(DIENST_DOMAIN, "historie_importieren", {
|
||||
start,
|
||||
ende,
|
||||
});
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
hass-Objekt geleistet hat (SPECIFICATION.md §3 "Data flow"):
|
||||
hass.states[id].attributes.daten -> zustandLesen()/datenLesen()
|
||||
hass.callService(...) -> dienstAufrufen()
|
||||
Dieselben Entitäten, dieselben pyscript-Dienste - nur über HTTP statt über
|
||||
ein Objekt, das nur innerhalb des HA-Frontends existiert. */
|
||||
Dieselben Entitäten, dieselben Dienste der Integration - nur über HTTP statt
|
||||
über ein Objekt, das nur innerhalb des HA-Frontends existiert. */
|
||||
|
||||
import type { DatenEntity, HassState } from "./types.ts";
|
||||
import { zugangLesen, type Zugang } from "./umgebung.ts";
|
||||
@@ -124,8 +124,8 @@ export class HassRest {
|
||||
return (zustand as DatenEntity<T>).attributes.daten;
|
||||
}
|
||||
|
||||
/** Ruft einen Dienst auf, z. B. dienstAufrufen("pyscript",
|
||||
"audi_dashboard_profil_schreiben", { profil_json }). */
|
||||
/** Ruft einen Dienst auf, z. B. dienstAufrufen("audi_dashboard",
|
||||
"profil_schreiben", { profil_json }). */
|
||||
async dienstAufrufen(
|
||||
bereich: string,
|
||||
dienst: string,
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface HassState<A = Record<string, unknown>> {
|
||||
context?: { id: string; parent_id: string | null; user_id: string | null };
|
||||
}
|
||||
|
||||
/** Die pyscript.*-Entitäten transportieren ihre Nutzlast im Attribut "daten". */
|
||||
/** Die Entitäten der Integration transportieren ihre Nutzlast im Attribut "daten". */
|
||||
export interface DatenAttribute<T> {
|
||||
daten: T;
|
||||
[weitere: string]: unknown;
|
||||
@@ -95,7 +95,7 @@ export interface SicherheitsPunkt {
|
||||
}
|
||||
|
||||
/** Feldnamen an der laufenden Instanz abgelesen, nicht aus der Spezifikation
|
||||
abgeleitet: frontend_veroeffentlichung.py schreibt "tankprozent",
|
||||
abgeleitet: veroeffentlichung.py schreibt "tankprozent",
|
||||
"gesichert" und "sicherheitscheck" — ein früherer Entwurf dieser Datei
|
||||
hatte "tank_prozent"/"sicher_abgestellt"/"sicherheit" angenommen, womit
|
||||
die Oberfläche still überall undefined gelesen hätte. */
|
||||
@@ -138,7 +138,7 @@ export interface Profil {
|
||||
[weitere: string]: unknown;
|
||||
}
|
||||
|
||||
/** Ergebnis eines Historien-Imports (pyscript/historienimport.py). Die
|
||||
/** Ergebnis eines Historien-Imports (custom_components/audi_dashboard/historienimport.py). Die
|
||||
Zählwerte fehlen, solange der Lauf noch nicht fertig ist; `meldung` steht
|
||||
nur im Fehlerfall. */
|
||||
export interface ImportErgebnis {
|
||||
@@ -158,19 +158,30 @@ export interface ImportErgebnis {
|
||||
|
||||
/* -------------------------------------------------- Entitäts-Verzeichnis */
|
||||
|
||||
/** Alle vom Backend veröffentlichten pyscript-Entitäten an einer Stelle.
|
||||
/** Alle vom Backend veröffentlichten Entitäten an einer Stelle.
|
||||
Kein Hardcoding über die App verstreut: wer eine Entität umbenennt,
|
||||
ändert genau diese Tabelle. */
|
||||
ändert genau diese Tabelle.
|
||||
|
||||
Die Namen sind ein Vertrag mit custom_components/audi_dashboard/const.py;
|
||||
das HA-Panel führt dieselbe Tabelle in audi-dashboard-app.js. Bis
|
||||
2026-08-23 hießen sie pyscript.audi_dashboard_*, weil pyscript sie
|
||||
bereitstellte und die Domain besaß. Seit dem Umbau zur eigenen Integration
|
||||
sind es echte Entitäten dieser Integration. */
|
||||
export const ENTITAETEN = {
|
||||
profil: "pyscript.audi_dashboard_profil",
|
||||
fahrten: "pyscript.audi_dashboard_fahrten",
|
||||
tankvorgaenge: "pyscript.audi_dashboard_tankvorgaenge",
|
||||
fahrzeugstatus: "pyscript.audi_dashboard_fahrzeugstatus",
|
||||
batterieverlauf: "pyscript.audi_dashboard_batterieverlauf",
|
||||
belegErgebnis: "pyscript.audi_dashboard_beleg_ergebnis",
|
||||
updateStatus: "pyscript.audi_dashboard_update_status",
|
||||
importStatus: "pyscript.audi_dashboard_import_status",
|
||||
appVersion: "pyscript.audi_dashboard_app_version",
|
||||
profil: "sensor.audi_dashboard_profil",
|
||||
fahrten: "sensor.audi_dashboard_fahrten",
|
||||
tankvorgaenge: "sensor.audi_dashboard_tankvorgaenge",
|
||||
fahrzeugstatus: "sensor.audi_dashboard_fahrzeugstatus",
|
||||
batterieverlauf: "sensor.audi_dashboard_batterieverlauf",
|
||||
belegErgebnis: "sensor.audi_dashboard_beleg_ergebnis",
|
||||
importStatus: "sensor.audi_dashboard_import_status",
|
||||
appVersion: "sensor.audi_dashboard_app_version",
|
||||
} as const;
|
||||
|
||||
export type EntitaetsSchluessel = keyof typeof ENTITAETEN;
|
||||
|
||||
/** Die Domain, unter der das Backend seine Dienste anbietet:
|
||||
`audi_dashboard.<name>`. Früher `pyscript.audi_dashboard_<name>` - der
|
||||
doppelte Präfix war nur nötig, weil sich alle pyscript-Dienste eine Domain
|
||||
teilten. Siehe custom_components/audi_dashboard/dienste.py. */
|
||||
export const DIENST_DOMAIN = "audi_dashboard";
|
||||
|
||||
Reference in New Issue
Block a user