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 <[email protected]>
This commit is contained in:
@@ -1,90 +1,72 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Installiert "Mein Audi" (DataMetric360) in einem Rutsch auf einer neuen
|
||||
Home-Assistant-Instanz - die Schritte 1-3 aus ANLEITUNG.md.
|
||||
Installiert "Mein Audi" (DataMetric360) als Home-Assistant-Integration -
|
||||
der Weg ohne HACS.
|
||||
|
||||
.BESCHREIBUNG
|
||||
Nimmt dem Erstaufsetzen die Handarbeit ab:
|
||||
1. pyscript\ -> <Ziel>\pyscript\
|
||||
2. data\ -> <Ziel>\audi_dashboard\ (Ordner wird umbenannt)
|
||||
fahrzeugprofil.example.json -> fahrzeugprofil.json
|
||||
3. www\ -> <Ziel>\www\
|
||||
4. configuration_snippet.yaml wird in <Ziel>\configuration.yaml eingefügt
|
||||
5. ha_token.txt wird geschrieben, wenn -Token übergeben wurde
|
||||
Kopiert genau einen Ordner:
|
||||
|
||||
custom_components\audi_dashboard\ -> <Ziel>\custom_components\
|
||||
|
||||
Mehr ist nicht nötig. Alles, was früher an dieser Stelle von Hand gemacht
|
||||
werden musste, macht die Integration inzwischen selbst: sie meldet ihr Panel
|
||||
an, liefert ihre Oberflächen-Dateien selbst aus, legt beim ersten Start ein
|
||||
Fahrzeugprofil aus der Vorlage an und bringt pypdf über ihre manifest.json
|
||||
mit, das Home Assistant beim ersten Laden nachinstalliert.
|
||||
|
||||
WOFÜR ES DIESES SKRIPT ÜBERHAUPT NOCH GIBT
|
||||
------------------------------------------
|
||||
Der eigentliche Weg ist HACS. HACS spricht aber ausschließlich mit GitHub
|
||||
(github.com/api.github.com stecken fest im Code) - solange dieses Projekt
|
||||
nur auf der eigenen Gitea-Instanz liegt, findet HACS es nicht. Dieses Skript
|
||||
ist der Weg dorthin, der ohne GitHub auskommt. Es installiert exakt
|
||||
denselben Ordner, den auch HACS installieren würde; ein späterer Wechsel auf
|
||||
HACS überschreibt ihn einfach.
|
||||
|
||||
SICHERHEIT GEGENÜBER DER BESTEHENDEN HA-INSTALLATION
|
||||
----------------------------------------------------
|
||||
Das Skript ist so gebaut, dass es eine laufende Home-Assistant-Installation
|
||||
(auch HA OS) nicht gefährden kann. Konkret und nachprüfbar:
|
||||
Die riskanteste Stelle der früheren Fassung gibt es nicht mehr: sie musste
|
||||
die configuration.yaml ergänzen (pyscript:, panel_custom:), und eine kaputte
|
||||
configuration.yaml ist der eine Weg, auf dem ein Installer Home Assistant am
|
||||
Starten hindern kann. Diese Fassung fasst die Datei überhaupt nicht mehr an.
|
||||
|
||||
- Es LÖSCHT NIE etwas. Im gesamten Skript kommt kein Remove-Item vor, und
|
||||
kopiert wird bewusst ohne robocopy /MIR - fremde Dateien in pyscript\
|
||||
und www\ bleiben liegen, es wird nur ergänzt.
|
||||
- Es fasst .storage\ nicht an: keine Integrationen, keine Geräte, keine
|
||||
Entitäten, keine Benutzer, keine Automatisierungen. automations.yaml,
|
||||
scripts.yaml, scenes.yaml und secrets.yaml werden nicht gelesen und
|
||||
nicht geschrieben.
|
||||
Was bleibt, ist nachprüfbar:
|
||||
- Geschrieben wird ausschließlich in <Ziel>\custom_components\audi_dashboard.
|
||||
Kein anderer Pfad wird angefasst - nicht .storage\, nicht
|
||||
configuration.yaml, nicht automations.yaml, nicht www\, nicht
|
||||
audi_dashboard\ (die Fahrzeugdaten).
|
||||
- Entfernt wird nur der eigene Ordner, und nur, wenn darin eine
|
||||
manifest.json mit "domain": "audi_dashboard" liegt. Ein fremder Ordner
|
||||
unter diesem Namen führt zum Abbruch, nicht zum Löschen.
|
||||
- Es startet Home Assistant nicht neu und greift nicht in den laufenden
|
||||
Betrieb ein. Bis zum manuellen Neustart ändert sich am Verhalten der
|
||||
Instanz nichts.
|
||||
- Die einzige Datei außerhalb der eigenen Ordner, die überhaupt verändert
|
||||
wird, ist configuration.yaml - und das ist der einzige Weg, auf dem
|
||||
dieses Skript HA am Starten hindern könnte. Deshalb dort drei Netze:
|
||||
eine zeitgestempelte Sicherung vor jeder Änderung, ein Abbruch ohne
|
||||
jede Änderung falls dort schon fremde pyscript:/panel_custom:-Blöcke
|
||||
stehen, und eine Rücklese-Prüfung nach dem Schreiben, die bei der
|
||||
kleinsten Abweichung automatisch zurückrollt.
|
||||
Betrieb ein.
|
||||
- -Pruefen zeigt den kompletten Ablauf, ohne irgendetwas zu schreiben.
|
||||
Im Zweifel damit anfangen.
|
||||
|
||||
Was das Skript NICHT kann: pyscript installieren (das macht HACS),
|
||||
pypdf nachinstallieren (braucht eine Shell auf der HA-Maschine) und HA
|
||||
neu starten. Diese Schritte stehen am Ende als Restliste.
|
||||
Warum der Zielordner vor dem Kopieren geleert wird (und nicht nur
|
||||
überschrieben): bleibt eine Datei aus einer älteren Fassung liegen, die es
|
||||
im neuen Stand nicht mehr gibt, lädt Home Assistant sie trotzdem mit. Das
|
||||
ist genau der Fehler, den man hinterher am schwersten findet. HACS macht es
|
||||
aus demselben Grund so.
|
||||
|
||||
Grundsatz: nichts kaputtmachen, was schon da ist. Das Skript ist darauf
|
||||
ausgelegt, gefahrlos mehrfach zu laufen (etwa nach einem Abbruch):
|
||||
|
||||
- Bestandsdaten (fahrzeugprofil.json, fahrten.jsonl, tankvorgaenge.jsonl,
|
||||
entitaeten.json, ha_token.txt) werden NIE überschrieben. Wer eine
|
||||
laufende Installation neu bespielt, behält sein Fahrzeugprofil, seine
|
||||
Fahrten, Tankvorgänge und Sensorzuordnung.
|
||||
- configuration.yaml wird vor jeder Änderung nach configuration.yaml.bak
|
||||
kopiert. Der eingefügte Teil steht zwischen zwei Markierungszeilen und
|
||||
wird bei einem erneuten Lauf ersetzt statt ein zweites Mal angehängt.
|
||||
- Stehen `pyscript:` oder `panel_custom:` dort schon aus anderer Quelle,
|
||||
fasst das Skript die Datei NICHT an, sondern zeigt an, was von Hand zu
|
||||
ergänzen ist. Zwei gleiche Top-Level-Schlüssel wären ungültiges YAML und
|
||||
HA würde nicht mehr starten - das ist keine Entscheidung, die ein
|
||||
Installer still für jemanden treffen sollte.
|
||||
|
||||
Nicht automatisierbar und deshalb am Ende als Restliste ausgegeben:
|
||||
HA-Neustart, `pip install pypdf` (braucht eine Shell auf der HA-Maschine)
|
||||
und das Zuordnen der Sensoren im Setup-Menü der App.
|
||||
Die Fahrzeugdaten liegen woanders (<Ziel>\audi_dashboard\) und werden von
|
||||
diesem Skript nie berührt - ein Update kann sie deshalb gar nicht treffen.
|
||||
|
||||
.PARAMETER Ziel
|
||||
config-Verzeichnis der HA-Instanz, z. B. "\\homeassistant.local\config"
|
||||
(Samba-Add-on) oder ein lokal gemounteter Pfad. Ohne Angabe sucht das
|
||||
Skript die üblichen Samba-Namen selbst ab.
|
||||
|
||||
.PARAMETER Token
|
||||
Long-Lived Access Token aus HA (Profil-Avatar -> "Long-lived access
|
||||
tokens"). Wird nur nach audi_dashboard\ha_token.txt geschrieben und für das
|
||||
Kilometerstand-Screening gebraucht. Ohne Angabe wird die Datei nicht
|
||||
angelegt und der Schritt landet in der Restliste. Eine schon vorhandene
|
||||
Datei bleibt in jedem Fall unangetastet.
|
||||
|
||||
.PARAMETER Pruefen
|
||||
Nur anzeigen, was passieren würde - es wird nichts geschrieben.
|
||||
|
||||
.BEISPIEL
|
||||
.\install.ps1
|
||||
.\install.ps1 -Ziel "\\192.168.1.50\config"
|
||||
.\install.ps1 -Ziel "\\homeassistant.local\config" -Token "eyJhb..."
|
||||
.\install.ps1 -Pruefen
|
||||
#>
|
||||
param(
|
||||
[string]$Ziel,
|
||||
[string]$Token,
|
||||
[switch]$Pruefen
|
||||
)
|
||||
|
||||
@@ -96,7 +78,6 @@ function Gut($text) { Write-Host " [ok] $text" -ForegroundColor Green }
|
||||
function Info($text) { Write-Host " [info] $text" -ForegroundColor DarkGray }
|
||||
function Warnung($text) { Write-Host " [!] $text" -ForegroundColor Yellow }
|
||||
|
||||
# Sammelt alles ein, was der Mensch am Ende noch selbst tun muss.
|
||||
$restliste = New-Object System.Collections.ArrayList
|
||||
|
||||
Write-Host ""
|
||||
@@ -104,6 +85,23 @@ Write-Host " Mein Audi - Installation" -ForegroundColor White
|
||||
Write-Host " ========================" -ForegroundColor White
|
||||
if ($Pruefen) { Warnung "Pruefmodus - es wird nichts geschrieben." }
|
||||
|
||||
# ------------------------------------------------------------ Quelle finden
|
||||
# Zwei Lagen, damit sowohl der Aufruf aus dem Projektordner heraus als auch
|
||||
# ein separat weitergegebenes Paket funktioniert.
|
||||
$quellKandidaten = @(
|
||||
(Join-Path $hier "custom_components\audi_dashboard"),
|
||||
(Join-Path $hier "..\..\custom_components\audi_dashboard")
|
||||
)
|
||||
$quelle = $null
|
||||
foreach ($k in $quellKandidaten) {
|
||||
if (Test-Path (Join-Path $k "manifest.json")) { $quelle = (Resolve-Path $k).Path; break }
|
||||
}
|
||||
if (-not $quelle) {
|
||||
throw "custom_components\audi_dashboard nicht gefunden - dieses Skript gehört in den Projektordner (oder das Paket daneben)."
|
||||
}
|
||||
$version = (Get-Content (Join-Path $quelle "manifest.json") -Raw | ConvertFrom-Json).version
|
||||
Gut "Quelle: $quelle (Version $version)"
|
||||
|
||||
# ---------------------------------------------------------------- Ziel finden
|
||||
if (-not $Ziel) {
|
||||
Schritt "Suche Home Assistant ..."
|
||||
@@ -132,229 +130,81 @@ if (-not $Ziel) {
|
||||
if (-not (Test-Path $Ziel)) {
|
||||
throw "Ziel nicht erreichbar: $Ziel - Samba-Share verbunden? Pfad korrekt?"
|
||||
}
|
||||
Gut "Ziel: $Ziel"
|
||||
|
||||
# Ein config-Verzeichnis ohne configuration.yaml ist mit hoher Wahrscheinlichkeit
|
||||
# der falsche Ordner - lieber hier abbrechen als Dateien irgendwohin streuen.
|
||||
$configYaml = Join-Path $Ziel "configuration.yaml"
|
||||
if (-not (Test-Path $configYaml)) {
|
||||
if (-not (Test-Path (Join-Path $Ziel "configuration.yaml"))) {
|
||||
throw "In $Ziel liegt keine configuration.yaml - ist das wirklich das config-Verzeichnis von HA?"
|
||||
}
|
||||
Gut "Ziel: $Ziel"
|
||||
|
||||
# pyscript wird über HACS installiert, nicht von hier - ohne die Integration
|
||||
# läuft das komplette Backend nicht, deshalb früh darauf hinweisen.
|
||||
if (-not (Test-Path (Join-Path $Ziel "custom_components\pyscript"))) {
|
||||
Warnung "custom_components\pyscript fehlt - pyscript ist offenbar noch nicht über HACS installiert."
|
||||
[void]$restliste.Add("pyscript über HACS installieren (HACS -> Integrationen -> 'pyscript' -> Download), danach dieses Skript erneut laufen lassen.")
|
||||
# ------------------------------------------------------- Zielordner prüfen
|
||||
$zielOrdner = Join-Path $Ziel "custom_components\audi_dashboard"
|
||||
$vorhandeneVersion = $null
|
||||
if (Test-Path $zielOrdner) {
|
||||
$zielManifest = Join-Path $zielOrdner "manifest.json"
|
||||
if (-not (Test-Path $zielManifest)) {
|
||||
throw "In $zielOrdner liegt keine manifest.json. Das ist nicht unser Ordner - Abbruch, es wird nichts angefasst."
|
||||
}
|
||||
$vorhandenesManifest = Get-Content $zielManifest -Raw | ConvertFrom-Json
|
||||
if ($vorhandenesManifest.domain -ne "audi_dashboard") {
|
||||
throw "$zielOrdner gehört zur Integration '$($vorhandenesManifest.domain)', nicht zu 'audi_dashboard' - Abbruch."
|
||||
}
|
||||
$vorhandeneVersion = $vorhandenesManifest.version
|
||||
Info "bereits installiert: Version $vorhandeneVersion"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- 1. pyscript
|
||||
Schritt "1/5 pyscript-Backend kopieren"
|
||||
# ----------------------------------------------------------------- Kopieren
|
||||
Schritt "Integration kopieren"
|
||||
if ($Pruefen) {
|
||||
Info "würde kopieren: $hier\pyscript\* -> $Ziel\pyscript\"
|
||||
if ($vorhandeneVersion) { Info "würde ersetzen: $zielOrdner (Version $vorhandeneVersion -> $version)" }
|
||||
else { Info "würde anlegen: $zielOrdner (Version $version)" }
|
||||
Info "würde sonst nichts anfassen - keine configuration.yaml, keine Fahrzeugdaten"
|
||||
} else {
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $Ziel "pyscript") | Out-Null
|
||||
# Bewusst kein robocopy /MIR: das würde fremde Skripte im selben Ordner
|
||||
# löschen. Hier wird nur ergänzt und überschrieben.
|
||||
Copy-Item (Join-Path $hier "pyscript\*") (Join-Path $Ziel "pyscript") -Recurse -Force
|
||||
Gut "pyscript\ kopiert"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------- 2. data -> audi_dashboard
|
||||
Schritt "2/5 Datenverzeichnis anlegen (data -> audi_dashboard)"
|
||||
$datenZiel = Join-Path $Ziel "audi_dashboard"
|
||||
# Dateien, die einer laufenden Installation gehören - nie überschreiben.
|
||||
$bestandsdaten = @(
|
||||
"fahrzeugprofil.json",
|
||||
"fahrten.jsonl",
|
||||
"tankvorgaenge.jsonl",
|
||||
"entitaeten.json",
|
||||
"ha_token.txt"
|
||||
)
|
||||
if (-not $Pruefen) { New-Item -ItemType Directory -Force -Path $datenZiel | Out-Null }
|
||||
|
||||
foreach ($datei in Get-ChildItem (Join-Path $hier "data") -File) {
|
||||
# fahrzeugprofil.example.json ist die Vorlage und heißt am Ziel anders.
|
||||
$zielName = $datei.Name
|
||||
if ($zielName -eq "fahrzeugprofil.example.json") { $zielName = "fahrzeugprofil.json" }
|
||||
$zielPfad = Join-Path $datenZiel $zielName
|
||||
|
||||
if (($bestandsdaten -contains $zielName) -and (Test-Path $zielPfad)) {
|
||||
Info "$zielName existiert bereits - bleibt unverändert"
|
||||
continue
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $Ziel "custom_components") | Out-Null
|
||||
if (Test-Path $zielOrdner) {
|
||||
# Die Prüfung oben hat bestätigt, dass das unser Ordner ist.
|
||||
Remove-Item $zielOrdner -Recurse -Force
|
||||
}
|
||||
if ($Pruefen) {
|
||||
Info "würde schreiben: $zielPfad"
|
||||
} else {
|
||||
Copy-Item $datei.FullName $zielPfad -Force
|
||||
Gut "$zielName"
|
||||
Copy-Item $quelle $zielOrdner -Recurse -Force
|
||||
# Übersetzte Reste einer früheren Fassung gehören nicht ins Ziel.
|
||||
Get-ChildItem $zielOrdner -Recurse -Directory -Filter "__pycache__" |
|
||||
ForEach-Object { Remove-Item $_.FullName -Recurse -Force }
|
||||
|
||||
# Rücklesen: liegt im Ziel wirklich das, was gemeint war?
|
||||
$kontrolle = Join-Path $zielOrdner "manifest.json"
|
||||
if (-not (Test-Path $kontrolle)) {
|
||||
throw "Nach dem Kopieren fehlt $kontrolle - die Installation ist unvollständig."
|
||||
}
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- 3. Frontend
|
||||
Schritt "3/5 Frontend kopieren"
|
||||
if ($Pruefen) {
|
||||
Info "würde kopieren: $hier\www\* -> $Ziel\www\"
|
||||
} else {
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $Ziel "www") | Out-Null
|
||||
Copy-Item (Join-Path $hier "www\*") (Join-Path $Ziel "www") -Recurse -Force
|
||||
|
||||
# Wie in update.ps1: die Versionsdatei bestimmt, ob Browser den neuen Code
|
||||
# ziehen. Immer frisch schreiben, sonst zeigt ein Browser, der die App
|
||||
# schon einmal geladen hatte, weiter den alten Stand.
|
||||
# Zwei Zahlen mit verschiedenen Aufgaben, siehe VERSIONIERUNG.md:
|
||||
# version = Unix-Sekunden, bricht den Browser-Cache, bei jedem Deploy neu
|
||||
# app = der Stand aus der Datei VERSION, identifiziert die Fassung
|
||||
# Die App-Version kommt aus der mitgelieferten Datei und wird ÜBERNOMMEN,
|
||||
# nicht neu erfunden - sonst verlöre die iOS-App ihren Vergleichswert und
|
||||
# könnte ihre eigene Veraltung nicht mehr erkennen.
|
||||
$version = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
|
||||
$appVersion = ""
|
||||
$quellVersion = Join-Path $hier "www\audi-dashboard-version.json"
|
||||
if (Test-Path $quellVersion) {
|
||||
try { $appVersion = (Get-Content $quellVersion -Raw | ConvertFrom-Json).app } catch { $appVersion = "" }
|
||||
$geschrieben = (Get-Content $kontrolle -Raw | ConvertFrom-Json).version
|
||||
if ($geschrieben -ne $version) {
|
||||
throw "Nach dem Kopieren steht Version '$geschrieben' statt '$version' im Ziel - die Installation ist unvollständig."
|
||||
}
|
||||
$versionsDatei = Join-Path $Ziel "www\audi-dashboard-version.json"
|
||||
$inhaltVersion = if ($appVersion) { "{ ""version"": $version, ""app"": ""$appVersion"" }" }
|
||||
else { "{ ""version"": $version }" }
|
||||
[System.IO.File]::WriteAllText($versionsDatei, $inhaltVersion, (New-Object System.Text.UTF8Encoding($false)))
|
||||
Gut "www\ kopiert (Version $version)"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------- 4. configuration.yaml
|
||||
Schritt "4/5 configuration.yaml ergänzen"
|
||||
$markeAuf = "# >>> Mein Audi (DataMetric360) - automatisch eingefügt, nicht von Hand bearbeiten"
|
||||
$markeZu = "# <<< Mein Audi (DataMetric360)"
|
||||
|
||||
# Beide Dateien ausdrücklich als UTF-8 lesen: Get-Content nimmt in
|
||||
# PowerShell 5.1 sonst die ANSI-Codepage an und macht aus "Ergänzung" ein
|
||||
# "Ergänzung", das dann so in der configuration.yaml landet.
|
||||
$utf8OhneBom = New-Object System.Text.UTF8Encoding($false)
|
||||
$snippet = [System.IO.File]::ReadAllText((Join-Path $hier "configuration_snippet.yaml"), $utf8OhneBom)
|
||||
$inhalt = [System.IO.File]::ReadAllText($configYaml, $utf8OhneBom)
|
||||
if ($null -eq $inhalt) { $inhalt = "" }
|
||||
|
||||
$block = "$markeAuf`r`n$snippet`r`n$markeZu`r`n"
|
||||
$schonDa = $inhalt.Contains($markeAuf)
|
||||
|
||||
# Top-Level-Schlüssel aus fremder Quelle? Dann Finger weg (siehe Kopfkommentar).
|
||||
$ohneUnserenBlock = $inhalt
|
||||
if ($schonDa) {
|
||||
$ohneUnserenBlock = [regex]::Replace(
|
||||
$inhalt,
|
||||
"(?ms)" + [regex]::Escape($markeAuf) + ".*?" + [regex]::Escape($markeZu) + "\r?\n?",
|
||||
""
|
||||
)
|
||||
}
|
||||
$fremdePyscript = [regex]::IsMatch($ohneUnserenBlock, "(?m)^pyscript:")
|
||||
$fremdePanel = [regex]::IsMatch($ohneUnserenBlock, "(?m)^panel_custom:")
|
||||
|
||||
if ($fremdePyscript -or $fremdePanel) {
|
||||
Warnung "configuration.yaml enthält bereits eigene Einträge - sie wird NICHT verändert."
|
||||
if ($fremdePyscript) { Info "gefunden: pyscript:" }
|
||||
if ($fremdePanel) { Info "gefunden: panel_custom:" }
|
||||
Write-Host ""
|
||||
Write-Host " Bitte von Hand zusammenführen (Inhalt aus configuration_snippet.yaml):" -ForegroundColor Yellow
|
||||
Write-Host " - unter pyscript: allow_all_imports: true"
|
||||
Write-Host " - hass_is_global: true"
|
||||
Write-Host " - unter panel_custom: den Listeneintrag 'audi-dashboard-panel'"
|
||||
[void]$restliste.Add("configuration.yaml von Hand ergänzen (siehe Hinweis oben) - zwei gleiche Top-Level-Schlüssel wären ungültiges YAML.")
|
||||
} elseif ($Pruefen) {
|
||||
if ($schonDa) { Info "würde den vorhandenen Mein-Audi-Block ersetzen" }
|
||||
else { Info "würde den Mein-Audi-Block anhängen" }
|
||||
} else {
|
||||
# Zeitgestempelte Sicherung statt einer festen .bak: die feste Datei wurde
|
||||
# bei einem zweiten Lauf von der bereits geänderten Fassung überschrieben -
|
||||
# ausgerechnet das Original, auf das man im Notfall zurückwill, war dann weg.
|
||||
$sicherung = "$configYaml.bak"
|
||||
$stempel = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$sicherungZeit = "$configYaml.$stempel.bak"
|
||||
Copy-Item $configYaml $sicherungZeit -Force
|
||||
if (-not (Test-Path $sicherung)) { Copy-Item $configYaml $sicherung -Force }
|
||||
Info "Sicherung: $(Split-Path $sicherungZeit -Leaf)"
|
||||
|
||||
if ($schonDa) {
|
||||
$neu = [regex]::Replace(
|
||||
$inhalt,
|
||||
"(?ms)" + [regex]::Escape($markeAuf) + ".*?" + [regex]::Escape($markeZu) + "\r?\n?",
|
||||
[System.Text.RegularExpressions.MatchEvaluator]{ param($m) $block }
|
||||
)
|
||||
} else {
|
||||
$trenner = ""
|
||||
if ($inhalt.Length -gt 0 -and -not $inhalt.EndsWith("`n")) { $trenner = "`r`n" }
|
||||
$neu = $inhalt + $trenner + "`r`n" + $block
|
||||
}
|
||||
# Ohne BOM schreiben - HA liest die Datei als YAML, ein BOM hat da nichts
|
||||
# zu suchen (Out-File -Encoding utf8 setzt in PowerShell 5.1 eines).
|
||||
[System.IO.File]::WriteAllText($configYaml, $neu, (New-Object System.Text.UTF8Encoding($false)))
|
||||
|
||||
# Rücklesen und prüfen, bevor wir die Datei so stehen lassen. Eine kaputte
|
||||
# configuration.yaml ist der einzige Weg, auf dem dieses Skript eine
|
||||
# laufende Home-Assistant-Installation lahmlegen könnte (HA startet dann
|
||||
# nicht mehr) - deshalb wird das Ergebnis hier verifiziert und im
|
||||
# Zweifelsfall sofort zurückgerollt, statt es dem Nutzer zu überlassen.
|
||||
$kontrolle = [System.IO.File]::ReadAllText($configYaml, $utf8OhneBom)
|
||||
$bestandNoch = [regex]::Replace(
|
||||
$kontrolle,
|
||||
"(?ms)" + [regex]::Escape($markeAuf) + ".*?" + [regex]::Escape($markeZu) + "\r?\n?",
|
||||
""
|
||||
)
|
||||
$treffer = ([regex]::Matches($kontrolle, [regex]::Escape($markeAuf))).Count
|
||||
$heil = $true
|
||||
$grund = ""
|
||||
if ($kontrolle.Length -lt $inhalt.Length) {
|
||||
$heil = $false; $grund = "die Datei ist kürzer geworden als vorher"
|
||||
} elseif ($treffer -ne 1) {
|
||||
$heil = $false; $grund = "der Mein-Audi-Block steht $treffer mal statt genau einmal darin"
|
||||
} elseif ($bestandNoch.Trim() -ne $ohneUnserenBlock.Trim()) {
|
||||
$heil = $false; $grund = "der bisherige Inhalt der Datei hat sich verändert"
|
||||
}
|
||||
|
||||
if (-not $heil) {
|
||||
Copy-Item $sicherungZeit $configYaml -Force
|
||||
Warnung "configuration.yaml wurde zurückgerollt - $grund."
|
||||
Write-Host " Die Datei ist unverändert wie vor dem Start dieses Skripts." -ForegroundColor Yellow
|
||||
[void]$restliste.Add("configuration.yaml von Hand ergänzen (Inhalt aus configuration_snippet.yaml) - der automatische Weg wurde aus Sicherheitsgründen zurückgenommen.")
|
||||
} elseif ($schonDa) {
|
||||
Gut "vorhandenen Block ersetzt (geprüft)"
|
||||
} else {
|
||||
Gut "Block angehängt (geprüft)"
|
||||
}
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- 5. Token
|
||||
Schritt "5/5 Zugriffstoken"
|
||||
$tokenDatei = Join-Path $datenZiel "ha_token.txt"
|
||||
if (Test-Path $tokenDatei) {
|
||||
Info "ha_token.txt existiert bereits - bleibt unverändert"
|
||||
} elseif ($Token) {
|
||||
if ($Pruefen) {
|
||||
Info "würde schreiben: $tokenDatei"
|
||||
} else {
|
||||
[System.IO.File]::WriteAllText($tokenDatei, $Token.Trim(), (New-Object System.Text.UTF8Encoding($false)))
|
||||
Gut "ha_token.txt geschrieben"
|
||||
}
|
||||
} else {
|
||||
Info "kein -Token übergeben"
|
||||
[void]$restliste.Add("Long-Lived Access Token anlegen (HA: Profil-Avatar unten links -> 'Long-lived access tokens' -> Token erstellen) und den Wert in $tokenDatei speichern - nur den Token, keine Anführungszeichen. Wird fürs Kilometerstand-Screening gebraucht.")
|
||||
$anzahl = (Get-ChildItem $zielOrdner -Recurse -File).Count
|
||||
Gut "custom_components\audi_dashboard (Version $version, $anzahl Dateien)"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- Restliste
|
||||
if (-not $vorhandeneVersion) {
|
||||
[void]$restliste.Add("Home Assistant neu starten: Einstellungen -> System -> Neu starten. (Beim ersten Start ladt HA die Abhaengigkeit pypdf nach - das kann eine Minute dauern.)")
|
||||
[void]$restliste.Add("Integration hinzufuegen: Einstellungen -> Geraete & Dienste -> Integration hinzufuegen -> 'Audi Dashboard'. Danach steht 'Mein Audi' in der Seitenleiste.")
|
||||
[void]$restliste.Add("In der App: Einstellungen -> Fahrzeug einrichten -> Setup - Sensoren zuordnen. Im Auslieferstand ist KEIN Sensor vorbelegt; zwingend fuer die Fahrterkennung ist der Zuendungs-/ACC-Sensor (binary_sensor, meist vom Teltonika FMM003). Ein Neustart ist dafuer nicht mehr noetig - Aenderungen wirken sofort.")
|
||||
} else {
|
||||
[void]$restliste.Add("Home Assistant neu starten: Einstellungen -> System -> Neu starten. (Oder kuerzer: Einstellungen -> Geraete & Dienste -> Audi Dashboard -> Neu laden.)")
|
||||
}
|
||||
|
||||
# Bewusst nicht automatisch eingefügt: der recorder-Block ist eine Entscheidung
|
||||
# über den Plattenplatz der Instanz (ein Jahr Fahrzeugverlauf sind grob 1-1,5 GB,
|
||||
# siehe Kopf von recorder_snippet.yaml), und viele Instanzen haben bereits einen
|
||||
# eigenen recorder:-Block mit anderen Einstellungen. Zusammenführen ist Handarbeit.
|
||||
[void]$restliste.Add("ZEITKRITISCH - Datenaufbewahrung: den Block aus recorder_snippet.yaml in die configuration.yaml übernehmen. Home Assistant löscht Sensor-Verläufe sonst nach 10 Tagen; was weg ist, lässt sich auch mit 'Daten importieren aus Home Assistant' nicht mehr nachtragen. Je früher der Block drin ist, desto mehr Vergangenheit bleibt erhalten. VORHER Einstellungen -> System -> Speicher prüfen: ein Jahr Verlauf braucht grob 1-1,5 GB. Bei wenig freiem Platz (SD-Karte, kleine eMMC) mit purge_keep_days: 90 anfangen - Details im Kopf der Datei.")
|
||||
[void]$restliste.Add("Home Assistant neu starten: Einstellungen -> System -> Neu starten.")
|
||||
[void]$restliste.Add("Nur für Tankbeleg-Upload: auf der HA-Maschine 'pip install pypdf' ausführen (Terminal-&-SSH-Add-on oder docker exec).")
|
||||
[void]$restliste.Add("Nach dem Neustart in der App: Einstellungen -> Fahrzeug einrichten -> Setup - Sensoren zuordnen. Im Auslieferstand ist KEIN Sensor vorbelegt; zwingend für die Fahrterkennung ist der Zündungs-/ACC-Sensor (binary_sensor, meist vom Teltonika FMM003). Danach einmal neu starten - die drei trigger-gebundenen Felder (Zündung, Kilometerstand, Tankfüllstand) werden erst dann wirksam.")
|
||||
[void]$restliste.Add("Optional, sobald die Sensoren zugeordnet sind: Einstellungen -> Einrichten -> 'Daten importieren aus Home Assistant' holt Fahrten, Tankvorgänge und Spannungswerte aus dem bereits aufgezeichneten HA-Verlauf nach.")
|
||||
[void]$restliste.Add("ZEITKRITISCH - Datenaufbewahrung: den Block aus recorder_snippet.yaml in die configuration.yaml uebernehmen. Home Assistant loescht Sensor-Verlaeufe sonst nach 10 Tagen; was weg ist, laesst sich auch mit 'Daten importieren aus Home Assistant' nicht mehr nachtragen. Je frueher der Block drin ist, desto mehr Vergangenheit bleibt erhalten. VORHER Einstellungen -> System -> Speicher pruefen: ein Jahr Verlauf braucht grob 1-1,5 GB. Bei wenig freiem Platz (SD-Karte, kleine eMMC) mit purge_keep_days: 90 anfangen - Details im Kopf der Datei.")
|
||||
[void]$restliste.Add("Optional, sobald die Sensoren zugeordnet sind: Einstellungen -> Einrichten -> 'Daten importieren aus Home Assistant' holt Fahrten, Tankvorgaenge und Spannungswerte aus dem bereits aufgezeichneten HA-Verlauf nach.")
|
||||
|
||||
Write-Host ""
|
||||
if ($Pruefen) {
|
||||
Write-Host " Pruefmodus beendet - es wurde nichts geschrieben." -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host " Dateien sind kopiert." -ForegroundColor Green
|
||||
Write-Host " Die Integration ist installiert." -ForegroundColor Green
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host " Das fehlt noch:" -ForegroundColor White
|
||||
@@ -364,6 +214,6 @@ foreach ($punkt in $restliste) {
|
||||
$nr++
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host " Danach erscheint 'Mein Audi' in der Seitenleiste." -ForegroundColor White
|
||||
Write-Host " Spätere Updates: update.ps1 statt install.ps1 - das lässt audi_dashboard\ in Ruhe."
|
||||
Write-Host " Fuer spaetere Updates reicht derselbe Aufruf noch einmal." -ForegroundColor White
|
||||
Write-Host " Die Fahrzeugdaten unter audi_dashboard\ bleiben dabei unangetastet."
|
||||
Write-Host ""
|
||||
|
||||
Reference in New Issue
Block a user