Fahrzustand aus der Zuendung statt aus dem Fahrtstatus, App-Version vergleichbar

BUG "Fahrzeug faehrt" aenderte sich nie. standortZustand() leitete den
Fahrzustand aus TRIPS[0].status === "offen" ab - zwei Fehler
uebereinander, die sich gegenseitig verstaerkt haben.

Erstens heisst "offen" nicht "unterwegs", sondern "Daten noch
unvollstaendig": _fahrt_beenden() legt die Fahrt mit diesem Status an,
wenn sie ENDET, und das Kilometerstand-Screening fuellt sie spaeter. Eine
Fahrt, die nie eine Strecke bekam - etwa weil damals kein KM_SENSOR
zugeordnet war -, bleibt fuer immer "offen". Zweitens ist TRIPS[0] die
AELTESTE Fahrt, nicht die neueste: fahrten_veroeffentlichen() reicht
profil.fahrten_lesen() unveraendert in Dateireihenfolge weiter, und die
ist aufsteigend. Zusammen hat die aelteste jemals unvollstaendig
gebliebene Fahrt das Fahrzeug dauerhaft als fahrend angezeigt; an der
Testinstanz war das eine seit 18 Tagen beendete Fahrt.

Nicht den Index geflickt, sondern die Quelle korrigiert: das Backend
veroeffentlicht jetzt "zuendung" im Fahrzeugstatus, gelesen aus dem
ohnehin zugeordneten ZUENDUNG_SENSOR - demselben Signal, das auch
fahrterkennung.py als massgeblich nimmt. Anzeige und Erfassung koennen
dadurch gar nicht mehr auseinanderlaufen. Ohne zugeordneten Sensor
(null) steht "Fahrzustand unbekannt" statt einer Behauptung.

companion-app hatte denselben Fehler spiegelverkehrt: liveZustandLesen()
las "zuendung" (gab es nie) und "lat"/"lon" (das Backend liefert
standort_lat/standort_lon), und der Typ Fahrzeug kannte keines dieser
Felder. Die Live-Ansicht meldete deshalb dauerhaft "Das Fahrzeug steht"
und zeigte nie eine Position. Felder in Fahrzeugstatus/Fahrzeug und im
Adapter ergaenzt, damit der Typ diese Fehlerklasse kuenftig faengt -
wovor sein eigener Kopfkommentar seit einem frueheren Vorfall warnt.

BUG "Standortzugriff verweigert" war irrefuehrend. Die Zeile steht
direkt unter dem Fahrzeugnamen, wo sonst der Abstand zum Auto steht,
handelt aber vom Standort DIESES Geraets - sie las sich, als sei der
Standort des Autos nicht abrufbar. Jetzt "GPS offline" wie gewuenscht.
Die verweigerte Freigabe behaelt eine eigene Meldung ("GPS-Freigabe
fehlt"): sie ist der einzige Fall mit anderer Abhilfe, und "GPS offline"
wuerde dort zur Signalsuche statt zum Freigabeschalter schicken.

VERSIONIERUNG: die Zahl, die alle fuer "die Version" hielten, ist keine.
Der Integer in audi-dashboard-version.json ist ein Cache-Brecher, den
install.ps1/update.ps1 bei jedem Deploy mit UtcNow neu setzen -
unabhaengig davon, ob sich Code geaendert hat. Zwei Builds derselben
Quelle bekommen verschiedene Zahlen. Er kann die Frage "ist das derselbe
Stand?" grundsaetzlich nicht beantworten.

Deshalb beide Aufgaben getrennt: neue Datei VERSION im Projektstamm
(2026.08.23.1) als Identitaet, von Hand erhoeht; der Integer bleibt
unveraendert der Cache-Brecher. VERSION fliesst in beide Seiten - als
zweites Feld "app" in audi-dashboard-version.json (alle drei
Deploy-Skripte uebernehmen es jetzt; sie haben die Datei bisher komplett
ueberschrieben und haetten es still zerstoert) und ueber vite define als
__APP_VERSION__ in den Companion-Build. Das Backend veroeffentlicht
pyscript.audi_dashboard_app_version, die App vergleicht und meldet eine
Abweichung in der Hinweisleiste - deren erklaerter Grundsatz "nie eine
stille Veraltung" genau dieser Fall ist, nur dass hier nicht die Anzeige
veraltet, sondern die App selbst.

Bewusst nur Gleichheitsvergleich, nie groesser/kleiner: die Version ist
eine Kennung, keine Zahl; Sortieren waere scheingenau und wuerde bei
einem Formatwechsel still falsch antworten. Fehlt eine der beiden
Seiten, wird nicht verglichen und nichts gemeldet - ein aelteres Backend
oder ein Start ohne Netz darf keinen Fehlalarm ausloesen. Der
vite-Build bricht dagegen hart ab, wenn VERSION fehlt, statt eine App zu
erzeugen, die ihre eigene Veraltung nicht erkennen kann. Das Panel
braucht nichts davon: es laedt bei jedem Seitenaufruf neu.

Geprueft: Backend meldet zuendung: False und app_version 2026.08.23.1 im
Testcontainer, Panel zeigt statt "Fahrzeug faehrt" jetzt "Geparkt seit
13 Tg. 13 Std." und statt der alten Meldung "GPS-Freigabe fehlt";
VERSION landet nachweislich im Build (im Bundle gegriffen) und der Build
bricht ohne die Datei ab (gegengeprueft); tsc sauber, Tests 112/112,
vite build sauber, HA-Start ohne Fehler.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 21:59:14 +02:00
parent 41f2a644d3
commit 99cef7c393
24 changed files with 614 additions and 47 deletions
+14 -1
View File
@@ -202,9 +202,22 @@ if ($Pruefen) {
# 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 = "" }
}
$versionsDatei = Join-Path $Ziel "www\audi-dashboard-version.json"
[System.IO.File]::WriteAllText($versionsDatei, "{ ""version"": $version }", (New-Object System.Text.UTF8Encoding($false)))
$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)"
}
@@ -19,6 +19,10 @@ tankvorgaenge_veroeffentlichen() auf einen Zeitraum eingeschränkt werden
(z. B. nur das laufende Jahr), statt hier vorschnell zu vereinfachen.
"""
import io
import json
import os
import einstellungen
import entitaeten
import profil
@@ -87,6 +91,17 @@ def _abs_zahl(wert):
return None if zahl is None else abs(zahl)
def _zu_bool(wert):
"""binary_sensor-Zustand als echtes True/False, None bei fehlender Meldung.
None ist hier ausdrücklich kein "nein": ohne zugeordneten Sensor weiß die
App schlicht nicht, ob gefahren wird - und muss das anzeigen dürfen (§9),
statt "steht" zu behaupten."""
if wert is None:
return None
return str(wert).lower() in ("on", "true", "1", "open", "yes")
def _standort():
"""Live-GPS-Position des Fahrzeugs (Übersicht -> Standort-Kachel) - liest
STANDORT_LAT_SENSOR/STANDORT_LON_SENSOR, zwei eigene sensor-Entities für
@@ -172,6 +187,18 @@ def fahrzeugstatus_veroeffentlichen():
"batteriespannung": _zu_zahl(batteriespannung),
"gesichert": gesichert,
"sicherheitscheck": sicherheitscheck,
# Fährt das Fahrzeug gerade? Kommt aus derselben Zündungs-Entität, die
# auch fahrterkennung.py als maßgebliches Signal für Fahrtbeginn/-ende
# nimmt - damit sagen Anzeige und Erfassung zwangsläufig dasselbe.
#
# Vorher wurde dieser Zustand im Frontend aus dem Status der Fahrten
# abgeleitet ("offen" = fährt). Das war falsch: "offen" heißt
# unvollständige Daten (Strecke fehlt noch, siehe
# fahrtabschluss_logik.py), nicht "unterwegs" - eine Fahrt ohne
# Kilometerstand blieb dauerhaft "offen" und das Fahrzeug damit
# dauerhaft "fahrend". None bedeutet: kein Zündungssensor zugeordnet,
# Zustand unbekannt.
"zuendung": _zu_bool(zustand_oder_none(einstellungen.ZUENDUNG_SENSOR)),
"standort_lat": standort["lat"],
"standort_lon": standort["lon"],
"standort_genauigkeit_m": standort["genauigkeit_m"],
@@ -188,7 +215,41 @@ def fahrzeugstatus_veroeffentlichen():
state.set("pyscript.audi_dashboard_fahrzeugstatus", "aktuell", new_attributes={"daten": daten})
APP_VERSION_PFAD = "/config/www/audi-dashboard-version.json"
def app_version_veroeffentlichen():
"""Welchen Oberflächen-Stand diese Installation ausliefert.
Gedacht für die Companion-App: die trägt ihre Version fest einkompiliert
(vite setzt __APP_VERSION__ aus der Datei VERSION im Projektstamm) und
kann sie gegen diesen Wert halten. Weicht sie ab, ist die App älter als
der Server und sagt das selbst - siehe VERSIONIERUNG.md.
Das Panel braucht den Vergleich nicht: es lädt seine Dateien bei jedem
Seitenaufruf frisch, sobald sich die Cache-Zahl in derselben Datei
geändert hat, und kann deshalb gar nicht veralten.
Bewusst eine eigene Entität statt eines Felds im Fahrzeugstatus: das hier
ist eine Eigenschaft der Installation, keine des Fahrzeugs."""
version = None
if os.path.exists(APP_VERSION_PFAD):
try:
f = task.executor(io.open, APP_VERSION_PFAD, "r")
inhalt = f.read()
f.close()
version = json.loads(inhalt).get("app")
except (ValueError, OSError) as fehler:
log.warning(f"audi_dashboard: App-Version nicht lesbar ({fehler})")
state.set(
"pyscript.audi_dashboard_app_version",
version or "unbekannt",
new_attributes={"daten": {"app": version}},
)
def alles_veroeffentlichen():
app_version_veroeffentlichen()
profil_veroeffentlichen()
fahrten_veroeffentlichen()
tankvorgaenge_veroeffentlichen()
+14 -2
View File
@@ -80,11 +80,23 @@ if (Test-Path "$hier\www\badges") {
Copy-Item "$hier\www\badges\*" "$Ziel\www\badges\" -Force -Recurse
}
# Zwei Zahlen mit verschiedenen Aufgaben, siehe VERSIONIERUNG.md:
# version = Unix-Sekunden, bricht den Browser-Cache, bei jedem Deploy neu
# app = identifiziert die Fassung; wird aus der mitgelieferten Datei
# übernommen, nicht neu erfunden
# An der App-Version erkennt die iOS-App, ob sie noch dem ausgelieferten Stand
# entspricht. Ginge sie hier verloren, wäre der Vergleich still abgeschaltet.
$version = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
"{ ""version"": $version }" | Out-File -FilePath "$Ziel\www\audi-dashboard-version.json" -Encoding utf8 -NoNewline
$appVersion = ""
if (Test-Path "$hier\www\audi-dashboard-version.json") {
try { $appVersion = (Get-Content "$hier\www\audi-dashboard-version.json" -Raw | ConvertFrom-Json).app } catch { $appVersion = "" }
}
$inhaltVersion = if ($appVersion) { "{ ""version"": $version, ""app"": ""$appVersion"" }" }
else { "{ ""version"": $version }" }
$inhaltVersion | Out-File -FilePath "$Ziel\www\audi-dashboard-version.json" -Encoding utf8 -NoNewline
Write-Host ""
Write-Host "Fertig. Version $version." -ForegroundColor Green
Write-Host "Fertig. Version $version$(if ($appVersion) { " (App $appVersion)" })." -ForegroundColor Green
Write-Host "pyscript-Änderungen sind bereits aktiv (Auto-Reload, kein Neustart nötig)."
Write-Host "Frontend-Änderungen: im Browser einmal ganz normal neu laden (F5) reicht -"
Write-Host "kein Hard-Refresh, kein HA-Neustart."
@@ -111,6 +111,7 @@ function profilZuCar(p, status) {
batteriespannung: status.batteriespannung,
gesichert: status.gesichert,
sicherheitscheck: status.sicherheitscheck || [],
zuendung: status.zuendung ?? null,
standortLat: status.standort_lat,
standortLon: status.standort_lon,
standortGenauigkeitM: status.standort_genauigkeit_m,
@@ -588,21 +589,49 @@ function tankstellenMarkerSVG(px = 32) {
return `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${sil}${icon}</svg>`;
}
/* Fahrzustand fürs Menü: "fährt" solange die aktuelle Fahrt noch offen ist
(siehe fahrterkennung.py), sonst "steht". Ohne echtes Bewegungssignal vom
Fahrzeug (siehe AGENTS.md, FMM003/flespi offen) ist das die beste Näherung
aus den bereits vorhandenen Fahrtdaten.
/* Fahrzustand fürs Menü: kommt aus der Zündung (CAR.zuendung, vom Backend aus
demselben Sensor veröffentlicht, den auch fahrterkennung.py als maßgebliches
Signal nimmt). Ist kein Zündungssensor zugeordnet, bleibt der Zustand
ehrlich unbekannt, statt "steht" zu behaupten.
Vorher stand hier `TRIPS[0].status === "offen"` - gleich zwei Fehler
übereinander, die sich gegenseitig verstärkt haben:
1. "offen" heißt NICHT "unterwegs", sondern "Daten noch unvollständig"
(die Strecke fehlt, siehe fahrtabschluss_logik.py). Eine beendete
Fahrt ohne Kilometerstand - etwa weil zu dem Zeitpunkt kein
KM_SENSOR zugeordnet war - bleibt für immer "offen".
2. TRIPS[0] ist die ÄLTESTE Fahrt, nicht die neueste:
fahrten_veroeffentlichen() gibt profil.fahrten_lesen() unverändert in
Dateireihenfolge weiter, und die ist aufsteigend.
Zusammen hieß das: die älteste jemals unvollständig gebliebene Fahrt hat
das Fahrzeug dauerhaft als "fahrend" angezeigt. An der Testinstanz war das
eine Fahrt, die seit 18 Tagen beendet war.
parkplatz unterscheidet einen endgültigen Stillstand (laenger als die in
"Fahrten zusammenfassen" hinterlegte Pausenzeit) von einem blossen
Zwischenstopp. Das Feld ist vorbereitet, wird aber derzeit von keiner
Ansicht gelesen - eine "Letzter Parkplatz"-Anzeige gibt es noch nicht. */
function letzteFahrt() {
let neueste = null;
for (const t of TRIPS) {
if (!t || !t.ts_end) continue;
if (!neueste || t.ts_end > neueste.ts_end) neueste = t;
}
return neueste;
}
function standortZustand() {
const t = TRIPS[0];
if (t && t.status === "offen") return { faehrt: true, seitTs: null, parkplatz: false };
const t = letzteFahrt();
const seitTs = t && t.ts_end ? new Date(t.ts_end).getTime() : null;
if (CAR.zuendung === true) return { faehrt: true, seitTs: null, parkplatz: false };
const minuten = seitTs != null ? (Date.now() - seitTs) / 60000 : null;
return { faehrt: false, seitTs, parkplatz: minuten != null && minuten >= (CONFIG.pauseMin ?? 15) };
return {
faehrt: false,
unbekannt: CAR.zuendung == null,
seitTs,
parkplatz: minuten != null && minuten >= (CONFIG.pauseMin ?? 15),
};
}
function dauerSeitText(ts) {
if (ts == null) return "unbekannt";
@@ -633,7 +662,7 @@ function standortAbstandText() {
return USER_POS_FEHLER || "Abstand wird ermittelt …";
}
function standortErfassen() {
if (!navigator.geolocation) { USER_POS_FEHLER = "Kein Zugriff auf den Standort dieses Geräts"; standortAnsichtAktualisieren(); return; }
if (!navigator.geolocation) { USER_POS_FEHLER = "GPS offline"; standortAnsichtAktualisieren(); return; }
navigator.geolocation.getCurrentPosition(
(pos) => {
USER_POS = { lat: pos.coords.latitude, lon: pos.coords.longitude, genauigkeit: pos.coords.accuracy };
@@ -641,7 +670,16 @@ function standortErfassen() {
standortAnsichtAktualisieren();
},
(err) => {
USER_POS_FEHLER = err.code === err.PERMISSION_DENIED ? "Standortzugriff verweigert" : "Standort nicht verfügbar";
// Diese Zeile steht direkt unter dem Fahrzeugnamen, an der Stelle, wo
// sonst der Abstand zum Auto steht - sie handelt aber vom Standort
// DIESES Geräts, nicht vom Fahrzeug. "Standortzugriff verweigert" las
// sich dort so, als sei der Standort des Autos nicht abrufbar.
//
// Die verweigerte Freigabe bleibt trotzdem eigens benannt: sie ist der
// einzige Fall mit einer anderen Abhilfe (Freigabe erteilen statt auf
// GPS-Empfang warten), und "GPS offline" würde dort in die falsche
// Richtung schicken.
USER_POS_FEHLER = err.code === err.PERMISSION_DENIED ? "GPS-Freigabe fehlt" : "GPS offline";
// Auch der Fehlschlag zaehlt als Versuch. Ohne diese Zeile greift die
// Drossel in standortErfassenFallsNoetig() nie, solange der Nutzer die
// Freigabe verweigert - und jedes Backend-Update loeste eine neue
@@ -1032,8 +1070,11 @@ function vStandort() {
</div>
<div class="standortmenu-adresse" id="standortAdresse">${esc(adresseText)}</div>
<div class="standortmenu-status">
${zustand.faehrt ? `<span class="dot"></span><span>Fahrzeug fährt</span>`
: `${ciSVG(CI.parking)}<span>${zustand.seitTs != null ? "Geparkt seit " + dauerSeitText(zustand.seitTs) : "Fahrzeug steht"}</span>`}
${zustand.faehrt
? `<span class="dot"></span><span>Fahrzeug fährt</span>`
: zustand.unbekannt
? `${ciSVG(CI.parking)}<span>Fahrzustand unbekannt</span>`
: `${ciSVG(CI.parking)}<span>${zustand.seitTs != null ? "Geparkt seit " + dauerSeitText(zustand.seitTs) : "Fahrzeug steht"}</span>`}
</div>
<div class="standortmenu-werte">
${ciSVG(CI.fuelRange)}
@@ -1 +1 @@
{"version": 1787015002}
{"version": 1787016000, "app": "2026.08.23.1"}
@@ -19,6 +19,10 @@ tankvorgaenge_veroeffentlichen() auf einen Zeitraum eingeschränkt werden
(z. B. nur das laufende Jahr), statt hier vorschnell zu vereinfachen.
"""
import io
import json
import os
import einstellungen
import entitaeten
import profil
@@ -87,6 +91,17 @@ def _abs_zahl(wert):
return None if zahl is None else abs(zahl)
def _zu_bool(wert):
"""binary_sensor-Zustand als echtes True/False, None bei fehlender Meldung.
None ist hier ausdrücklich kein "nein": ohne zugeordneten Sensor weiß die
App schlicht nicht, ob gefahren wird - und muss das anzeigen dürfen (§9),
statt "steht" zu behaupten."""
if wert is None:
return None
return str(wert).lower() in ("on", "true", "1", "open", "yes")
def _standort():
"""Live-GPS-Position des Fahrzeugs (Übersicht -> Standort-Kachel) - liest
STANDORT_LAT_SENSOR/STANDORT_LON_SENSOR, zwei eigene sensor-Entities für
@@ -172,6 +187,18 @@ def fahrzeugstatus_veroeffentlichen():
"batteriespannung": _zu_zahl(batteriespannung),
"gesichert": gesichert,
"sicherheitscheck": sicherheitscheck,
# Fährt das Fahrzeug gerade? Kommt aus derselben Zündungs-Entität, die
# auch fahrterkennung.py als maßgebliches Signal für Fahrtbeginn/-ende
# nimmt - damit sagen Anzeige und Erfassung zwangsläufig dasselbe.
#
# Vorher wurde dieser Zustand im Frontend aus dem Status der Fahrten
# abgeleitet ("offen" = fährt). Das war falsch: "offen" heißt
# unvollständige Daten (Strecke fehlt noch, siehe
# fahrtabschluss_logik.py), nicht "unterwegs" - eine Fahrt ohne
# Kilometerstand blieb dauerhaft "offen" und das Fahrzeug damit
# dauerhaft "fahrend". None bedeutet: kein Zündungssensor zugeordnet,
# Zustand unbekannt.
"zuendung": _zu_bool(zustand_oder_none(einstellungen.ZUENDUNG_SENSOR)),
"standort_lat": standort["lat"],
"standort_lon": standort["lon"],
"standort_genauigkeit_m": standort["genauigkeit_m"],
@@ -188,7 +215,41 @@ def fahrzeugstatus_veroeffentlichen():
state.set("pyscript.audi_dashboard_fahrzeugstatus", "aktuell", new_attributes={"daten": daten})
APP_VERSION_PFAD = "/config/www/audi-dashboard-version.json"
def app_version_veroeffentlichen():
"""Welchen Oberflächen-Stand diese Installation ausliefert.
Gedacht für die Companion-App: die trägt ihre Version fest einkompiliert
(vite setzt __APP_VERSION__ aus der Datei VERSION im Projektstamm) und
kann sie gegen diesen Wert halten. Weicht sie ab, ist die App älter als
der Server und sagt das selbst - siehe VERSIONIERUNG.md.
Das Panel braucht den Vergleich nicht: es lädt seine Dateien bei jedem
Seitenaufruf frisch, sobald sich die Cache-Zahl in derselben Datei
geändert hat, und kann deshalb gar nicht veralten.
Bewusst eine eigene Entität statt eines Felds im Fahrzeugstatus: das hier
ist eine Eigenschaft der Installation, keine des Fahrzeugs."""
version = None
if os.path.exists(APP_VERSION_PFAD):
try:
f = task.executor(io.open, APP_VERSION_PFAD, "r")
inhalt = f.read()
f.close()
version = json.loads(inhalt).get("app")
except (ValueError, OSError) as fehler:
log.warning(f"audi_dashboard: App-Version nicht lesbar ({fehler})")
state.set(
"pyscript.audi_dashboard_app_version",
version or "unbekannt",
new_attributes={"daten": {"app": version}},
)
def alles_veroeffentlichen():
app_version_veroeffentlichen()
profil_veroeffentlichen()
fahrten_veroeffentlichen()
tankvorgaenge_veroeffentlichen()
+12 -2
View File
@@ -80,11 +80,21 @@ if (Test-Path "$hier\www\badges") {
Copy-Item "$hier\www\badges\*" "$Ziel\www\badges\" -Force -Recurse
}
# Zwei Zahlen mit verschiedenen Aufgaben, siehe VERSIONIERUNG.md:
# version = Unix-Sekunden, bricht den Browser-Cache, bei jedem Deploy neu
# app = Inhalt der Datei VERSION im Projektstamm, identifiziert die Fassung
# Die App-Version muss mitgeschrieben werden: an ihr erkennt die iOS-App, ob sie
# noch dem ausgelieferten Stand entspricht. Ginge sie hier verloren, wäre der
# Vergleich still abgeschaltet - genau die Veraltung, die er verhindern soll.
$version = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
"{ ""version"": $version }" | Out-File -FilePath "$Ziel\www\audi-dashboard-version.json" -Encoding utf8 -NoNewline
$appVersion = ""
if (Test-Path "$hier\..\VERSION") { $appVersion = (Get-Content "$hier\..\VERSION" -Raw).Trim() }
$inhaltVersion = if ($appVersion) { "{ ""version"": $version, ""app"": ""$appVersion"" }" }
else { "{ ""version"": $version }" }
$inhaltVersion | Out-File -FilePath "$Ziel\www\audi-dashboard-version.json" -Encoding utf8 -NoNewline
Write-Host ""
Write-Host "Fertig. Version $version." -ForegroundColor Green
Write-Host "Fertig. Version $version$(if ($appVersion) { " (App $appVersion)" })." -ForegroundColor Green
Write-Host "pyscript-Änderungen sind bereits aktiv (Auto-Reload, kein Neustart nötig)."
Write-Host "Frontend-Änderungen: im Browser einmal ganz normal neu laden (F5) reicht -"
Write-Host "kein Hard-Refresh, kein HA-Neustart."
+52 -11
View File
@@ -111,6 +111,7 @@ function profilZuCar(p, status) {
batteriespannung: status.batteriespannung,
gesichert: status.gesichert,
sicherheitscheck: status.sicherheitscheck || [],
zuendung: status.zuendung ?? null,
standortLat: status.standort_lat,
standortLon: status.standort_lon,
standortGenauigkeitM: status.standort_genauigkeit_m,
@@ -588,21 +589,49 @@ function tankstellenMarkerSVG(px = 32) {
return `<svg viewBox="0 0 ${box} ${box}" width="${px}" height="${px}" fill="none" aria-hidden="true">${sil}${icon}</svg>`;
}
/* Fahrzustand fürs Menü: "fährt" solange die aktuelle Fahrt noch offen ist
(siehe fahrterkennung.py), sonst "steht". Ohne echtes Bewegungssignal vom
Fahrzeug (siehe AGENTS.md, FMM003/flespi offen) ist das die beste Näherung
aus den bereits vorhandenen Fahrtdaten.
/* Fahrzustand fürs Menü: kommt aus der Zündung (CAR.zuendung, vom Backend aus
demselben Sensor veröffentlicht, den auch fahrterkennung.py als maßgebliches
Signal nimmt). Ist kein Zündungssensor zugeordnet, bleibt der Zustand
ehrlich unbekannt, statt "steht" zu behaupten.
Vorher stand hier `TRIPS[0].status === "offen"` - gleich zwei Fehler
übereinander, die sich gegenseitig verstärkt haben:
1. "offen" heißt NICHT "unterwegs", sondern "Daten noch unvollständig"
(die Strecke fehlt, siehe fahrtabschluss_logik.py). Eine beendete
Fahrt ohne Kilometerstand - etwa weil zu dem Zeitpunkt kein
KM_SENSOR zugeordnet war - bleibt für immer "offen".
2. TRIPS[0] ist die ÄLTESTE Fahrt, nicht die neueste:
fahrten_veroeffentlichen() gibt profil.fahrten_lesen() unverändert in
Dateireihenfolge weiter, und die ist aufsteigend.
Zusammen hieß das: die älteste jemals unvollständig gebliebene Fahrt hat
das Fahrzeug dauerhaft als "fahrend" angezeigt. An der Testinstanz war das
eine Fahrt, die seit 18 Tagen beendet war.
parkplatz unterscheidet einen endgültigen Stillstand (laenger als die in
"Fahrten zusammenfassen" hinterlegte Pausenzeit) von einem blossen
Zwischenstopp. Das Feld ist vorbereitet, wird aber derzeit von keiner
Ansicht gelesen - eine "Letzter Parkplatz"-Anzeige gibt es noch nicht. */
function letzteFahrt() {
let neueste = null;
for (const t of TRIPS) {
if (!t || !t.ts_end) continue;
if (!neueste || t.ts_end > neueste.ts_end) neueste = t;
}
return neueste;
}
function standortZustand() {
const t = TRIPS[0];
if (t && t.status === "offen") return { faehrt: true, seitTs: null, parkplatz: false };
const t = letzteFahrt();
const seitTs = t && t.ts_end ? new Date(t.ts_end).getTime() : null;
if (CAR.zuendung === true) return { faehrt: true, seitTs: null, parkplatz: false };
const minuten = seitTs != null ? (Date.now() - seitTs) / 60000 : null;
return { faehrt: false, seitTs, parkplatz: minuten != null && minuten >= (CONFIG.pauseMin ?? 15) };
return {
faehrt: false,
unbekannt: CAR.zuendung == null,
seitTs,
parkplatz: minuten != null && minuten >= (CONFIG.pauseMin ?? 15),
};
}
function dauerSeitText(ts) {
if (ts == null) return "unbekannt";
@@ -633,7 +662,7 @@ function standortAbstandText() {
return USER_POS_FEHLER || "Abstand wird ermittelt …";
}
function standortErfassen() {
if (!navigator.geolocation) { USER_POS_FEHLER = "Kein Zugriff auf den Standort dieses Geräts"; standortAnsichtAktualisieren(); return; }
if (!navigator.geolocation) { USER_POS_FEHLER = "GPS offline"; standortAnsichtAktualisieren(); return; }
navigator.geolocation.getCurrentPosition(
(pos) => {
USER_POS = { lat: pos.coords.latitude, lon: pos.coords.longitude, genauigkeit: pos.coords.accuracy };
@@ -641,7 +670,16 @@ function standortErfassen() {
standortAnsichtAktualisieren();
},
(err) => {
USER_POS_FEHLER = err.code === err.PERMISSION_DENIED ? "Standortzugriff verweigert" : "Standort nicht verfügbar";
// Diese Zeile steht direkt unter dem Fahrzeugnamen, an der Stelle, wo
// sonst der Abstand zum Auto steht - sie handelt aber vom Standort
// DIESES Geräts, nicht vom Fahrzeug. "Standortzugriff verweigert" las
// sich dort so, als sei der Standort des Autos nicht abrufbar.
//
// Die verweigerte Freigabe bleibt trotzdem eigens benannt: sie ist der
// einzige Fall mit einer anderen Abhilfe (Freigabe erteilen statt auf
// GPS-Empfang warten), und "GPS offline" würde dort in die falsche
// Richtung schicken.
USER_POS_FEHLER = err.code === err.PERMISSION_DENIED ? "GPS-Freigabe fehlt" : "GPS offline";
// Auch der Fehlschlag zaehlt als Versuch. Ohne diese Zeile greift die
// Drossel in standortErfassenFallsNoetig() nie, solange der Nutzer die
// Freigabe verweigert - und jedes Backend-Update loeste eine neue
@@ -1032,8 +1070,11 @@ function vStandort() {
</div>
<div class="standortmenu-adresse" id="standortAdresse">${esc(adresseText)}</div>
<div class="standortmenu-status">
${zustand.faehrt ? `<span class="dot"></span><span>Fahrzeug fährt</span>`
: `${ciSVG(CI.parking)}<span>${zustand.seitTs != null ? "Geparkt seit " + dauerSeitText(zustand.seitTs) : "Fahrzeug steht"}</span>`}
${zustand.faehrt
? `<span class="dot"></span><span>Fahrzeug fährt</span>`
: zustand.unbekannt
? `${ciSVG(CI.parking)}<span>Fahrzustand unbekannt</span>`
: `${ciSVG(CI.parking)}<span>${zustand.seitTs != null ? "Geparkt seit " + dauerSeitText(zustand.seitTs) : "Fahrzeug steht"}</span>`}
</div>
<div class="standortmenu-werte">
${ciSVG(CI.fuelRange)}
@@ -1 +1 @@
{"version": 1787015002}
{"version": 1787016000, "app": "2026.08.23.1"}