VersaGUI-py/AGENTS.md
cjjohn 553ecc54d8 Restructure AGENTS.md to the projekt-doku standard, audit README status
AGENTS.md now follows the mandated section structure (Project Goal,
Aktuell unterstützte Architektur, Kritische Domänenregeln,
Existing-Codebase-Regel, Implementierungsdisziplin, Naming, Deferred
Work, Dokumentation und Verifikation) instead of an organically grown
set of headings. Folds in all prior technical notes (protocol
validation, network-drive build/runtime gotchas, threading rules)
under the appropriate section, and fixes a stale "no tray icon yet"
line that no longer matched the code. README gets an explicit honest
status paragraph (what's tested, what's not: no automated tests, no
prebuilt exe download, hardcoded paths).

No docs/ tree: project has no database or persistent multi-user
service, so the full doc structure isn't warranted per the standard.
2026-08-06 16:05:36 +02:00

10 KiB
Raw Permalink Blame History

AGENTS.md

Project Goal

Eigenständiges Begleit-Tool für das VersaPad-Makropad (Grovy311-Kontext, nicht Teil der fremden jappel-Repos VersaGUI/VersaMCU). Zeigt die Steuermatrix (4×5-Button-Grid + 4 Encoder) an, synchronisiert sie live mit dem Board und kann sie komplett neu programmieren — inzwischen ein schlankes Python-Pendant zur offiziellen VersaGUI (C#/.NET), plus ein MCP-Server, der dieselbe Programmierung per KI-Tool-Aufruf ermöglicht. Nutzerorientierte Einführung: README.md.

Aktuell unterstützte Architektur

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

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

Binär-/Serial-Schicht (Programmiermodus + MCP):

  • versapad_protocol.py — pack/unpack für SDeviceConfig (740B, alle 3 Profile) und SMacroTable (512B, 32 Slots) + CRC16 (Poly 0x1021, Init 0xFFFF), 1:1 aus den Firmware-Structs. Gegen echtes Board validiert: read → unpack → pack ist byte-identisch zum Original, inkl. CRC.
  • versapad_serial.pyVersaPadLink: liest/schreibt komplette Config + Makros per 8-Byte-Paket-Protokoll, Board-Identifikation per VID/PID 239A:0042. Schreiben ist sicher im Sinne von "kann NVM nicht zerlegen" — Firmware prüft Magic/CRC/Keycode-Bereich vor jedem Save, antwortet sonst nur mit NACK.
  • versapad_combined.py — Ein-Datei-Format (alle 3 Profile + Makros + nur lokal gespeicherte Profilnamen), Default-Pfad ~\OneDrive\Desktop\versapad_config_all.json. Passt zum Wire-Protokoll: CONFIG_BEGIN/COMMIT überträgt ohnehin immer den kompletten 740B-Block.

UI:

  • desktop_viewer.py — Tkinter, flaches Design, drei unabhängige Modi (Nur lesen / Live-Sync / Programmiermodus, siehe Kritische Domänenregeln), Tray-Icon statt Taskleisten-Minimierung, Info-Button mit MCP-Doku.
  • action_dialog.py — Bearbeiten-Dialoge (ActionEditDialog, MacroStepsDialog) für den Programmiermodus.

MCP-Server:

  • versapad_mcp_server.py — registriert als User-Scope-MCP-Server "versapad" (claude mcp add -s user versapad -- <python> versapad_mcp_server.py). Nutzt MCP-SDK v2 (Paket mcp, Klasse mcp.server.mcpserver.MCPServernicht FastMCP aus mcp.server.fastmcp, das existiert in dieser SDK-Version nicht mehr, wurde umbenannt). @mcp.tool()-dekorierte Funktionen bleiben direkt aufrufbar (kein .fn-Unterschied wie bei älteren FastMCP-Versionen). Tool-Liste: siehe README oder MCP_INFO_TEXT in desktop_viewer.py.

Vollständige Modulübersicht mit Zeilenreferenzen bei Bedarf direkt im Code nachschlagen — die Dateien sind klein genug, dass eine separate docs/current-architecture.md hier keinen Mehrwert hätte (siehe Dokumentation und Verifikation unten für die Größeneinschätzung).

Kritische Domänenregeln

  • Geometrie: index = spalte*5 + reihe, Reihe 0 = oben, Reihe 4 = unten (Firmware-Reihenfolge). Nicht raten, nicht neu herleiten.
  • Encoder 0 sw ist auf allen 3 Profilen der Profilwechsel — nicht ohne Rücksprache mit dem User ändern.
  • Makros sind eine EINE globale 32-Slot-Tabelle, nicht pro Profil. Slot 0-19 = MX-Button-Index, Slot 20-31 = 20 + enc*3 + act_idx. Zwei Profile, die denselben Slot referenzieren, spielen dieselben Schritte ab.
  • Ein Makro-Schritt mit keycode=0 beendet die Sequenz (Firmware-Konvention) — keine Lücken vor dem letzten belegten Schritt lassen.
  • Makro-Schritte erlauben nur Strg/Shift/Alt als Modifier, kein Win (passend zum Original-ActionDialog.cs-Verhalten).
  • Profilnamen leben NUR lokal in versapad_config_all.json (profile_names) — die Firmware-Structs haben keinen Platz für einen String (Header exakt 32B, jedes Profil exakt 236B, alles verplant). Sie landen nie aufs Board, egal welcher Schreibpfad benutzt wird.
  • Der COM-Port ist exklusiv. Live-Sync und Programmiermodus schalten sich gegenseitig aus (ein VersaPadLink kann nicht von zwei Konsumenten gleichzeitig genutzt werden); VersaGUI läuft als Tray-App dauerhaft im Hintergrund weiter, auch wenn nur ihr Konfigurationsfenster geschlossen wird — für Parallelbetrieb mit diesem Tool muss sie über ihr eigenes Tray-Menü ("Beenden") wirklich beendet werden.
  • Programmiermodus-Änderungen werden bei jeder Bearbeitung automatisch in versapad_config_all.json gespeichert (nicht erst bei explizitem "Datei speichern"). Der Nur-Lese-Modus bevorzugt dieselbe Datei, falls 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 Bearbeitung "verschwunden".
  • HID-Tasten-Auswahl im Programmiermodus ist ein Dropdown, kein Tastendruck-Capture (bewusst — kein WinAPI-Hook, um keinen AV-Fehlalarm wie bei den Fensterverstecktricks in anderen Projekten zu riskieren).
  • Zeichentasten-Labels zeigen eine US-Layout-Näherung, nicht das tatsächlich aktive Windows-Tastaturlayout (die echte VersaGUI löst das über GetKeyNameText(), das bilden wir ohne WinAPI-Call nicht nach).
  • Tk-Aufrufe (self.after(), Widget-Konfiguration) NIE direkt aus einem Fremdthread (Serial-Thread, pystray-Thread) — hat in einer früheren Version einen stillen Absturz verursacht. Threads legen Ergebnisse nur in eine queue.Queue, der Main-Thread holt sie per after()-Polling ab.
  • .exe (PyInstaller) läuft NICHT vom Netzlaufwerk aus (Z:\Git\..., SMB-Share) — Windows blockiert das Nachladen der _internal-DLLs von einem Netzwerkpfad ohne jede Fehlermeldung. Bauen scheitert dort zusätzlich an Tcl/Tk-tzdata-Pfadlängen. build_and_deploy.ps1 kopiert den Quellcode deshalb zuerst nach %TEMP%, baut nur dort, deployt nach %LOCALAPPDATA%.
  • --onedir, nicht --onefile beim PyInstaller-Build — Single-File-Bundles lösen öfter AV-Fehlalarme aus (sehen strukturell wie ein Packer aus).

Existing-Codebase-Regel

Es gibt bereits eine Codebasis mit validierter, gegen die echte Firmware getesteter Logik (Binärformat, CRC, Protokoll). Nicht blind neu raten oder umschreiben. Vor größeren Änderungen am Binärformat/Protokoll immer gegen die Firmware- und GUI-Quellen abgleichen, nicht aus dem Gedächtnis rekonstruieren:

  • VersaGUI/src/ActionDialog.cs (HidKeyName, s_consumer, Modifier-Bits)
  • VersaGUI/src/Protocol.cs (Paket-IDs, Chunk-Größen)
  • 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 tatsächlich behandelt — Protocol.cs definiert mehr Konstanten als zwingend implementiert sind, immer hier gegenchecken)

Frühere falsche Annahme, die hier stand: Makros seien nur per SWD/JTAG schreibbar. Falsch — USB_CMD_MACRO_BEGIN/DATA/COMMIT in CMainController.cpp sind normal über USB implementiert. Korrigiert, auch im versapad-Skill (~/.claude/skills/versapad/SKILL.md).

Implementierungsdisziplin

In Phasen arbeiten, nicht mehrere unabhängige Features in einem Rutsch ohne Zwischenverifikation bauen. Vor jeder Änderung am Binärformat/Protokoll: gegen ein echtes Board testen (read → unpack → pack → Vergleich auf Byte-Identität), nicht nur gegen Beispieldaten. Vor jedem Schreibvorgang aufs Board: erst mit unveränderten Daten testen (Identity-Write), bevor echte Änderungen geschrieben werden.

Naming

Code-Identifier englisch (Python-Konvention: pack_config, read_macros, ActionEditDialog). UI-Text, Kommentare und Docstrings deutsch (Projekt- und User-Konvention). Domänenbegriffe aus der Firmware direkt übernehmen, nicht neu erfinden: profile, action, led, slot, macro, encoder, chunk. Keine generischen Namen wie data/item/entry, wo eine Bedeutung existiert — Ausnahme: data als Feldname ist durch die Firmware selbst vorgegeben (SAction.data), dort beibehalten statt umzubenennen.

Deferred Work

  • Volle docs/-Baumstruktur (siehe Dokumentation und Verifikation unten — Projektgröße rechtfertigt das aktuell nicht, kein DB-/API-Dienst)
  • Board-seitiges Umschalten des aktiven Profils per Button in der GUI — explizit vom User abgelehnt ("lass uns weg"), Live-Sync bleibt read-only
  • Profilnamen aufs Board schreiben — technisch unmöglich (kein Platz im Firmware-Struct), bleibt lokal
  • Tastendruck-Capture statt Dropdown im Programmiermodus — bewusst vermieden (WinAPI-Hook-Risiko)
  • Hintergrund-Thread für "Vom Board laden"/"Zum Board übertragen" — laufen aktuell synchron im UI-Thread (kurzzeitiges Einfrieren möglich)
  • Vorgefertigte .exe im Repo/als Release-Asset — bewusst nicht committet (Build-Artefakt), Anleitung zum Selbstbauen steht in README.md

Nicht an diesen Punkten arbeiten, ohne dass der User es explizit anfragt.

Dokumentation und Verifikation

  • README.md ist der Einstiegspunkt (Installation, Nutzung, Architektur- Überblick).
  • AGENTS.md (diese Datei) ist die agentenseitige Quelle der Wahrheit für Domänenregeln und Architekturgrenzen — jede Session aktualisieren, die daran etwas ändert oder etwas Wichtiges lernt.
  • Größeneinschätzung nach Projekt-Dokumentationsstandard: kleines/mittleres Tool ohne eigene Datenbank und ohne persistenten API-Dienst (der Browser-Server ist ein einfacher lokaler Lese-Viewer, kein Mehrbenutzer-Backend) → README.md + AGENTS.md sind Pflicht und vorhanden, ein voller docs/-Baum ist nicht angemessen.

Prüfungen vor einem Commit an Binärformat/Protokoll:

python -m py_compile *.py

Danach ein Live-Testskript gegen ein angeschlossenes Board laufen lassen (read → unpack → pack → Bytevergleich, siehe Existing-Codebase-Regel) — es gibt keine automatisierten Unit-Tests dafür, die Verifikation läuft manuell/interaktiv pro Session.

Committen: prägnante Commit-Message je abgeschlossenem, verifiziertem Arbeitspaket. Nach jedem Push: alle bekannten Remotes prüfen (origin auf GitHub, jappel auf git.jappel.io) — beide müssen synchron bleiben, siehe Speicher-Notiz "Multi-Remote-Repos synchron halten".