# VersaGUI Windows-Tray-App zur Konfiguration und Steuerung des VersaPad v2. Geschrieben in **C# / .NET 7 / WinForms**. ## Voraussetzungen - Windows 10/11 - [.NET 7 SDK](https://dotnet.microsoft.com/download/dotnet/7.0) (zum Bauen) - VersaMCU-Firmware auf dem Board geflasht - Board per USB verbunden (erscheint als CDC COM-Port, kein Treiber nötig) ## Starten ```bash dotnet run ``` Oder als Release-Build: ```bash dotnet publish -c Release -r win-x64 --self-contained ``` Die App erscheint als Icon in der Windows-Taskleiste (System Tray). Kein Hauptfenster. ## Bedienung 1. **Board verbinden** – App erkennt das Board automatisch per VID/PID (`0x239A / 0x0042`) via WMI 2. **Rechtsklick** auf das Tray-Icon → **Konfiguration...** 3. Taste/Encoder anklicken → Aktion auswählen: - **HID Tastatur**: Großen Button klicken, dann gewünschte Taste drücken (Strg+C, F5, …) - **HID Consumer**: Dropdown – Play/Pause, Lautstärke, etc. - **Host Command**: Numerische Command-ID (zukünftig: URL/Programm) - **LED-Farbe**: Farbpicker für die Idle-LED des Buttons 4. **Auf Board speichern** – überträgt Config in den NVM des Boards 5. Beim nächsten Verbinden wird die Config automatisch vom Board geladen --- ## Funktionsumfang (Anforderungskatalog) ### 1 Verbindung & Geräteerkennung | # | Anforderung | Status | |---|-------------|--------| | 1.1 | App läuft als **Windows Tray-Anwendung** ohne sichtbares Hauptfenster | ✅ | | 1.2 | Automatische Board-Erkennung per **WMI / VID+PID** (`0x239A / 0x0042`) | ✅ | | 1.3 | **Automatischer Reconnect** alle 3 s; 5 s Backoff nach Verbindungsverlust | ✅ | | 1.4 | Verbindungsstatus im Tray-Icon (Symbol + Tooltip) sichtbar | ✅ | | 1.5 | Beim Verbinden wird die gespeicherte Config **automatisch vom Board gelesen** | ✅ | | 1.6 | Beim Verbinden wird die **Makro-Tabelle automatisch vom Board gelesen** | ✅ | ### 2 Tastenbelegung – HID Tastatur | # | Anforderung | Status | |---|-------------|--------| | 2.1 | Taste durch **Drücken erfassen** (kein manuelles HID-ID-Eingeben) | ✅ | | 2.2 | Modifier-Kombination: **Strg / Shift / Alt / Win** einzeln oder kombiniert | ✅ | | 2.3 | **Pfeiltasten, Enter, Escape, F1–F12, Numpad** erfassbar | ✅ | | 2.4 | **Layout-unabhängige Erfassung** via PS/2-Scan-Code → HID-Usage; Ö/Ä/Ü auf QWERTZ korrekt | ✅ | | 2.5 | Taste-Name wird laut **aktivem Windows-Layout** angezeigt (`GetKeyNameText`) | ✅ | | 2.6 | Board führt Tastendruck als **USB-HID-Tastatureingabe** aus (funktioniert ohne laufende App) | ✅ | ### 3 Tastenbelegung – HID Consumer / Medientasten | # | Anforderung | Status | |---|-------------|--------| | 3.1 | Auswahl per **Dropdown** mit Klartext-Namen | ✅ | | 3.2 | Unterstützte Aktionen: Play/Pause, Nächster/Vorheriger Titel, Stop, Lauter/Leiser, Mute, Taschenrechner, Browser Zurück/Vor | ✅ | ### 4 Tastenbelegung – Makros | # | Anforderung | Status | |---|-------------|--------| | 4.1 | Bis zu **4 Schritte** pro Makro (je 1 Taste + Modifier) | ✅ | | 4.2 | Jeder Schritt per **Taste drücken** erfassen, inkl. Sondertasten | ✅ | | 4.3 | Leere Schritte werden übersprungen (kürzere Makros möglich) | ✅ | | 4.4 | **32 Makro-Slots** – je ein Slot pro MX-Button (0–19) und Encoder-Aktion (20–31) | ✅ | | 4.5 | Makro-Tabelle wird **separat** vom Board gelesen und geschrieben (NVM Row 1) | ✅ | | 4.6 | Board führt Makro-Schritte mit 10 ms Key-Down + 20 ms Pause **ohne laufende App** aus | ✅ | ### 5 Encoder-Belegung | # | Anforderung | Status | |---|-------------|--------| | 5.1 | **4 Encoder**, je 3 Aktionen: SW (Drücken), CW (Rechts), CCW (Links) | ✅ | | 5.2 | Gleiche Aktionstypen wie Tasten (HID Key, Consumer, Makro, Host Command) | ✅ | ### 6 LED-Konfiguration | # | Anforderung | Status | |---|-------------|--------| | 6.1 | **Basis-LED-Farbe** pro MX-Button via Farbpicker (RGB) | ✅ | | 6.2 | **Animationsmodus** pro Button wählbar: Statisch, Blinken, Pulsieren, Regenbogen | ✅ | | 6.3 | **Animations-Tempo**: Schnell (0,5 s) / Mittel (1 s) / Langsam (2 s) / Sehr langsam (4 s) | ✅ | | 6.4 | Regenbogen-Modus: Board berechnet Hue-Sweep lokal, gleichmäßige Phasenverteilung | ✅ | | 6.5 | LED-Config und Aktionstyp im **gleichen Dialog** bearbeitbar | ✅ | ### 7 Konfiguration speichern & laden | # | Anforderung | Status | |---|-------------|--------| | 7.1 | Config und Makros werden gleichzeitig per **„Auf Board speichern"** übertragen | ✅ | | 7.2 | Board validiert Config mit **CRC16-CCITT + Magic + Version**; NACK bei Fehler | ✅ | | 7.3 | Config bleibt nach Stromverlust im **NVM** erhalten (kein Flash-Verschleiß bei nur Lesen) | ✅ | | 7.4 | **JSON-Export** der gesamten Config (Aktionen + LED) in Datei | ✅ | | 7.5 | **JSON-Import** mit Versionscheck; ungültige Dateien werden abgelehnt | ✅ | ### 8 Verbindungsprotokoll | # | Anforderung | Status | |---|-------------|--------| | 8.1 | **8-Byte-Festlängen-Pakete** über CDC Serial (kein Treiber nötig) | ✅ | | 8.2 | **Ping/Pong** zur manuellen Verbindungsdiagnose im Konfigurations-Fenster | ✅ | | 8.3 | Config-Transfer: BEGIN → n×DATA(6 Byte) → COMMIT → ACK/NACK | ✅ | | 8.4 | Makro-Transfer: gleiche Struktur, 43 Chunks à 6 Byte (256 Byte Tabelle) | ✅ | | 8.5 | Debug-Logging aller RX-Pakete in `%TEMP%\versapad_rx.txt` | ✅ | ### 9 Nicht implementiert / Roadmap | # | Anforderung | Status | |---|-------------|--------| | 9.1 | **Host Command**: URL/Programm öffnen wenn Board-Taste gedrückt | 🔲 TODO | | 9.2 | Eigenes **Tray-Icon** (aktuell: Windows-Standard-Icon) | 🔲 TODO | | 9.3 | **Fader/Potentiometer**-Unterstützung (3× ADC-Achsen auf Board vorhanden) | 🔲 TODO | --- ## Projekt-Struktur ``` VersaGUI/ ├── VersaGUI.csproj – .NET 7 WinForms Projekt ├── Program.cs – Einstiegspunkt, startet TrayApp ├── TrayApp.cs – ApplicationContext: Tray-Icon, Menü, Paket-Routing ├── ConfigForm.cs – Konfigurations-Fenster (4×5 Grid + Encoder-Panel) ├── ActionDialog.cs – Dialog: Taste erfassen / Consumer / Makro / LED-Animation ├── DeviceConfig.cs – C#-Spiegel von SDeviceConfig (223 B, Version 2) + MacroTable (256 B) ├── ConfigJson.cs – JSON-Export/Import der DeviceConfig ├── Protocol.cs – Protokoll-Konstanten (Commands + Events) └── SerialManager.cs – COM-Port-Erkennung, Read-Loop, Config + Makros senden/empfangen ``` ## Architektur ``` TrayApp ├── SerialManager – Hintergrund-Thread: COM-Port-Verbindung + Leseloop │ ├── Connected-Event → Config vom Board anfordern (CMD_CONFIG_READ) │ └── PacketReceived → TrayApp.OnPacket() ├── DeviceConfig – In-Memory-Modell der Board-Config └── ConfigForm (optional) – Öffnet sich auf Benutzeranfrage └── ActionDialog – Modaler Dialog pro Taste/Encoder ``` ### SerialManager - Erkennt das Board per **WMI** (`Win32_PnPEntity`, VID/PID-Filter) → COM-Port-Name - Lese-Loop im Hintergrund-Thread: sammelt 8-Byte-Pakete, feuert `PacketReceived`-Event auf dem UI-Thread - **Reconnect-Timer**: versucht alle 3 Sekunden neu zu verbinden; nach Disconnect 5 Sekunden Backoff - Wichtig: `DtrEnable = true` muss **vor** `Open()` gesetzt werden – der SAMD21 prüft die DTR-Leitung in `if (!SerialUSB)` und verwirft sonst alle ausgehenden Pakete ### DeviceConfig / Serialisierung `DeviceConfig.ToBytes()` erzeugt den exakt **223-Byte**-Puffer der `SDeviceConfig`-Struct (Version 2, little-endian, packed). `CRC16-CCITT` (Poly 0x1021, Init 0xFFFF) über Bytes 7–222 – identisch zur Firmware-Implementierung. `MacroTable.ToBytes()` erzeugt **256 Byte** (32 Slots × 4 Steps × 2 Byte). Config-Übertragung Board→PC (Lesen): ``` PC sendet: CMD_CONFIG_READ (0x13) Board sendet: EVT_CONFIG_BEGIN (Chunk-Anzahl = 38) EVT_CONFIG_DATA × 38 (je 6 Nutzbytes) EVT_CONFIG_END ``` Config-Übertragung PC→Board (Schreiben): ``` PC sendet: CMD_CONFIG_BEGIN (0x10, Chunk-Anzahl) CMD_CONFIG_DATA × 38 CMD_CONFIG_COMMIT (0x12) Board sendet: EVT_CONFIG_ACK (0x90) oder EVT_CONFIG_NACK (0x91) ``` Makro-Übertragung (analog, separate Kommandos 0x20–0x23): ``` PC sendet: CMD_MACRO_BEGIN (0x20, Chunk-Anzahl = 43) CMD_MACRO_DATA × 43 CMD_MACRO_COMMIT (0x22) Board sendet: EVT_MACRO_ACK (0x95) ``` ### Bekannte .NET-Eigenheiten | Problem | Lösung | |---|---| | `.NET 7 SerialPort.ReadByte()` wirft `IOException` statt `TimeoutException` auf Timeout | `catch (IOException)` → nur als Fehler behandeln wenn Port nicht mehr offen ist | | `DtrEnable = false` (Standard) → Board sendet nichts | `DtrEnable = true` im SerialPort-Konstruktor setzen, **vor** `Open()` | | DTR-Zustandswechsel nach `Open()` löst CDC-Disconnect auf dem Board aus | DTR vor `Open()` setzen → kein Zustandswechsel |