Fix crash when desktop config JSON is deleted: self-heal from board

server.py and desktop_viewer.py read-only mode required the desktop
config JSONs to exist and crashed/showed "Config-Datei fehlt" if one
was deleted, even though the board already holds the config durably
in NVM. Add versapad_combined.fetch_from_board()/load_or_fetch(): a
missing combined JSON is now transparently rebuilt from the board via
serial and cached back to disk, falling back to the legacy per-profile
JSONs (or a clear error) only when the board is unreachable.
This commit is contained in:
cjjohn 2026-08-08 17:06:53 +02:00
parent 8a2a73c68d
commit 540ce5b1eb
4 changed files with 104 additions and 10 deletions

View file

@ -526,8 +526,13 @@ class VersaPadViewer(tk.Tk):
Bevorzugt die kombinierte Datei (versapad_config_all.json), falls
vorhanden -- so zeigen im Programmiermodus gespeicherte Aenderungen
sich auch hier, statt dass die alten Einzel-JSONs weiter durchscheinen.
Faellt zurueck auf die klassischen versapad_config{1,2,3}.json, wenn
es noch keine kombinierte Datei gibt."""
Fehlt sie (z.B. versehentlich geloescht) und ist Live-Sync gerade aus
(COM-Port frei), wird sie automatisch per Serial vom Board neu
aufgebaut und als neuer Cache gespeichert -- das Board ist die
eigentliche Quelle der Wahrheit, kein Datei-Handling von Hand mehr
noetig. Nur wenn das nicht klappt (Board nicht erreichbar, Live-Sync
haelt den Port), weicht es zuletzt auf die klassischen
versapad_config{1,2,3}.json aus."""
if os.path.exists(vcomb.DEFAULT_PATH):
try:
data = vcomb.load_file(vcomb.DEFAULT_PATH)
@ -538,8 +543,25 @@ class VersaPadViewer(tk.Tk):
})
return cfg, f"Quelle: {vcomb.DEFAULT_PATH}"
except (KeyError, IndexError, ValueError):
pass # kaputte/unvollstaendige Datei -- auf Einzel-JSONs ausweichen
return vp.load_profile(self.profile), f"Quelle: {vp.CONFIG_PATHS[self.profile]}"
pass # kaputte/unvollstaendige Datei -- weiter unten ausweichen
elif not self.live_sync.get():
try:
data = vcomb.fetch_from_board(link=self._link)
vcomb.save_file(data, vcomb.DEFAULT_PATH)
raw = data["profiles"][self.profile]
cfg = vp.annotate_profile({
"buttons": [dict(b) for b in raw["buttons"]],
"encoders": [dict(e) for e in raw["encoders"]],
})
return cfg, "Quelle: Board (neu vom Geraet geladen)"
except RuntimeError:
pass # Board nicht erreichbar -- weiter unten ausweichen
try:
return vp.load_profile(self.profile), f"Quelle: {vp.CONFIG_PATHS[self.profile]}"
except FileNotFoundError as e:
hint = (" (Live-Sync ist an -- COM-Port belegt, zum automatischen "
"Neuladen vom Board erst ausschalten)") if self.live_sync.get() else ""
raise FileNotFoundError(f"{e}{hint}") from e
def _render(self):
for w in self.grid_frame.winfo_children():