Human-facing reference documentation, split from AGENTS.md's agent-facing domain rules and bug history (which stays there, not duplicated here): - docs/architecture.md: layer diagram, module responsibilities, config storage location, the three GUI modes, and the port-exclusivity / multi-process caveats around concurrent access - docs/data-model.md: the combined and legacy JSON formats, the binary SDeviceConfig/SDeviceProfile/SMacroTable NVM layout byte-for-byte, action types, LED fields, macro-slot conventions, button grid geometry - docs/protocol.md: the 8-byte serial packet format, command/event tables, the read/write/status-poll flows, connection lifecycle, and error states README.md now links to all three from a new "Dokumentation" section, and AGENTS.md's outdated "docs/ tree isn't warranted yet" note is removed now that it exists on explicit user request.
5.8 KiB
Serial-Wire-Protokoll
Beschreibt, wie versapad_serial.VersaPadLink mit dem Board spricht. 1:1
aus VersaGUI/src/Protocol.cs und VersaMCU/doc/07_serial_protocol.md
übernommen (siehe „Existing-Codebase-Regel“ in
AGENTS.md — bei Änderungen gegen die Firmware-/
C#-Quellen abgleichen, nicht aus dem Gedächtnis rekonstruieren). Für die
Bedeutung der übertragenen Bytes (Config/Makro-Layout) siehe
data-model.md.
Transport
- USB-CDC (virtueller COM-Port), 115200 Baud.
- Board-Erkennung über USB VID:PID
239A:0042(versapad_serial.find_port()durchsuchtserial.tools.list_ports.comports()). - Der Port ist exklusiv — siehe „Nebenläufigkeit“ in
architecture.md. - Jedes Paket ist exakt 8 Byte:
Byte Bedeutung [0]Command-/Event-ID [1]je nach Befehl: Chunk-Index, Chunk-Anzahl, oder ungenutzt [2..7]Payload, 6 Byte ( PAYLOAD_SIZE)
Befehle (Host → Board)
| Konstante | Wert | Zweck |
|---|---|---|
CMD_READ_STATUS |
0x06 |
Leichtgewichtiger Status-Poll (aktives Profil) |
CMD_CONFIG_BEGIN |
0x10 |
Start eines Config-Schreibvorgangs |
CMD_CONFIG_DATA |
0x11 |
Ein Config-Datenpaket |
CMD_CONFIG_COMMIT |
0x12 |
Config committen (NVM-Schreiben nach Validierung) |
CMD_CONFIG_READ |
0x13 |
Komplette Config vom Board anfordern |
CMD_MACRO_BEGIN |
0x20 |
Start eines Makro-Schreibvorgangs |
CMD_MACRO_DATA |
0x21 |
Ein Makro-Datenpaket |
CMD_MACRO_COMMIT |
0x22 |
Makros committen |
CMD_MACRO_READ |
0x23 |
Komplette Makro-Tabelle vom Board anfordern |
Events (Board → Host)
| Konstante | Wert | Zweck |
|---|---|---|
EVT_STATUS |
0x86 |
Antwort auf CMD_READ_STATUS |
EVT_CONFIG_ACK |
0x90 |
Config-Commit erfolgreich |
EVT_CONFIG_NACK |
0x91 |
Config-Commit abgelehnt (Validierung fehlgeschlagen) |
EVT_CONFIG_BEGIN |
0x92 |
Board beginnt, Config-Dump zu senden |
EVT_CONFIG_DATA |
0x93 |
Ein Config-Datenpaket (Antwort auf CMD_CONFIG_READ) |
EVT_CONFIG_END |
0x94 |
Config-Dump vollständig |
EVT_MACRO_ACK |
0x95 |
Makro-Commit erfolgreich |
EVT_MACRO_BEGIN |
0x96 |
Board beginnt, Makro-Dump zu senden |
EVT_MACRO_DATA |
0x97 |
Ein Makro-Datenpaket |
EVT_MACRO_END |
0x98 |
Makro-Dump vollständig |
EVT_MACRO_NACK |
0x99 |
Makro-Commit abgelehnt |
Ablauf: Lesen (CONFIG_READ / MACRO_READ)
Genutzt von read_full_config() (740B, ⌈740/6⌉ = 124 Datenpakete) und
read_macros() (512B, ⌈512/6⌉ = 86 Datenpakete). Deadline 3s.
Host → [CMD_CONFIG_READ, 0, 0,0,0,0,0,0]
Board → [EVT_CONFIG_BEGIN, ...]
Board → [EVT_CONFIG_DATA, chunk_idx=0, <=6B Payload]
Board → [EVT_CONFIG_DATA, chunk_idx=1, <=6B Payload]
... (124 Pakete insgesamt für Config, 86 für Makros)
Board → [EVT_CONFIG_END, ...]
chunk_idx (Byte [1]) bestimmt den Ziel-Offset im Empfangspuffer:
offset = chunk_idx * 6. Kommt EVT_END, ohne dass zuvor EVT_BEGIN
gesehen wurde, gilt das als Timeout (unvollständige/verpasste Antwort).
Ablauf: Schreiben (CONFIG_BEGIN/DATA/COMMIT bzw. MACRO_*)
Genutzt von write_full_config()/write_macros(). Deadline 5s. Anzahl
Chunks maximal 255 (ein Byte) — bei größeren Blobs schlägt der Aufruf mit
"too_large" fehl, bevor überhaupt gesendet wird.
Host → [CMD_CONFIG_BEGIN, chunk_count, 0,0,0,0,0,0]
Host → [CMD_CONFIG_DATA, 0, <=6B Payload (zero-padded)]
Host → [CMD_CONFIG_DATA, 1, <=6B Payload]
... (ein Paket je Chunk)
Host → [CMD_CONFIG_COMMIT, 0, 0,0,0,0,0,0]
Board → [EVT_CONFIG_ACK, ...] -- oder EVT_CONFIG_NACK bei fehlgeschlagener Validierung
Sicherheitsnetz: Die Firmware prüft Magic/Version/CRC (Config) bzw.
Keycode-Bereich (Makros) vor jedem NVM-Schreiben und antwortet sonst
nur mit NACK — ein fehlerhafter Schreibversuch kann das Board laut
Firmware-Design nicht in einen inkonsistenten Zustand bringen, siehe
nvm_config_validate() in den Firmware-Quellen.
Ablauf: Leichtgewichtiger Status-Poll (READ_STATUS)
Genutzt von read_active_profile() für kontinuierliches Live-Sync-Polling
(alle 1,5s). Deadline 1s.
Host → [CMD_READ_STATUS, 0, 0,0,0,0,0,0]
Board → [EVT_STATUS, active_profile (0-2), ...]
Ein einzelnes Antwortpaket statt eines vollen CONFIG_READ-Dumps (124
Pakete) — Letzterer blockiert die Firmware in poll_vendor() lang genug,
dass laufende LED-Pulse-Animationen sichtbar stottern. Ältere Firmware
ohne CMD_READ_STATUS antwortet einfach gar nicht → sauberer Timeout,
kein Absturz (siehe AGENTS.md, Eintrag „4215323“ in der Historie).
Verbindungsaufbau (VersaPadLink._ensure_open())
- Board per VID/PID finden (
find_port()). serial.Serial(port, 115200, timeout=0.5)öffnen.- DTR auf
Truesetzen, 0,2s warten (Board braucht kurz, bis es nach dem Öffnen/DTR-Toggle wieder reagiert). - Input-Buffer leeren.
Wichtig: VersaPadLink schließt die Verbindung nicht automatisch nach
einem Befehl — nur bei serial.SerialException (Fehlerfall) oder
explizitem .close()-Aufruf. Aufrufer, die den exklusiven Port nicht
dauerhaft blockieren wollen, müssen selbst schließen (siehe
versapad_mcp_server.py, das nach jedem Board-Tool-Aufruf _link.close()
in einem finally-Block aufruft).
Fehlerzustände (VersaPadLink.last_error)
| Wert | Bedeutung |
|---|---|
None |
kein Fehler |
"no_pyserial" |
Paket pyserial nicht installiert |
"not_found" |
kein Gerät mit passender VID/PID gefunden |
"busy" |
Port gefunden, aber Öffnen fehlgeschlagen (von woanders gehalten — VersaGUI, Live-Sync, ein anderer Prozess) |
"timeout" |
keine (gültige) Antwort innerhalb der Deadline |
"nack" |
Board hat den Commit explizit abgelehnt (Validierung fehlgeschlagen) |
"too_large" |
zu sendender Blob braucht mehr als 255 Chunks |