Move config storage next to the install and auto-create it if missing
versapad_combined.DEFAULT_PATH was hardcoded to this one machine's OneDrive desktop, which made the tool unusable anywhere else. versapad_data.app_dir() now resolves to the running .exe's own folder when frozen, or the project directory when run from source, and DEFAULT_PATH hangs off that instead. load_or_fetch() previously raised when both the file was missing and the board unreachable, blocking a fresh install with no config and no board attached. It now falls back to an empty default_combined() in that case, so the tool is immediately usable either way. desktop_viewer's _current_profile_view() picks up the same fallback instead of re-implementing a narrower version of it. Also drop the hardcoded PROFILE_NAMES dict, which had drifted out of sync with the profile_names already stored in the combined JSON -- renaming a profile in Programmiermodus never showed up in the read-only/browser views. server.py and desktop_viewer.py now read names from the same JSON everywhere. versapad_data.CONFIG_PATHS (read-only interop with the official C# VersaGUI's JSON export) is intentionally left on the OneDrive desktop -- nothing in this codebase writes there, it's not part of this tool's own config.
This commit is contained in:
parent
13c3745479
commit
5d14bdd826
7 changed files with 147 additions and 57 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -3,3 +3,7 @@ __pycache__/
|
||||||
*.log
|
*.log
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
|
# Nutzer-Config -- landet neben der Installation (versapad_data.app_dir()),
|
||||||
|
# beim Start aus dem Quellcode also direkt hier im Projektordner
|
||||||
|
versapad_config_all.json
|
||||||
|
|
|
||||||
44
AGENTS.md
44
AGENTS.md
|
|
@ -16,6 +16,12 @@ Nutzerorientierte Einführung: [`README.md`](README.md).
|
||||||
- `versapad_data.py` — Decoding für Anzeige: JSON laden, HID-Keycode/
|
- `versapad_data.py` — Decoding für Anzeige: JSON laden, HID-Keycode/
|
||||||
Consumer-Usage/Modifier → lesbarer Text, Grid-Geometrie
|
Consumer-Usage/Modifier → lesbarer Text, Grid-Geometrie
|
||||||
(`index = spalte*5+reihe`), `hid_key_choices()`/`consumer_choices()`.
|
(`index = spalte*5+reihe`), `hid_key_choices()`/`consumer_choices()`.
|
||||||
|
`app_dir()` liefert das Verzeichnis für die eigene Config
|
||||||
|
(`versapad_combined.DEFAULT_PATH`) — bei der `.exe` der Installations-
|
||||||
|
ordner, sonst der Projektordner, siehe Installierbarkeit-Notiz unten.
|
||||||
|
`CONFIG_PATHS` bleibt bewusst auf dem OneDrive-Desktop hartkodiert (Lese-
|
||||||
|
Interop mit einem JSON-Export der offiziellen VersaGUI, kein von diesem
|
||||||
|
Tool geschriebenes Format, siehe dort).
|
||||||
- `server.py` — stdlib `http.server`, generiert HTML pro Request neu,
|
- `server.py` — stdlib `http.server`, generiert HTML pro Request neu,
|
||||||
Profil-Wechsel über `?profile=0|1|2`, Auto-Reload alle 4s. Rein lesend,
|
Profil-Wechsel über `?profile=0|1|2`, Auto-Reload alle 4s. Rein lesend,
|
||||||
kein Programmiermodus (bewusst einfach gehalten).
|
kein Programmiermodus (bewusst einfach gehalten).
|
||||||
|
|
@ -39,8 +45,14 @@ Nutzerorientierte Einführung: [`README.md`](README.md).
|
||||||
liefert `READ_STATUS` schlicht Timeout, kein Absturz.
|
liefert `READ_STATUS` schlicht Timeout, kein Absturz.
|
||||||
- `versapad_combined.py` — Ein-Datei-Format (alle 3 Profile + Makros +
|
- `versapad_combined.py` — Ein-Datei-Format (alle 3 Profile + Makros +
|
||||||
**nur lokal gespeicherte** Profilnamen), Default-Pfad
|
**nur lokal gespeicherte** Profilnamen), Default-Pfad
|
||||||
`~\OneDrive\Desktop\versapad_config_all.json`. Passt zum Wire-Protokoll:
|
`versapad_data.app_dir()\versapad_config_all.json` (neben der
|
||||||
`CONFIG_BEGIN/COMMIT` überträgt ohnehin immer den kompletten 740B-Block.
|
Installation, nicht mehr hartkodiert auf einer bestimmten Maschine).
|
||||||
|
Passt zum Wire-Protokoll: `CONFIG_BEGIN/COMMIT` überträgt ohnehin immer
|
||||||
|
den kompletten 740B-Block. `load_or_fetch()` legt bei fehlender Datei
|
||||||
|
automatisch eine neue an — erst Versuch per Serial vom Board, sonst als
|
||||||
|
leere `default_combined()` (kein Board noetig fuer die Erstbenutzung).
|
||||||
|
`read_profile_names()` liest nur die Namen (kein Board-Zugriff, fuer
|
||||||
|
Tab-Beschriftungen im Nur-Lese-Modus, siehe Installierbarkeit-Notiz).
|
||||||
|
|
||||||
**UI:**
|
**UI:**
|
||||||
- `desktop_viewer.py` — Tkinter, flaches Design, drei unabhängige Modi
|
- `desktop_viewer.py` — Tkinter, flaches Design, drei unabhängige Modi
|
||||||
|
|
@ -93,6 +105,34 @@ Dokumentation und Verifikation unten für die Größeneinschätzung).
|
||||||
vorhanden, und fällt sonst auf die klassischen `versapad_config{1,2,3}.json`
|
vorhanden, und fällt sonst auf die klassischen `versapad_config{1,2,3}.json`
|
||||||
zurück — beide Ansichten müssen dieselbe Quelle zeigen, sonst wirkt eine
|
zurück — beide Ansichten müssen dieselbe Quelle zeigen, sonst wirkt eine
|
||||||
Bearbeitung "verschwunden".
|
Bearbeitung "verschwunden".
|
||||||
|
- **Installierbarkeit verbessert 2026-08-14:** Drei Probleme beim
|
||||||
|
Weitergeben an andere Leute behoben. (1) `build_and_deploy.ps1` starb bei
|
||||||
|
fehlenden Paketen (pyinstaller/pystray/pillow) kommentarlos, v.a. bei
|
||||||
|
Doppelklick im Explorer, weil das Fenster sich sofort schließt. Fix:
|
||||||
|
`requirements.txt` (alle Pakete an einer Stelle, README und Skript nutzen
|
||||||
|
dieselbe Datei), Skript installiert sie selbst per `pip install -r`,
|
||||||
|
läuft komplett in try/catch, prüft `$LASTEXITCODE` nach jedem nativen
|
||||||
|
Aufruf, und pausiert am Ende (Erfolg wie Fehler) auf Tastendruck --
|
||||||
|
`-NoPause` für CI/Automation. (2) `versapad_combined.DEFAULT_PATH` war
|
||||||
|
hartkodiert auf `~\OneDrive\Desktop` einer bestimmten Maschine -- für
|
||||||
|
andere Nutzer unbrauchbar. Fix: `versapad_data.app_dir()` (neue
|
||||||
|
Funktion) liefert bei der gebauten `.exe` deren Installationsordner
|
||||||
|
(`sys.executable`-Verzeichnis), sonst den Projektordner (`__file__`-
|
||||||
|
Verzeichnis) -- `DEFAULT_PATH` hängt jetzt daran, landet also immer neben
|
||||||
|
der laufenden Installation. `versapad_data.CONFIG_PATHS` (Lese-Interop
|
||||||
|
mit der C#-VersaGUI) bleibt bewusst auf dem Desktop, siehe oben. (3) Fehlte
|
||||||
|
die Config UND war kein Board erreichbar, blockierte das Tool mit einer
|
||||||
|
Fehlermeldung statt zu starten. Fix: `load_or_fetch()` legt jetzt bei
|
||||||
|
Board-Fehlschlag eine leere `default_combined()` an statt `RuntimeError`
|
||||||
|
zu werfen -- Erstbenutzung ganz ohne vorhandene Config oder Board
|
||||||
|
funktioniert jetzt. (4) Profilnamen waren zusätzlich hartkodiert in
|
||||||
|
`versapad_data.PROFILE_NAMES` (Dict, jetzt entfernt, ersetzt durch
|
||||||
|
`NUM_PROFILES = 3`) und liefen der JSON-`profile_names` parallel --
|
||||||
|
Programmiermodus zeigte umbenannte Profile, Lesemodus/Browser-Ansicht
|
||||||
|
weiterhin die alten Namen. Fix: `server.py` und `desktop_viewer.py` lesen
|
||||||
|
Namen jetzt immer aus der kombinierten JSON (`combined["profile_names"]`
|
||||||
|
bzw. `versapad_combined.read_profile_names()`), eine einzige Quelle der
|
||||||
|
Wahrheit für alle Frontends.
|
||||||
- **Bug behoben 2026-08-08:** `server.py` (`vp.load_profile()`) und
|
- **Bug behoben 2026-08-08:** `server.py` (`vp.load_profile()`) und
|
||||||
`desktop_viewer.py` (`_current_profile_view()`) lasen im Nur-Lese-Modus
|
`desktop_viewer.py` (`_current_profile_view()`) lasen im Nur-Lese-Modus
|
||||||
hart von den Desktop-JSONs -- fehlten sie (z.B. User loescht sie), gab es
|
hart von den Desktop-JSONs -- fehlten sie (z.B. User loescht sie), gab es
|
||||||
|
|
|
||||||
32
README.md
32
README.md
|
|
@ -11,10 +11,14 @@ Board-Schreibzugriff) und der MCP-Server sind funktionsfähig und gegen ein
|
||||||
echtes Board getestet (Read-Modify-Write ist byte-identisch zum Original,
|
echtes Board getestet (Read-Modify-Write ist byte-identisch zum Original,
|
||||||
inklusive CRC). Nicht vorhanden: automatisierte Tests (Verifikation läuft
|
inklusive CRC). Nicht vorhanden: automatisierte Tests (Verifikation läuft
|
||||||
manuell gegen ein angeschlossenes Board), eine vorgefertigte `.exe` zum
|
manuell gegen ein angeschlossenes Board), eine vorgefertigte `.exe` zum
|
||||||
Download (siehe unten, warum), und Mehrbenutzer-/Netzwerkbetrieb. Die
|
Download (siehe unten, warum), und Mehrbenutzer-/Netzwerkbetrieb. Die eigene
|
||||||
Standard-Dateipfade für die Config-JSONs sind aktuell für eine bestimmte
|
Config-Datei (`versapad_config_all.json`) liegt automatisch neben der
|
||||||
Windows-Maschine hartkodiert (`versapad_data.CONFIG_PATHS`,
|
Installation (siehe „Aufbau" unten) und wird bei Bedarf automatisch neu
|
||||||
`versapad_combined.DEFAULT_PATH`) — für einen anderen Rechner dort anpassen.
|
angelegt — kein manuelles Pfad-Anpassen mehr nötig. Nur die *optionale*
|
||||||
|
Lese-Interop mit den JSON-Exports der offiziellen VersaGUI
|
||||||
|
(`versapad_data.CONFIG_PATHS`) ist noch für eine bestimmte Windows-Maschine
|
||||||
|
hartkodiert (OneDrive-Desktop) — für einen anderen Rechner dort anpassen,
|
||||||
|
falls gewünscht.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
@ -102,11 +106,21 @@ Das Binärformat (`versapad_protocol.py`) ist 1:1 aus den
|
||||||
VersaMCU-Firmware-Quellen übernommen und gegen ein echtes Board validiert
|
VersaMCU-Firmware-Quellen übernommen und gegen ein echtes Board validiert
|
||||||
(Read → unpack → pack ist bytegenau identisch zum Original, inklusive CRC).
|
(Read → unpack → pack ist bytegenau identisch zum Original, inklusive CRC).
|
||||||
|
|
||||||
Config-Dateien liegen standardmäßig auf dem Desktop
|
Die eigene Config-Datei (`versapad_config_all.json` — alle 3 Profile +
|
||||||
(`versapad_config1/2/3.json` für den reinen Lesemodus,
|
Makros + lokale Profilnamen, siehe `versapad_combined.py`) liegt neben der
|
||||||
`versapad_config_all.json` für den Programmiermodus — Pfade sind aktuell
|
Installation: bei der gebauten `.exe` im selben Ordner, beim Start aus dem
|
||||||
hartkodiert für eine bestimmte Windows-Maschine, siehe `CONFIG_PATHS` in
|
Quellcode im Projektordner (`versapad_data.app_dir()`). Fehlt sie (z.B.
|
||||||
`versapad_data.py` bzw. `DEFAULT_PATH` in `versapad_combined.py`).
|
frische Installation), wird sie automatisch angelegt — per Serial vom
|
||||||
|
Board, falls eins angeschlossen ist, sonst als leere Default-Config.
|
||||||
|
Profilnamen (`profile_names`) stehen direkt in dieser Datei und lassen sich
|
||||||
|
per Doppelklick auf einen Tab (Programmiermodus) oder `rename_profile()`
|
||||||
|
(MCP) ändern.
|
||||||
|
|
||||||
|
Die klassischen `versapad_config1/2/3.json` sind kein von diesem Tool
|
||||||
|
geschriebenes Format, sondern optionale Lese-Interop mit einem JSON-Export
|
||||||
|
der offiziellen VersaGUI (C#/.NET) — Pfad aktuell hartkodiert auf den
|
||||||
|
OneDrive-Desktop einer bestimmten Windows-Maschine, siehe `CONFIG_PATHS` in
|
||||||
|
`versapad_data.py`.
|
||||||
|
|
||||||
## MCP-Server
|
## MCP-Server
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,8 +167,8 @@ class VersaPadViewer(tk.Tk):
|
||||||
self.tabs = tk.Frame(self, bg=BG)
|
self.tabs = tk.Frame(self, bg=BG)
|
||||||
self.tabs.pack(fill="x", padx=20, pady=(12, 10))
|
self.tabs.pack(fill="x", padx=20, pady=(12, 10))
|
||||||
self.tab_buttons = {}
|
self.tab_buttons = {}
|
||||||
for p in sorted(vp.PROFILE_NAMES):
|
for p in range(vp.NUM_PROFILES):
|
||||||
btn = tk.Label(self.tabs, text=vp.PROFILE_NAMES[p], bg=CARD_BG, fg=TEXT,
|
btn = tk.Label(self.tabs, text=f"Profil {p}", bg=CARD_BG, fg=TEXT,
|
||||||
font=("Segoe UI", 10, "bold"), padx=14, pady=6, cursor="hand2")
|
font=("Segoe UI", 10, "bold"), padx=14, pady=6, cursor="hand2")
|
||||||
btn.pack(side="left", padx=(0, 8))
|
btn.pack(side="left", padx=(0, 8))
|
||||||
btn.bind("<Button-1>", lambda e, prof=p: self.set_profile(prof, manual=True))
|
btn.bind("<Button-1>", lambda e, prof=p: self.set_profile(prof, manual=True))
|
||||||
|
|
@ -242,11 +242,16 @@ class VersaPadViewer(tk.Tk):
|
||||||
self._render()
|
self._render()
|
||||||
|
|
||||||
def _update_tab_labels(self):
|
def _update_tab_labels(self):
|
||||||
for p, btn in self.tab_buttons.items():
|
"""Profilnamen kommen immer aus der kombinierten JSON (nicht mehr
|
||||||
|
hartkodiert) -- im Programmiermodus aus dem In-Memory-State, sonst
|
||||||
|
per schlankem Datei-Read (kein Board-Zugriff, siehe
|
||||||
|
versapad_combined.profile_names())."""
|
||||||
if self.editing.get() and self.combined:
|
if self.editing.get() and self.combined:
|
||||||
btn.configure(text=self.combined["profile_names"][p])
|
names = self.combined["profile_names"]
|
||||||
else:
|
else:
|
||||||
btn.configure(text=vp.PROFILE_NAMES[p])
|
names = vcomb.read_profile_names()
|
||||||
|
for p, btn in self.tab_buttons.items():
|
||||||
|
btn.configure(text=names[p])
|
||||||
|
|
||||||
def _rename_tab(self, profile):
|
def _rename_tab(self, profile):
|
||||||
if not self.editing.get() or self.combined is None:
|
if not self.editing.get() or self.combined is None:
|
||||||
|
|
@ -526,16 +531,19 @@ class VersaPadViewer(tk.Tk):
|
||||||
Bevorzugt die kombinierte Datei (versapad_config_all.json), falls
|
Bevorzugt die kombinierte Datei (versapad_config_all.json), falls
|
||||||
vorhanden -- so zeigen im Programmiermodus gespeicherte Aenderungen
|
vorhanden -- so zeigen im Programmiermodus gespeicherte Aenderungen
|
||||||
sich auch hier, statt dass die alten Einzel-JSONs weiter durchscheinen.
|
sich auch hier, statt dass die alten Einzel-JSONs weiter durchscheinen.
|
||||||
Fehlt sie (z.B. versehentlich geloescht) und ist Live-Sync gerade aus
|
Fehlt sie (z.B. versehentlich geloescht, oder frische Installation
|
||||||
(COM-Port frei), wird sie automatisch per Serial vom Board neu
|
ganz ohne Config) und ist Live-Sync gerade aus (COM-Port frei), wird
|
||||||
aufgebaut und als neuer Cache gespeichert -- das Board ist die
|
sie automatisch angelegt -- per Serial vom Board, oder als leere
|
||||||
eigentliche Quelle der Wahrheit, kein Datei-Handling von Hand mehr
|
Default-Config, wenn auch kein Board erreichbar ist (siehe
|
||||||
noetig. Nur wenn das nicht klappt (Board nicht erreichbar, Live-Sync
|
versapad_combined.load_or_fetch()) -- das Tool ist damit auch ganz
|
||||||
haelt den Port), weicht es zuletzt auf die klassischen
|
ohne vorhandene Config sofort benutzbar, kein Datei-Handling von
|
||||||
versapad_config{1,2,3}.json aus."""
|
Hand mehr noetig. Nur wenn Live-Sync gerade an ist (haelt den
|
||||||
if os.path.exists(vcomb.DEFAULT_PATH):
|
COM-Port) und noch keine Datei existiert, weicht es zuletzt auf die
|
||||||
|
klassischen versapad_config{1,2,3}.json (Export der offiziellen
|
||||||
|
VersaGUI) aus."""
|
||||||
|
if os.path.exists(vcomb.DEFAULT_PATH) or not self.live_sync.get():
|
||||||
try:
|
try:
|
||||||
data = vcomb.load_file(vcomb.DEFAULT_PATH)
|
data = vcomb.load_or_fetch(link=self._link)
|
||||||
raw = data["profiles"][self.profile]
|
raw = data["profiles"][self.profile]
|
||||||
cfg = vp.annotate_profile({
|
cfg = vp.annotate_profile({
|
||||||
"buttons": [dict(b) for b in raw["buttons"]],
|
"buttons": [dict(b) for b in raw["buttons"]],
|
||||||
|
|
@ -544,18 +552,6 @@ class VersaPadViewer(tk.Tk):
|
||||||
return cfg, f"Quelle: {vcomb.DEFAULT_PATH}"
|
return cfg, f"Quelle: {vcomb.DEFAULT_PATH}"
|
||||||
except (KeyError, IndexError, ValueError):
|
except (KeyError, IndexError, ValueError):
|
||||||
pass # kaputte/unvollstaendige Datei -- weiter unten ausweichen
|
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:
|
try:
|
||||||
return vp.load_profile(self.profile), f"Quelle: {vp.CONFIG_PATHS[self.profile]}"
|
return vp.load_profile(self.profile), f"Quelle: {vp.CONFIG_PATHS[self.profile]}"
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
|
|
@ -564,6 +560,7 @@ class VersaPadViewer(tk.Tk):
|
||||||
raise FileNotFoundError(f"{e}{hint}") from e
|
raise FileNotFoundError(f"{e}{hint}") from e
|
||||||
|
|
||||||
def _render(self):
|
def _render(self):
|
||||||
|
self._update_tab_labels()
|
||||||
for w in self.grid_frame.winfo_children():
|
for w in self.grid_frame.winfo_children():
|
||||||
w.destroy()
|
w.destroy()
|
||||||
for w in self.enc_frame.winfo_children():
|
for w in self.enc_frame.winfo_children():
|
||||||
|
|
|
||||||
|
|
@ -98,8 +98,8 @@ def render_page(profile):
|
||||||
"encoders": [dict(e) for e in raw["encoders"]],
|
"encoders": [dict(e) for e in raw["encoders"]],
|
||||||
})
|
})
|
||||||
tabs = "".join(
|
tabs = "".join(
|
||||||
f'<a class="tab {"active" if p == profile else ""}" href="/?profile={p}">{html.escape(vp.PROFILE_NAMES[p])}</a>'
|
f'<a class="tab {"active" if p == profile else ""}" href="/?profile={p}">{html.escape(combined["profile_names"][p])}</a>'
|
||||||
for p in sorted(vp.PROFILE_NAMES)
|
for p in range(vp.NUM_PROFILES)
|
||||||
)
|
)
|
||||||
cells = "".join(render_cell(b) for b in cfg["buttons"])
|
cells = "".join(render_cell(b) for b in cfg["buttons"])
|
||||||
encoders = "".join(render_encoder(e) for e in cfg["encoders"])
|
encoders = "".join(render_encoder(e) for e in cfg["encoders"])
|
||||||
|
|
@ -130,7 +130,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||||
profile = int(query.get("profile", ["0"])[0])
|
profile = int(query.get("profile", ["0"])[0])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
profile = 0
|
profile = 0
|
||||||
if profile not in vp.PROFILE_NAMES:
|
if not (0 <= profile < vp.NUM_PROFILES):
|
||||||
profile = 0
|
profile = 0
|
||||||
try:
|
try:
|
||||||
body = render_page(profile).encode("utf-8")
|
body = render_page(profile).encode("utf-8")
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import versapad_data as vp
|
||||||
import versapad_protocol as proto
|
import versapad_protocol as proto
|
||||||
import versapad_serial as vs
|
import versapad_serial as vs
|
||||||
|
|
||||||
DEFAULT_PATH = os.path.expanduser(r"~\OneDrive\Desktop\versapad_config_all.json")
|
DEFAULT_PATH = os.path.join(vp.app_dir(), "versapad_config_all.json")
|
||||||
|
|
||||||
DEFAULT_NAMES = ["Windows", "Fusion 360", "BricsCAD"]
|
DEFAULT_NAMES = ["Windows", "Fusion 360", "BricsCAD"]
|
||||||
|
|
||||||
|
|
@ -126,18 +126,37 @@ def fetch_from_board(link=None, profile_names=None):
|
||||||
|
|
||||||
def load_or_fetch(path=DEFAULT_PATH, link=None, profile_names=None):
|
def load_or_fetch(path=DEFAULT_PATH, link=None, profile_names=None):
|
||||||
"""Bevorzugt die lokale Kombi-Datei. Fehlt sie (z.B. versehentlich
|
"""Bevorzugt die lokale Kombi-Datei. Fehlt sie (z.B. versehentlich
|
||||||
geloescht), wird sie automatisch per Serial vom Board neu aufgebaut und
|
geloescht, oder frische Installation ohne jede Config), wird sie
|
||||||
als neuer Cache gespeichert, statt einen Fehler zu werfen -- das Board
|
automatisch neu angelegt -- zuerst per Serial-Versuch vom Board (das
|
||||||
behaelt die Config dauerhaft im NVM, die Desktop-JSON ist nur ein
|
behaelt die Config dauerhaft im NVM, die JSON ist nur ein Lesecache
|
||||||
Lesecache dafuer und muss nicht von Hand gepflegt werden. Ist das Board
|
dafuer), und falls auch das Board nicht erreichbar ist (nicht
|
||||||
nicht erreichbar (nicht verbunden, COM-Port belegt), wirft es
|
verbunden, COM-Port belegt, frisch installiert ohne Board in Reichweite)
|
||||||
RuntimeError mit Klartext-Ursache -- Aufrufer entscheidet, ob es einen
|
als leere Default-Config (vgl. default_combined()) -- damit ist das
|
||||||
weiteren Fallback gibt (z.B. alte Einzel-JSONs)."""
|
Tool auch ganz ohne vorhandene Config sofort benutzbar, statt mit
|
||||||
|
einem Fehler zu blockieren."""
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
return load_file(path)
|
return load_file(path)
|
||||||
|
try:
|
||||||
combined = fetch_from_board(link=link, profile_names=profile_names)
|
combined = fetch_from_board(link=link, profile_names=profile_names)
|
||||||
|
except RuntimeError:
|
||||||
|
combined = default_combined()
|
||||||
|
if profile_names:
|
||||||
|
combined["profile_names"] = profile_names
|
||||||
try:
|
try:
|
||||||
save_file(combined, path)
|
save_file(combined, path)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass # Board-Daten trotzdem verwertbar, nur der Cache konnte nicht geschrieben werden
|
pass # Daten trotzdem verwertbar, nur der Cache konnte nicht geschrieben werden
|
||||||
return combined
|
return combined
|
||||||
|
|
||||||
|
|
||||||
|
def read_profile_names(path=DEFAULT_PATH):
|
||||||
|
"""Nur die (lokalen) Profilnamen lesen, ohne die volle Config zu
|
||||||
|
brauchen -- fuer Tab-Beschriftungen im Nur-Lese-Modus. Greift bewusst
|
||||||
|
nicht aufs Board zu (kein COM-Port-Konflikt mit Live-Sync), faellt bei
|
||||||
|
fehlender/kaputter Datei auf DEFAULT_NAMES zurueck."""
|
||||||
|
if os.path.exists(path):
|
||||||
|
try:
|
||||||
|
return load_file(path).get("profile_names", list(DEFAULT_NAMES))
|
||||||
|
except (OSError, ValueError):
|
||||||
|
pass
|
||||||
|
return list(DEFAULT_NAMES)
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,35 @@ Kein Schreibzugriff auf die JSONs -- reines Lesen/Anzeigen.
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
NUM_PROFILES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def app_dir():
|
||||||
|
"""Verzeichnis fuer die eigene Config-Datei (versapad_config_all.json,
|
||||||
|
siehe versapad_combined.DEFAULT_PATH): bei der gebauten .exe (--onedir)
|
||||||
|
das Installationsverzeichnis neben der .exe, sonst der Ordner dieses
|
||||||
|
Moduls (Projektordner beim Start aus dem Quellcode). Kein hartkodierter
|
||||||
|
Pfad mehr -- so laesst sich das Tool auf jede Maschine kopieren/
|
||||||
|
installieren, ohne Pfade von Hand anzupassen."""
|
||||||
|
if getattr(sys, "frozen", False):
|
||||||
|
return os.path.dirname(sys.executable)
|
||||||
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
# CONFIG_PATHS zeigt bewusst weiterhin auf den OneDrive-Desktop -- das sind
|
||||||
|
# keine von diesem Tool geschriebenen Dateien, sondern ein Export der
|
||||||
|
# offiziellen (C#/.NET-)VersaGUI auf dieser einen Maschine (reine Lese-
|
||||||
|
# Interop, siehe README "Bekannte Einschraenkungen"). Fuer das eigentliche,
|
||||||
|
# von diesem Tool selbst gepflegte Format siehe versapad_combined.DEFAULT_PATH
|
||||||
|
# (liegt jetzt in app_dir(), nicht mehr hartkodiert auf dem Desktop).
|
||||||
CONFIG_PATHS = {
|
CONFIG_PATHS = {
|
||||||
0: os.path.expanduser(r"~\OneDrive\Desktop\versapad_config1.json"),
|
0: os.path.expanduser(r"~\OneDrive\Desktop\versapad_config1.json"),
|
||||||
1: os.path.expanduser(r"~\OneDrive\Desktop\versapad_config2.json"),
|
1: os.path.expanduser(r"~\OneDrive\Desktop\versapad_config2.json"),
|
||||||
2: os.path.expanduser(r"~\OneDrive\Desktop\versapad_config3.json"),
|
2: os.path.expanduser(r"~\OneDrive\Desktop\versapad_config3.json"),
|
||||||
}
|
}
|
||||||
|
|
||||||
PROFILE_NAMES = {
|
|
||||||
0: "Profil 0 – Windows",
|
|
||||||
1: "Profil 1 – Fusion 360",
|
|
||||||
2: "Profil 2 – BricsCAD",
|
|
||||||
}
|
|
||||||
|
|
||||||
# index = spalte*5 + reihe, Reihe 0 = oben, Reihe 4 = unten (VersaMCU-Firmware-Reihenfolge)
|
# index = spalte*5 + reihe, Reihe 0 = oben, Reihe 4 = unten (VersaMCU-Firmware-Reihenfolge)
|
||||||
GRID_COLS = 4
|
GRID_COLS = 4
|
||||||
GRID_ROWS = 5
|
GRID_ROWS = 5
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue