Setup-Zuordnung: Zuruecksetzen wirkt, Sicherung, Absicherung

Drei Befunde aus REVIEW_main_2026-08-13.md, alle an der Testinstanz geprueft.

Zuruecksetzen (Befund 2 und 5): overrides_anwenden() uebersprang leere Werte
und setzte nur belegte. Weil setattr das Modul-Attribut dauerhaft veraendert,
blieb ein einmal gesetzter Wert danach fuer immer stehen - bei 15 von 17
Feldern hatte Zuruecksetzen keine Wirkung, und die Oberflaeche zeigte wieder
den alten Wert, als sei das Speichern fehlgeschlagen. Umgekehrt wurde eine
Liste aus leeren Eintraegen gesetzt statt uebersprungen, was den
Sicherheitscheck mit zwoelf unbekannt-Zeilen fuellte. Jetzt wird fuer jedes
bekannte Feld geschrieben: der Override, wenn belegt, sonst der eingebaute
Standardwert. Damit ist der Vorgang zugleich wiederholbar.

Sicherung (Befund 6): entitaeten.json war weder in backup.py noch in der
Wiederherstellung noch im Browser-Export enthalten - die gesamte Zuordnung
waere nach einem Rueckspielen still weg gewesen. Jetzt ueberall dabei; fehlt
der Abschnitt in aelteren Sicherungen, bleibt die aktuelle Zuordnung stehen.

Absicherung (Befund 7): overrides_lesen() fing kaputtes JSON nicht ab. Der
Aufruf steht in beim_start() vor alles_veroeffentlichen() - eine unlesbare
Zeile liess das Panel komplett leer bleiben. Geprueft: es folgt jetzt eine
verstaendliche Meldung, alle sechs Entitaeten werden trotzdem veroeffentlicht.

Dabei ein pyscript-Fallstrick gefunden und im Code vermerkt: Generator-
ausdruecke sind nicht implementiert (not implemented ast ast_generatorexp),
Mengen-, Listen- und Dict-Comprehensions dagegen schon.
This commit is contained in:
Paul Nothaft
2026-08-13 15:52:05 +02:00
parent 12418a3d52
commit 64f457dd87
4 changed files with 91 additions and 17 deletions
+1
View File
@@ -5,6 +5,7 @@
data/fahrzeugprofil.json
data/fahrten.jsonl
data/tankvorgaenge.jsonl
data/entitaeten.json
data/tests/belege/*.pdf
# Generiertes Installations-Bundle (nur eine Kopie der bereits getrackten
+21 -4
View File
@@ -25,11 +25,20 @@ import json
import os
import shutil
import entitaeten
import frontend_veroeffentlichung
import profil
BACKUP_ORDNER = f"{profil.BASIS}/backups"
_DATEIEN = ["fahrzeugprofil.json", "fahrten.jsonl", "tankvorgaenge.jsonl"]
# entitaeten.json gehört dazu: Ohne sie wäre nach einer Wiederherstellung die
# komplette Sensor-Zuordnung aus dem Setup-Menü verloren, und weil sie sonst
# nirgends abgelegt ist, gäbe es nichts zurückzuholen.
_DATEIEN = [
"fahrzeugprofil.json",
"fahrten.jsonl",
"tankvorgaenge.jsonl",
"entitaeten.json",
]
def _backup_jetzt():
@@ -75,10 +84,14 @@ def _automatisches_backup():
@service
def audi_dashboard_backup_wiederherstellen(profil_json=None, fahrten_json=None, tankvorgaenge_json=None):
def audi_dashboard_backup_wiederherstellen(profil_json=None, fahrten_json=None, tankvorgaenge_json=None, entitaeten_json=None):
"""Stellt ein über "Backup exportieren" heruntergeladenes Backup wieder
her - überschreibt Profil, Fahrten und Tankvorgänge komplett. Aufruf als
pyscript.audi_dashboard_backup_wiederherstellen."""
her - überschreibt Profil, Fahrten, Tankvorgänge und die Sensor-Zuordnung
komplett. Aufruf als pyscript.audi_dashboard_backup_wiederherstellen.
entitaeten_json ist optional: ältere Sicherungen, die vor dem Setup-Menü
entstanden sind, enthalten den Abschnitt nicht - dann bleibt die aktuelle
Zuordnung unangetastet, statt sie zu leeren."""
if profil_json:
p = json.loads(profil_json) if isinstance(profil_json, str) else profil_json
profil.profil_schreiben(p)
@@ -88,5 +101,9 @@ def audi_dashboard_backup_wiederherstellen(profil_json=None, fahrten_json=None,
if tankvorgaenge_json is not None:
tankvorgaenge = json.loads(tankvorgaenge_json) if isinstance(tankvorgaenge_json, str) else tankvorgaenge_json
profil.tankvorgaenge_schreiben(tankvorgaenge)
if entitaeten_json:
zuordnung = json.loads(entitaeten_json) if isinstance(entitaeten_json, str) else entitaeten_json
entitaeten.overrides_schreiben(zuordnung)
entitaeten.overrides_anwenden()
frontend_veroeffentlichung.alles_veroeffentlichen()
log.info("audi_dashboard: Backup wiederhergestellt")
+62 -12
View File
@@ -151,12 +151,34 @@ _STANDARDWERTE = {feld["key"]: getattr(einstellungen, feld["key"], None) for fel
# atomares Schreiben über eine .tmp-Datei + os.replace.
def overrides_lesen():
"""Liest die gespeicherten Zuordnungen, oder {} wenn die Datei fehlt bzw.
beschädigt ist.
Der try/except ist nicht Vorsicht um ihrer selbst willen: overrides_
anwenden() läuft in beim_start() VOR alles_veroeffentlichen(). Ohne die
Absicherung reißt eine einzige unlesbare Zeile in dieser Datei den
gesamten Startvorgang mit - das Panel bliebe komplett leer, ohne dass
irgendetwas auf die Ursache hindeutet."""
if not os.path.exists(OVERRIDES_PFAD):
return {}
f = task.executor(io.open, OVERRIDES_PFAD, "r")
inhalt = f.read()
f.close()
return json.loads(inhalt) if inhalt.strip() else {}
if not inhalt.strip():
return {}
try:
gelesen = json.loads(inhalt)
except ValueError as fehler:
log.error(
f"audi_dashboard: {OVERRIDES_PFAD} ist kein gültiges JSON ({fehler}). "
"Die eingebauten Standardwerte gelten weiter; die Zuordnung lässt sich "
"im Setup-Menü neu speichern."
)
return {}
if not isinstance(gelesen, dict):
log.error(f"audi_dashboard: {OVERRIDES_PFAD} enthält kein Objekt - wird ignoriert.")
return {}
return gelesen
def overrides_schreiben(mapping):
@@ -175,19 +197,47 @@ def overrides_schreiben(mapping):
# ------------------------------------------------------- Anwenden/Lesen ---
def _ist_leer(wert):
"""Leer heißt: keine Zuordnung. Auch eine Liste, die nur leere Einträge
enthält - das Setup-Menü schickt für unbelegte Positionen ["","",...],
und das ist inhaltlich dasselbe wie gar keine Liste.
Die Schleife steht hier bewusst statt eines any(...)-Generatorausdrucks:
pyscript kennt Generatorausdrücke nicht und wirft zur Laufzeit
"not implemented ast ast_generatorexp". Mengen-, Listen- und
Dict-Comprehensions funktionieren dagegen (siehe _SCHLUESSEL oben)."""
if wert in (None, ""):
return True
if isinstance(wert, list):
for eintrag in wert:
if eintrag:
return False
return True
return False
def overrides_anwenden():
"""setattr(einstellungen, key, wert) für jeden in entitaeten.json
hinterlegten, nicht-leeren Wert - siehe Kopfkommentar, warum das
ausreicht, damit alle bestehenden Verbraucher den neuen Wert sofort
sehen (Ausnahme: die drei TRIGGER_GEBUNDEN-Felder, dort erst nach
Neustart)."""
"""Setzt für JEDES bekannte Feld den wirksamen Wert auf dem laufenden
einstellungen-Modul - den Override, wenn einer hinterlegt ist, sonst den
eingebauten Standardwert.
Wichtig ist das "jedes": Eine frühere Fassung übersprang leere Werte und
setzte nur die belegten. Weil setattr() das Modul-Attribut aber dauerhaft
verändert, blieb ein einmal gesetzter Wert danach für immer stehen -
"Zurücksetzen" im Setup-Menü hatte bei allen Feldern mit leerem
Standardwert (15 von 17) schlicht keine Wirkung, und die Oberfläche zeigte
beim nächsten Zeichnen wieder den alten Wert, als sei das Speichern
fehlgeschlagen. Umgekehrt wurde eine Liste aus leeren Einträgen gesetzt
statt übersprungen, was den Sicherheitscheck mit zwölf "unbekannt"-Zeilen
füllte und "Sicher abgestellt" dauerhaft grau werden ließ.
Jedes Feld bei jedem Durchlauf zu schreiben ist damit nicht nur korrekt,
sondern auch idempotent: der Zustand hängt allein an entitaeten.json,
nicht an der Reihenfolge vorheriger Aufrufe."""
overrides = overrides_lesen()
for key, wert in overrides.items():
if key not in _SCHLUESSEL:
continue
if wert in (None, "", []):
continue
setattr(einstellungen, key, wert)
for key in _SCHLUESSEL:
wert = overrides.get(key)
setattr(einstellungen, key, _STANDARDWERTE[key] if _ist_leer(wert) else wert)
def aktueller_stand():
+7 -1
View File
@@ -3382,7 +3382,10 @@ function ereignisseVerdrahten() {
const bk = e.target.closest("[data-backup]");
if (bk && bk.dataset.backup === "jetzt") { serviceRufen("audi_dashboard_backup_jetzt", {}); return; }
if (bk && bk.dataset.backup === "export") {
const paket = { erstellt: new Date().toISOString(), profil: PROFIL_ROH, fahrten: TRIPS, tankvorgaenge: FILLS };
// Die Sensor-Zuordnung gehört mit ins Backup: sie liegt sonst nur in
// entitaeten.json und wäre nach einer Wiederherstellung verloren.
const paket = { erstellt: new Date().toISOString(), profil: PROFIL_ROH, fahrten: TRIPS, tankvorgaenge: FILLS,
entitaeten: (ENTITAETEN && ENTITAETEN.werte) || null };
const a = document.createElement("a");
a.href = "data:application/json;charset=utf-8," + encodeURIComponent(JSON.stringify(paket, null, 2));
a.download = `backup_${new Date().toISOString().slice(0, 10)}.json`; document.body.appendChild(a); a.click(); a.remove();
@@ -3522,6 +3525,9 @@ function ereignisseVerdrahten() {
profil_json: JSON.stringify(paket.profil),
fahrten_json: JSON.stringify(TRIPS),
tankvorgaenge_json: JSON.stringify(FILLS),
// Fehlt der Abschnitt (Sicherung von vor dem Setup-Menü), bleibt die
// aktuelle Zuordnung stehen, statt geleert zu werden.
...(paket.entitaeten ? { entitaeten_json: JSON.stringify(paket.entitaeten) } : {}),
});
bild = 0; render();
} catch (err) { hinweis("Die Datei ließ sich nicht lesen", err.message); }