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:
parent
8a2a73c68d
commit
540ce5b1eb
4 changed files with 104 additions and 10 deletions
21
server.py
21
server.py
|
|
@ -1,7 +1,10 @@
|
|||
"""
|
||||
VersaPad Viewer -- Browser-Variante.
|
||||
Liest bei jedem Request live die 3 Config-JSONs vom Desktop und rendert
|
||||
die Steuermatrix (4x5 Grid + 4 Encoder) je Profil als HTML.
|
||||
Liest bei jedem Request die kombinierte Config-JSON vom Desktop und
|
||||
rendert die Steuermatrix (4x5 Grid + 4 Encoder) je Profil als HTML. Fehlt
|
||||
die JSON (z.B. geloescht), wird sie automatisch per Serial vom Board neu
|
||||
aufgebaut und als neuer Cache gespeichert -- das Board ist die eigentliche
|
||||
Quelle der Wahrheit, siehe versapad_combined.load_or_fetch().
|
||||
Kein Build-Schritt, kein externes Framework -- nur stdlib.
|
||||
|
||||
Start: python server.py [--port 8765]
|
||||
|
|
@ -12,6 +15,7 @@ import webbrowser
|
|||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
import versapad_combined as vcomb
|
||||
import versapad_data as vp
|
||||
|
||||
PAGE_CSS = """
|
||||
|
|
@ -87,7 +91,12 @@ def render_encoder(enc):
|
|||
|
||||
|
||||
def render_page(profile):
|
||||
cfg = vp.load_profile(profile)
|
||||
combined = vcomb.load_or_fetch()
|
||||
raw = combined["profiles"][profile]
|
||||
cfg = vp.annotate_profile({
|
||||
"buttons": [dict(b) for b in raw["buttons"]],
|
||||
"encoders": [dict(e) for e in raw["encoders"]],
|
||||
})
|
||||
tabs = "".join(
|
||||
f'<a class="tab {"active" if p == profile else ""}" href="/?profile={p}">{html.escape(vp.PROFILE_NAMES[p])}</a>'
|
||||
for p in sorted(vp.PROFILE_NAMES)
|
||||
|
|
@ -107,7 +116,7 @@ def render_page(profile):
|
|||
<div class="grid">{cells}</div>
|
||||
<h2>Encoder</h2>
|
||||
<div class="encoders">{encoders}</div>
|
||||
<footer>Quelle: {html.escape(vp.CONFIG_PATHS[profile])}</footer>
|
||||
<footer>Quelle: {html.escape(vcomb.DEFAULT_PATH)}</footer>
|
||||
</body></html>"""
|
||||
|
||||
|
||||
|
|
@ -130,10 +139,10 @@ class Handler(BaseHTTPRequestHandler):
|
|||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
except FileNotFoundError as e:
|
||||
except (FileNotFoundError, RuntimeError) as e:
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
self.wfile.write(f"Config-Datei fehlt: {e}".encode("utf-8"))
|
||||
self.wfile.write(f"Config nicht verfuegbar: {e}".encode("utf-8"))
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue