VersaGUI-py/AGENTS.md
cjjohn 50658107d5 Add PyInstaller packaging (--onedir) as alternative to pyw launch
Avoids the cmd.exe flash from double-clicking run_desktop.bat and
gives a proper double-click .exe like VersaGUI.exe. Must be deployed
locally (%LOCALAPPDATA%) -- Windows silently refuses to load the
_internal DLLs when the .exe sits on the Z: network share, no error
shown. build_and_deploy.ps1 handles build + local copy in one step.
2026-08-05 08:04:38 +02:00

8.5 KiB

versapad-viewer

Eigenes kleines Tool (Grovy311-Kontext, nicht Teil der fremden jappel-Repos VersaGUI/VersaMCU). Zeigt die Steuermatrix (4x5 Button-Grid + 4 Encoder) als Browser-Live-Seite oder natives Tkinter-Fenster. Inzwischen mehr als ein reiner Viewer: der Tkinter-Desktop-Viewer hat einen vollen Programmiermodus (Action-Editor, Serial-Schreibzugriff aufs Board, Makro-Editor) bekommen — faktisch ein schlankes Python-Pendant zu VersaGUI. Der Browser-Server (server.py) blieb bewusst read-only/einfach.

Architektur

Read-only-Schicht (JSON, beide Frontends):

  • versapad_data.py — Decoding fuer Anzeige: JSON laden, HID-Keycode/ Consumer-Usage/Modifier → lesbarer Text, Grid-Geometrie (index = spalte*5+reihe), hid_key_choices()/consumer_choices() fuer Dropdowns.
  • server.py — stdlib http.server, generiert HTML bei jedem Request neu, Profil-Wechsel über ?profile=0|1|2, Auto-Reload alle 4s. Rein lesend, kein Programmiermodus (bewusst einfach gehalten).

Binaer-/Serial-Schicht (fuer den Programmiermodus):

  • versapad_protocol.py — pack/unpack fuer SDeviceConfig(740B, alle 3 Profile) und SMacroTable(512B, 32 Slots) + CRC16 (Poly 0x1021, Init 0xFFFF), 1:1 aus den Firmware-Structs (nvm_config.h, action.h, macro_config.h, CButton.h-LEDAnim-Enum, nvm_config.cpp-CRC). Gegen echtes Board validiert: read → unpack → pack → byte-identisch zum Original, inkl. CRC — siehe Git-Historie dieser Datei / Sessionlog.
  • versapad_serial.pyVersaPadLink: liest/schreibt komplette Config + Makros per 8-Byte-Paket-Protokoll (CmdConfigRead/Begin/Data/Commit, CmdMacroRead/Begin/Data/Commit), Board-Identifikation per VID/PID 239A:0042. Schreiben ist sicher im Sinne von "kann NVM nicht zerlegen" — Firmware prueft Magic/CRC/Keycode-Bereich vor jedem Save und antwortet sonst nur mit NACK (kein Datenmuell moeglich).
  • versapad_combined.py — kombiniertes Ein-Datei-Format (alle 3 Profile + Makros + nur lokal gespeicherte Profilnamen) statt der 3 Einzel-JSONs. Passt besser zum Wire-Protokoll: CONFIG_BEGIN/COMMIT ueberträgt ohnehin immer den kompletten 740B-Block auf einmal, nie nur ein Profil. Default-Pfad: ~\OneDrive\Desktop\versapad_config_all.json.

UI:

  • desktop_viewer.py — Tkinter, flaches Design (Label-Flächen, keine Canvas-Formen/Anti-Aliasing). Zellen nutzen .place() mit festen Pixel-Positionen statt .pack() (mit pack sah das Grid je nach leerer/ belegter Zelle ungleichmäßig aus). Drei unabhängige Modi:
    • Nur lesen (Default): pollt die 3 JSONs alle 1.5s.
    • Live-Sync: pollt per Serial das aktive Profil vom Board, schaltet Tabs automatisch mit. Laeuft in einem Hintergrundthread, der NUR ueber eine queue.Queue mit dem Main-Thread kommuniziert (self.after() direkt aus einem Fremdthread aufrufen ist in Tkinter nicht threadsicher und hat den Prozess in einer frueheren Version lautlos abstuerzen lassen — pyw hat kein Konsolenfenster, der Crash war unsichtbar).
    • Programmiermodus: Zellen anklicken → action_dialog.py (ActionEditDialog/MacroStepsDialog) bearbeitet Action-Typ, HID-Taste (Dropdown statt Tastendruck-Capture — kein WinAPI-Hook, um nicht wieder einen AV-Fehlalarm wie bei den Fensterverstecktricks zu riskieren), Consumer, Makro-Slot+Schritte, ProfileSwitch, LED-Farbe/Animation. "Vom Board laden"/"Zum Board übertragen" nutzen die Binaer-Schicht. Schaltet Live-Sync automatisch aus (ein VersaPadLink kann nicht von zwei Konsumenten gleichzeitig genutzt werden).
  • action_dialog.py — die beiden Bearbeiten-Dialoge, importiert nur versapad_data (fuer Dropdown-Inhalte).

Quelle der Wahrheit — nicht raten

Bei Aenderungen am Schema/Decoding/Binaerformat immer gegen die Firmware- und GUI-Quellen abgleichen, nie aus dem Gedaechtnis rekonstruieren:

  • VersaGUI/src/ActionDialog.cs (HidKeyName, s_consumer, Modifier-Bits)
  • VersaGUI/src/Protocol.cs (Paket-IDs, Chunk-Groessen)
  • VersaMCU/src/config/nvm_config.h + .cpp (SDeviceConfig-Layout, CRC)
  • VersaMCU/src/config/action.h (SAction, ActionType-Enum-Werte)
  • VersaMCU/src/config/macro_config.h + .cpp (SMacroTable-Layout)
  • VersaMCU/src/CButton.h (LEDAnim-Enum-Werte)
  • VersaMCU/src/CMainController.cpp (welche Commands die Firmware tatsaechlich behandelt — Protocol.cs definiert mehr Konstanten als die Firmware zwingend implementiert, immer hier gegenchecken)

Zeichentasten (A-Z, Ziffern, Satzzeichen) zeigen US-Layout-Näherung, da die echte GUI das über GetKeyNameText() layoutabhängig auflöst — das bilden wir ohne WinAPI-Call nicht nach.

Korrektur einer frueheren Annahme: Anders als zuerst notiert, kann die Firmware Makros sehr wohl per normaler USB-Verbindung schreiben (USB_CMD_MACRO_BEGIN/DATA/COMMIT in CMainController.cpp sind implementiert) — kein SWD/JTAG noetig. Der VersaPad-Skill (~/.claude/skills/versapad/SKILL.md) behauptet noch das Gegenteil und sollte bei Gelegenheit korrigiert werden.

Start

py server.py            # Browser, http://127.0.0.1:8765
pyw desktop_viewer.py    # natives Fenster, kein Konsolenfenster

Oder run_browser.bat / run_desktop.bat per Doppelklick, oder die Desktop-Verknuepfungen "VersaPad Viewer (Browser)" / "VersaPad Viewer (Fenster)".

Wichtig: pyw/py benutzen, nicht bare pythonw/python — auf hal9001 liegt in PATH zuerst eine Hermes-Agent-venv (...\hermes\hermes-agent\venv\ Scripts\pythonw.exe) ohne pyserial. pyw/py (Python Launcher) lösen zuverlässig zur echten Python313-Installation auf.

Gepackte .exe (PyInstaller) — kein Terminal-Flash, kein Python-Setup nötig

build_and_deploy.ps1 baut desktop_viewer.py mit PyInstaller (--windowed --onedir) und kopiert das Ergebnis lokal nach %LOCALAPPDATA%\VersaPadViewer\VersaPadViewer.exe. Desktop-Verknüpfung "VersaPad Viewer (Fenster)" zeigt dorthin.

--onedir, nicht --onefile — Single-File-PyInstaller-Bundles lösen öfter AV-Fehlalarme aus (sehen strukturell wie ein Packer aus); --onedir (Ordner mit .exe + _internal) ist unauffälliger. Trotzdem unsigniert, also grundsätzlich nie über Smart App Control/AV in Stein gemeißelt sicher.

Kritischer Stolperstein: Die gebaute .exe läuft NICHT vom Netzlaufwerk aus (Z:\Git\..., SMB-Share auf cj-ki) — Windows blockiert das Nachladen der _internal-DLLs von einem Netzwerkpfad, der Prozess startet dann einfach nie, ganz ohne Fehlermeldung/Crash-Log/AV-Benachrichtigung (sah zuerst wie ein AV-Block aus, war aber keiner — sauber verifiziert indem dieselbe .exe 1:1 lokal kopiert sofort lief). Bauen darf auf Z: passieren, das fertige Bundle muss vor dem Start nach C: kopiert werden — genau das macht build_and_deploy.ps1.

Bei erneutem Bauen: .\build_and_deploy.ps1 in PowerShell ausführen (lokal, nicht über die sandboxed Bash-Tool-Umgebung — die hat eine eigene Dateisystem-Sicht, die nicht zuverlässig mit dem echten Windows-Dateisystem übereinstimmt; Kopieroperationen für Dinge, die der User/andere Prozesse sehen sollen, gehören in PowerShell, nicht in Bash).

Dateien/Pfade (hardcoded, hal9001-spezifisch)

  • Einzel-JSONs (Read-only-Modus): ~\OneDrive\Desktop\versapad_config{1,2,3}.json — Profil 0 Windows / 1 Fusion 360 / 2 BricsCAD, siehe versapad_data.CONFIG_PATHS.
  • Kombinierte Datei (Programmiermodus-Export): ~\OneDrive\Desktop\ versapad_config_all.json (Default, siehe versapad_combined.DEFAULT_PATH).

Offene Punkte

  • Kein automatischer Windows-Start/Tray-Icon — bewusst einfach gehalten, bei Bedarf nachrüstbar.
  • Live-Sync und VersaGUI (bzw. Programmiermodus) können den COM-Port nicht gleichzeitig halten (exklusiver Zugriff) — zeigt dann "Port belegt", kein Crash. VersaGUI läuft als Tray-App dauerhaft im Hintergrund weiter, auch wenn nur das Konfigurationsfenster geschlossen wird — für Programmiermodus/ Live-Sync muss es über das Tray-Icon → "Beenden" wirklich beendet werden. Live-Sync wird beim Aktivieren des Programmiermodus automatisch ausgeschaltet. Innerhalb des Programmiermodus selbst laufen "Vom Board laden"/"Zum Board übertragen" synchron im UI-Thread (kurzzeitiges Einfrieren möglich, v.a. bei Timeout) — bislang nicht als Hintergrund-Thread ausgelagert. Der Browser-Server (server.py) hat keinen Programmiermodus.
  • HID-Tasten-Auswahl im Programmiermodus ist ein Dropdown, kein Tastendruck-Capture (bewusst, um WinAPI-Hooks/AV-Fehlalarme zu vermeiden).
  • Macro-Editor erlaubt nur Strg/Shift/Alt pro Schritt (kein Win), passend zum Original-ActionDialog.cs-Verhalten fuer Makro-Steps.