Compare commits
No commits in common. "0ad7858a090bbfca443fe97218cdf1df5e8190b5" and "2eb43826ce6994ef5ac15845fadf15047568007f" have entirely different histories.
0ad7858a09
...
2eb43826ce
@ -124,7 +124,6 @@ Die App erscheint als Icon in der Windows-Taskleiste (System Tray). Kein Hauptfe
|
|||||||
| 9.1 | **Host Command**: URL/Programm öffnen wenn Board-Taste gedrückt | 🔲 TODO |
|
| 9.1 | **Host Command**: URL/Programm öffnen wenn Board-Taste gedrückt | 🔲 TODO |
|
||||||
| 9.2 | Eigenes **Tray-Icon** (aktuell: Windows-Standard-Icon) | 🔲 TODO |
|
| 9.2 | Eigenes **Tray-Icon** (aktuell: Windows-Standard-Icon) | 🔲 TODO |
|
||||||
| 9.3 | **Fader/Potentiometer**-Unterstützung (3× ADC-Achsen auf Board vorhanden) | 🔲 TODO |
|
| 9.3 | **Fader/Potentiometer**-Unterstützung (3× ADC-Achsen auf Board vorhanden) | 🔲 TODO |
|
||||||
| 9.4 | **Profile**: mehrere Tastenbelegungen speichern und wechseln | 🔲 TODO |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -192,10 +191,6 @@ PC sendet: CMD_MACRO_BEGIN (0x20, Chunk-Anzahl = 43)
|
|||||||
Board sendet: EVT_MACRO_ACK (0x95)
|
Board sendet: EVT_MACRO_ACK (0x95)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Kommunikationsprotokoll
|
|
||||||
|
|
||||||
Das vollständige Protokoll (alle Command- und Event-IDs, NVM-Layout, Paketformat) ist in [VersaMCU/README.md](../VersaMCU/README.md#serial-protokoll-8-bytes-fixed) dokumentiert.
|
|
||||||
|
|
||||||
### Bekannte .NET-Eigenheiten
|
### Bekannte .NET-Eigenheiten
|
||||||
|
|
||||||
| Problem | Lösung |
|
| Problem | Lösung |
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
# VersaGUI – Architektur-Übersicht
|
|
||||||
|
|
||||||
## Technologie-Stack
|
|
||||||
|
|
||||||
| Merkmal | Wert |
|
|
||||||
|---|---|
|
|
||||||
| Sprache | C# / .NET 7 |
|
|
||||||
| UI-Framework | WinForms (`[STAThread]`) |
|
|
||||||
| Einstiegspunkt | `Program.cs` → `Application.Run(new TrayApp())` |
|
|
||||||
| Laufzeitmodell | `ApplicationContext` (kein `Form` als Hauptfenster) |
|
|
||||||
| Threading | UI-Thread + 1 Hintergrund-Lese-Thread + Timer-Thread |
|
|
||||||
|
|
||||||
## Komponentenübersicht
|
|
||||||
|
|
||||||
| Datei | Verantwortung |
|
|
||||||
|---|---|
|
|
||||||
| `TrayApp` | `ApplicationContext`; hält Tray-Icon, öffnet ConfigForm, verarbeitet Board-Events |
|
|
||||||
| `SerialManager` | Verbindungsverwaltung, WMI-Erkennung, Lese-Thread, Sende-Methoden |
|
|
||||||
| `ConfigForm` | Hauptfenster (Grid + Encoder-Panel + Footer); öffnet ActionDialog |
|
|
||||||
| `ActionDialog` | Modaler Dialog zum Bearbeiten einer Aktion + LED-Einstellungen |
|
|
||||||
| `DeviceConfig` | C#-Spiegel von `SDeviceConfig`; Serialisierung/Deserialisierung (223 B) |
|
|
||||||
| `MacroTable` | C#-Spiegel von `SMacroTable`; Serialisierung/Deserialisierung (256 B) |
|
|
||||||
| `ConfigJson` | JSON-Import/Export für `DeviceConfig` |
|
|
||||||
| `Protocol` | Konstanten für alle Command/Event-IDs (spiegelt `usb_serial.h`) |
|
|
||||||
|
|
||||||
## Datenfluss
|
|
||||||
|
|
||||||
```
|
|
||||||
Board → SerialManager (ReadLoop, BG-Thread)
|
|
||||||
→ SynchronizationContext.Post (→ UI-Thread)
|
|
||||||
→ TrayApp.OnPacket()
|
|
||||||
├── Config-Dump: _rxConfigBuf aufbauen → DeviceConfig.FromBytes()
|
|
||||||
├── Makro-Dump: _rxMacroBuf aufbauen → MacroTable.FromBytes()
|
|
||||||
└── HOST_COMMAND-Events: (TODO: Aktion ausführen)
|
|
||||||
|
|
||||||
Benutzer → ConfigForm → ActionDialog
|
|
||||||
→ DeviceConfig / MacroTable (in-memory ändern)
|
|
||||||
→ SerialManager.SendConfig() + SendMacros() (BG-Task)
|
|
||||||
→ Board (chunked, 6 B/Paket)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Threading-Modell
|
|
||||||
|
|
||||||
```
|
|
||||||
UI-Thread : TrayApp, ConfigForm, ActionDialog, alle WinForms-Controls
|
|
||||||
BG-Thread : SerialManager.ReadLoop() – blockiert auf ReadByte()
|
|
||||||
Timer-Thread : SerialManager._reconnectTimer → TryConnect() alle 3 s
|
|
||||||
Sende-Task : ConfigForm.OnSave() → Task.Run() (blockiert ~400 ms für Transfer)
|
|
||||||
```
|
|
||||||
|
|
||||||
Alle Board-Events werden per `SynchronizationContext.Post` auf den UI-Thread gepostet. Controls dürfen nie vom BG-Thread angefasst werden.
|
|
||||||
|
|
||||||
## Verbindungslebenszyklus
|
|
||||||
|
|
||||||
```
|
|
||||||
Start → Timer feuert → TryConnect() → WMI-Suche (VID 0x239A / PID 0x0042)
|
|
||||||
→ SerialPort öffnen (DtrEnable=true VOR Open()!)
|
|
||||||
→ 200 ms warten → ReadLoop starten → Connected-Event → RequestConfig() + RequestMacros()
|
|
||||||
|
|
||||||
Disconnect → ReadLoop bricht ab → Disconnected-Event → 5 s Backoff → Timer läuft weiter
|
|
||||||
```
|
|
||||||
|
|
||||||
## Invarianten / Constraints
|
|
||||||
|
|
||||||
- **DtrEnable=true muss VOR `Open()` gesetzt werden**: SAMD21 prüft DTR für `usb_serial_send()`. Der Default-Wert false würde alle Board→PC-Antworten still verwerfen.
|
|
||||||
- **IOException ≠ Disconnect**: .NET 7 wirft `IOException` statt `TimeoutException` bei `ReadByte()`-Timeout. Nur als echten Fehler behandeln wenn `_port.IsOpen == false`.
|
|
||||||
- **Packed-kompatible Serialisierung**: `DeviceConfig.ToBytes()` muss exakt 223 B in derselben Reihenfolge wie `SDeviceConfig` (packed C++) erzeugen. Jede Änderung am Firmware-Layout muss hier gespiegelt werden.
|
|
||||||
- **Config-Version**: `DeviceConfig.Version == 2`. `FromBytes()` prüft Magic + Version + CRC; schlägt einer fehl → Methode gibt `false` zurück, Config bleibt unverändert.
|
|
||||||
- **Debug-Log**: `versapad_rx.txt` in `%TEMP%` – vor Release-Nutzung entfernen (TODO).
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
# SerialManager
|
|
||||||
|
|
||||||
**Datei:** `SerialManager.cs`
|
|
||||||
|
|
||||||
## Verantwortung
|
|
||||||
|
|
||||||
- COM-Port-Erkennung per WMI (VID/PID)
|
|
||||||
- Verbindungsaufbau und automatischer Reconnect
|
|
||||||
- Hintergrund-Lese-Thread mit Paket-Assembler
|
|
||||||
- Sende-Methoden für alle Protokoll-Operationen
|
|
||||||
|
|
||||||
## COM-Port-Erkennung
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
VidPid = "VID_239A&PID_0042" // muss mit platformio.ini übereinstimmen
|
|
||||||
|
|
||||||
SELECT Name, DeviceID FROM Win32_PnPEntity WHERE DeviceID LIKE '%VID_239A&PID_0042%'
|
|
||||||
// Name enthält z.B. "USB Serial Device (COM3)" → extrahiert "COM3"
|
|
||||||
```
|
|
||||||
|
|
||||||
Erkennt das Board auch bei wechselnder COM-Nummer. Funktioniert auch nach USB-Reinitialisierung (Kabel abziehen/stecken nach SWD-Flash).
|
|
||||||
|
|
||||||
## Verbindungsaufbau (TryConnect)
|
|
||||||
|
|
||||||
```
|
|
||||||
Monitor.TryEnter(_connectLock) – nur ein gleichzeitiger Versuch
|
|
||||||
FindPort() per WMI
|
|
||||||
SerialPort erstellen:
|
|
||||||
ReadTimeout = 500 ms
|
|
||||||
WriteTimeout = 500 ms
|
|
||||||
DtrEnable = true ← VOR Open() setzen! (SAMD21 CDC-Constraint)
|
|
||||||
port.Open()
|
|
||||||
Thread.Sleep(200) – CDC stabilisieren
|
|
||||||
ReadLoop-Thread starten
|
|
||||||
Connected-Event → UI-Thread
|
|
||||||
```
|
|
||||||
|
|
||||||
**Reconnect-Timer**: alle 3 s, Start nach 500 ms. 5 s Backoff (`_waitingAfterDisconnect`) nach Verbindungsverlust damit Board vollständig re-enumerieren kann.
|
|
||||||
|
|
||||||
## ReadLoop (Hintergrund-Thread)
|
|
||||||
|
|
||||||
```
|
|
||||||
while (port.IsOpen):
|
|
||||||
b = port.ReadByte() – blockiert 500 ms (ReadTimeout)
|
|
||||||
buf[bufFill++] = b
|
|
||||||
if bufFill < 8: continue
|
|
||||||
→ SerialPacket fertig → SynchronizationContext.Post → PacketReceived auf UI-Thread
|
|
||||||
bufFill = 0
|
|
||||||
|
|
||||||
Bei IOException:
|
|
||||||
if port.IsOpen: ignorieren ← .NET 7: IOException = normaler Timeout (kein Fehler!)
|
|
||||||
else: break → Disconnect
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sende-Methoden
|
|
||||||
|
|
||||||
| Methode | Funktion |
|
|
||||||
|---|---|
|
|
||||||
| `Send(pkt)` | Rohe 8-Byte-Übertragung (fire-and-forget, ignoriert Sendefehler) |
|
|
||||||
| `SetLedOverride(keyId, r, g, b)` | CMD 0x01 |
|
|
||||||
| `ClearLedOverride(keyId)` | CMD 0x02 |
|
|
||||||
| `SetLedBase(keyId, r, g, b)` | CMD 0x03 |
|
|
||||||
| `RequestConfig()` | CMD 0x13 – Board antwortet mit Config-Dump |
|
|
||||||
| `RequestMacros()` | CMD 0x23 – Board antwortet mit Makro-Dump |
|
|
||||||
| `SendConfig(cfg)` | BEGIN(0x10) → 38×DATA(0x11) → COMMIT(0x12), 5 ms zwischen Chunks |
|
|
||||||
| `SendMacros(macros)` | BEGIN(0x20) → 43×DATA(0x21) → COMMIT(0x22), 5 ms zwischen Chunks |
|
|
||||||
|
|
||||||
`SendConfig` und `SendMacros` blockieren ~400 ms → werden in `Task.Run()` aus `ConfigForm.OnSave()` aufgerufen.
|
|
||||||
|
|
||||||
## Events
|
|
||||||
|
|
||||||
| Event | Gefeuert wenn |
|
|
||||||
|---|---|
|
|
||||||
| `Connected` | Verbindung erfolgreich hergestellt (auf UI-Thread) |
|
|
||||||
| `Disconnected` | Verbindung verloren (auf UI-Thread) |
|
|
||||||
| `PacketReceived` | Vollständiges 8-Byte-Paket empfangen (auf UI-Thread) |
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
# DeviceConfig & MacroTable
|
|
||||||
|
|
||||||
**Datei:** `DeviceConfig.cs`
|
|
||||||
|
|
||||||
## Überblick
|
|
||||||
|
|
||||||
C#-Spiegel der Firmware-Structs. Muss byte-kompatibel mit `SDeviceConfig` (nvm_config.h) und `SMacroTable` (macro_config.h) sein.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## DeviceConfig
|
|
||||||
|
|
||||||
### Felder
|
|
||||||
|
|
||||||
| Feld | Typ | Inhalt |
|
|
||||||
|---|---|---|
|
|
||||||
| `MxActions[20]` | `DeviceAction[]` | Aktionen für MX-Buttons 0–19 |
|
|
||||||
| `EncActions[4,3]` | `DeviceAction[,]` | Encoder [0–3][SW=0/CW=1/CCW=2] |
|
|
||||||
| `LedBase[20]` | `Color[]` | RGB-Basis-LED-Farbe je Button |
|
|
||||||
| `LedAnim[20]` | `LedAnimType[]` | Animation je Button |
|
|
||||||
| `LedPeriod[20]` | `ushort[]` | Animationsperiode in ms |
|
|
||||||
|
|
||||||
### Serialisierungs-Layout (ToBytes / FromBytes, 223 B)
|
|
||||||
|
|
||||||
```
|
|
||||||
Offset 0 4B Magic 0x56503202 (little-endian)
|
|
||||||
Offset 4 1B Version = 2
|
|
||||||
Offset 5 2B CRC16-CCITT über Bytes 7–222 (little-endian)
|
|
||||||
Offset 7 60B MxActions[20] je 3B: type(1) + data_lo(1) + data_hi(1)
|
|
||||||
Offset 67 36B EncActions[4][3] je 3B
|
|
||||||
Offset103 20B LedBase[i].R
|
|
||||||
Offset123 20B LedBase[i].G
|
|
||||||
Offset143 20B LedBase[i].B
|
|
||||||
Offset163 20B LedAnim[i] als byte
|
|
||||||
Offset183 40B LedPeriod[i] als uint16 little-endian
|
|
||||||
```
|
|
||||||
|
|
||||||
### CRC16-CCITT
|
|
||||||
|
|
||||||
Polynom `0x1021`, Init `0xFFFF`, über Bytes 7–222 (nach dem CRC-Feld selbst). Muss identisch mit Firmware-Implementierung sein. `DeviceConfig.Crc16()` ist statisch und direkt testbar.
|
|
||||||
|
|
||||||
### Defaults (entspricht Firmware-Defaults)
|
|
||||||
|
|
||||||
- Alle Aktionen: `None`
|
|
||||||
- LEDs: warm-weiß (R=80, G=40, B=0)
|
|
||||||
- Animation: `ColorCycle` (Regenbogen), Period 4000 ms
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## DeviceAction
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public enum ActionType : byte { None=0, HidKey=1, HidConsumer=2, HostCommand=3, Macro=4 }
|
|
||||||
|
|
||||||
public class DeviceAction {
|
|
||||||
public ActionType Type { get; set; }
|
|
||||||
public ushort Data { get; set; }
|
|
||||||
// HidKey: Low-Byte = HID Keycode, High-Byte = Modifier
|
|
||||||
// HidConsumer: Consumer Usage ID
|
|
||||||
// HostCommand: Command-ID
|
|
||||||
// Macro: Slot-Index 0–31
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`DeviceAction.Display` gibt einen lesbaren String zurück (z.B. `"Strg+C"`, `"Play/Pause"`, `"Makro 3"`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## MacroTable
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class MacroTable {
|
|
||||||
public const int Slots = 32;
|
|
||||||
public const int MaxSteps = 4;
|
|
||||||
public MacroStep[,] Steps { get; } // [slot][step]
|
|
||||||
}
|
|
||||||
public class MacroStep {
|
|
||||||
public byte Keycode { get; set; } // 0 = leer
|
|
||||||
public byte Modifier { get; set; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Slot-Konvention
|
|
||||||
|
|
||||||
| Slots | Verwendung |
|
|
||||||
|---|---|
|
|
||||||
| 0–19 | MX-Button `mxIdx` (`MacroTable.SlotForMx(mxIdx)`) |
|
|
||||||
| 20–31 | Encoder: `20 + enc * 3 + actIdx` (`MacroTable.SlotForEncoder(enc, actIdx)`) |
|
|
||||||
|
|
||||||
### Serialisierung (256 B)
|
|
||||||
|
|
||||||
32 Slots × 4 Steps × 2 B = 256 B. Keycode zuerst, dann Modifier. Kein Magic/CRC (Board akzeptiert jeden Inhalt).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## LedAnimType
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public enum LedAnimType : byte {
|
|
||||||
Static=0, Blink=1, Pulse=2, ColorCycle=5
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Werte entsprechen `LEDAnim` in der Firmware. `FADE_IN` (3) und `FADE_OUT` (4) existieren in der Firmware aber nicht in der GUI (nicht konfigurierbar, nur `COLOR_FADE` intern).
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
# TrayApp
|
|
||||||
|
|
||||||
**Datei:** `TrayApp.cs`
|
|
||||||
|
|
||||||
## Verantwortung
|
|
||||||
|
|
||||||
- App-Lebenszyklus als `ApplicationContext` (kein Hauptfenster)
|
|
||||||
- Tray-Icon mit Verbindungsstatus und Kontextmenü
|
|
||||||
- Empfang und Routing aller Board-Events (via `SerialManager.PacketReceived`)
|
|
||||||
- Config/Makro-Dump-Empfang (chunked, via `_rxConfigBuf` / `_rxMacroBuf`)
|
|
||||||
- Öffnen des `ConfigForm` (nur eine Instanz gleichzeitig)
|
|
||||||
|
|
||||||
## Tray-Menü
|
|
||||||
|
|
||||||
```
|
|
||||||
[●/○] VersaPad – verbunden / nicht verbunden (disabled, nur Anzeige)
|
|
||||||
──────────────────────────────────────────────
|
|
||||||
Konfiguration... → OpenConfigForm()
|
|
||||||
──────────────────────────────────────────────
|
|
||||||
Beenden → Application.Exit()
|
|
||||||
```
|
|
||||||
|
|
||||||
Icon und Tooltip spiegeln den Verbindungsstatus:
|
|
||||||
- Verbunden: `SystemIcons.Information`, Text "VersaPad – verbunden"
|
|
||||||
- Getrennt: `SystemIcons.Application`, Text "VersaPad – nicht verbunden"
|
|
||||||
|
|
||||||
(TODO: eigenes Icon einbinden)
|
|
||||||
|
|
||||||
## Board-Event-Handling (OnPacket)
|
|
||||||
|
|
||||||
| Event-ID | Aktion |
|
|
||||||
|---|---|
|
|
||||||
| `EvtConfigBegin` | `_rxConfigBuf = new byte[223]` |
|
|
||||||
| `EvtConfigData` | Chunk in `_rxConfigBuf` eintragen (`KeyId * 6` = Byte-Offset) |
|
|
||||||
| `EvtConfigEnd` | `DeviceConfig.FromBytes()` → `ConfigForm.RefreshAll()` |
|
|
||||||
| `EvtMacroBegin` | `_rxMacroBuf = new byte[256]` |
|
|
||||||
| `EvtMacroData` | Chunk in `_rxMacroBuf` eintragen |
|
|
||||||
| `EvtMacroEnd` | `MacroTable.FromBytes()` |
|
|
||||||
| `EvtPong` | MessageBox "Ping OK" |
|
|
||||||
| `EvtConfigAck` | MessageBox "Config gespeichert" |
|
|
||||||
| `EvtConfigNack` | MessageBox "Config FEHLER" |
|
|
||||||
| `EvtMacroAck` | MessageBox "Makros gespeichert" |
|
|
||||||
| `EvtKeyDown` | TODO: HOST_COMMAND-Aktion ausführen |
|
|
||||||
| `EvtEncCw/Ccw` | TODO: Encoder HOST_COMMAND |
|
|
||||||
|
|
||||||
## Verbindungslebenszyklus
|
|
||||||
|
|
||||||
```
|
|
||||||
OnConnected():
|
|
||||||
Icon + Text + Menü-Item aktualisieren
|
|
||||||
serial.RequestConfig() → Config-Dump vom Board
|
|
||||||
serial.RequestMacros() → Makro-Dump vom Board
|
|
||||||
|
|
||||||
OnDisconnected():
|
|
||||||
Icon + Text + Menü-Item aktualisieren
|
|
||||||
```
|
|
||||||
|
|
||||||
## TODOs in dieser Klasse
|
|
||||||
|
|
||||||
- HOST_COMMAND-Ausführung: `EvtKeyDown` empfangen → Aktion aus Config laden → URL/Programm starten
|
|
||||||
- Eigenes Tray-Icon statt `SystemIcons.Application`
|
|
||||||
- Debug-Log (`versapad_rx.txt`) entfernen
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
# ConfigForm
|
|
||||||
|
|
||||||
**Datei:** `ConfigForm.cs`
|
|
||||||
|
|
||||||
## Verantwortung
|
|
||||||
|
|
||||||
Hauptkonfigurationsfenster: zeigt alle 20 MX-Buttons und 4 Encoder, öffnet `ActionDialog` bei Klick, speichert Config + Makros auf das Board.
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
|
|
||||||
```
|
|
||||||
┌── Tasten (GroupBox) ──────────────────────────────────────────┐
|
|
||||||
│ TableLayoutPanel 4 Spalten × 5 Zeilen │
|
|
||||||
│ Jede Zelle = Button mit LED-Hintergrundfarbe + Aktionstext │
|
|
||||||
└───────────────────────────────────────────────────────────────┘
|
|
||||||
┌── Encoder (GroupBox) ─────────────────────────────────────────┐
|
|
||||||
│ Header: SW / CW / CCW │
|
|
||||||
│ 4 Zeilen × 3 Buttons (ENC 0–3) │
|
|
||||||
└───────────────────────────────────────────────────────────────┘
|
|
||||||
[Auf Board speichern] [Ping] [Exportieren] [Importieren] [Schließen]
|
|
||||||
```
|
|
||||||
|
|
||||||
`FixedSingle`-Border, `StartPosition = CenterScreen`, kein Maximieren.
|
|
||||||
|
|
||||||
## MX-Button-Darstellung (RefreshMxButton)
|
|
||||||
|
|
||||||
| Animation | Hintergrund | Textfarbe |
|
|
||||||
|---|---|---|
|
|
||||||
| ColorCycle | `(40, 40, 40)` dunkelgrau | Weiß |
|
|
||||||
| Andere | `LedBase[mxIdx]` | Schwarz/Weiß nach Luminanz |
|
|
||||||
|
|
||||||
Text = `action.Display + animName` (z.B. `"Strg+C [Blinken]"`).
|
|
||||||
|
|
||||||
Luminanz-Formel für Textfarben-Kontrast: `(R*299 + G*587 + B*114) / 1000`
|
|
||||||
|
|
||||||
## mx_idx ↔ Physisches Layout
|
|
||||||
|
|
||||||
```
|
|
||||||
mx_idx = col * 5 + row (col=0..3, row=0..4)
|
|
||||||
```
|
|
||||||
|
|
||||||
Entspricht `key_id - 5` in der Firmware. Im TableLayoutPanel: Spalte=col, Zeile=row.
|
|
||||||
|
|
||||||
## Speichern (OnSave)
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
Task.Run(() => {
|
|
||||||
_serial.SendConfig(_config); // ~300 ms
|
|
||||||
Thread.Sleep(50);
|
|
||||||
_serial.SendMacros(_macros); // ~250 ms
|
|
||||||
InvokeOnUi(() => { /* Button-Text + Enabled zurücksetzen */ });
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Save-Button wird während der Übertragung deaktiviert, Text wechselt zu "Wird gesendet...".
|
|
||||||
Save-Button ist nur aktiviert wenn Board verbunden (`_serial.IsConnected`).
|
|
||||||
|
|
||||||
## Import / Export
|
|
||||||
|
|
||||||
- **Export**: `ConfigJson.Serialize(_config)` → `SaveFileDialog` → `.json`-Datei
|
|
||||||
- **Import**: `OpenFileDialog` → Datei lesen → `ConfigJson.Deserialize()` → `RefreshAll()`
|
|
||||||
|
|
||||||
Fehler (IO, JSON-Parse, falsche Version) werden per `MessageBox` angezeigt.
|
|
||||||
|
|
||||||
## RefreshAll
|
|
||||||
|
|
||||||
Wird von `TrayApp` nach erfolgreicher Config vom Board aufgerufen (über `_configForm?.RefreshAll()`). Aktualisiert alle 20 MX-Buttons und 12 Encoder-Buttons ohne Dialog.
|
|
||||||
|
|
||||||
## Extensions (in derselben Datei)
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Statischer ToolTip für alle Controls
|
|
||||||
public static void ToolTipText(this Control ctrl, string text)
|
|
||||||
public static string TypeDescription(this DeviceAction a)
|
|
||||||
```
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
# ActionDialog
|
|
||||||
|
|
||||||
**Datei:** `ActionDialog.cs`
|
|
||||||
|
|
||||||
## Verantwortung
|
|
||||||
|
|
||||||
Modaler Dialog zum Bearbeiten einer einzelnen Aktion (MX-Button oder Encoder-Slot) inkl. optionaler LED-Einstellungen.
|
|
||||||
|
|
||||||
## Ergebnis-Properties
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public DeviceAction ResultAction { get; } // Typ + Keycode/Usage/etc.
|
|
||||||
public Color ResultColor { get; } // LED-Basisfarbe
|
|
||||||
public LedAnimType ResultAnim { get; } // Animation
|
|
||||||
public ushort ResultPeriod { get; } // Periode in ms
|
|
||||||
```
|
|
||||||
|
|
||||||
## Panels (je nach Typ ein Panel sichtbar)
|
|
||||||
|
|
||||||
| Typ | Panel | Inhalt |
|
|
||||||
|---|---|---|
|
|
||||||
| HID Tastatur | `_hidKeyPanel` | Capture-Button + Strg/Shift/Alt/Win-Checkboxen |
|
|
||||||
| HID Consumer | `_consumerPanel` | Dropdown mit 12 Medien-Aktionen |
|
|
||||||
| Host Command | `_cmdPanel` | TextBox für numerische Command-ID |
|
|
||||||
| Makro | `_macroPanel` | 4 Step-Buttons + je Strg/Shift/Alt-Checkboxen |
|
|
||||||
| Keine | — | Alle Panels ausgeblendet |
|
|
||||||
|
|
||||||
LED-Panels (`_colorPanel`, `_animPanel`) erscheinen zusätzlich wenn `showColor=true` (nur MX-Buttons, nicht Encoder).
|
|
||||||
|
|
||||||
`UpdateLayout()` repositioniert LED-Panels und passt `ClientSize` dynamisch an wenn der Typ gewechselt wird.
|
|
||||||
|
|
||||||
## Tasten-Capture (HID-Modus)
|
|
||||||
|
|
||||||
1. Benutzer klickt "Taste drücken..."
|
|
||||||
2. `_capturing = true`
|
|
||||||
3. Nächste Taste in `ProcessCmdKey()` → `VkToHid()` → `_hidKeycode` setzen
|
|
||||||
4. Capture-Button zeigt Tastenname (`HidKeyName()`)
|
|
||||||
|
|
||||||
**Warum `ProcessCmdKey` statt `OnKeyDown`?**
|
|
||||||
WinForms behandelt Pfeil- und Enter-Tasten als "Dialog Keys" in `ProcessDialogKey()` — sie erreichen `OnKeyDown` nie. `ProcessCmdKey` wird vor `ProcessDialogKey` aufgerufen und kann diese Tasten abfangen.
|
|
||||||
|
|
||||||
## Makro-Capture
|
|
||||||
|
|
||||||
Jeder der 4 Steps hat einen eigenen Capture-Button. `_captureStep` (0–3, -1 = inaktiv) zeigt welcher Step gerade aufnimmt. Capture-Logik identisch mit HID-Modus, schreibt in `_stepKeycodes[captureStep]`.
|
|
||||||
|
|
||||||
## Schlüssellookup (layout-unabhängig)
|
|
||||||
|
|
||||||
**Problem**: Windows VK-Codes sind tastaturlayout-abhängig. `Keys.OemQuotes` auf QWERTZ → Ö, auf US → `'`. Würde falsche HID-Codes liefern.
|
|
||||||
|
|
||||||
**Lösung**: PS/2 Scan-Codes sind layout-unabhängig (physische Tastenposition).
|
|
||||||
|
|
||||||
```
|
|
||||||
VK → Scan-Code via MapVirtualKey(vk, MAPVK_VK_TO_VSC)
|
|
||||||
Scan-Code → HID Usage via s_scanToHid[sc]
|
|
||||||
```
|
|
||||||
|
|
||||||
`s_scanToHid`: vollständige PS/2-Set-1 → HID-Tabelle (~60 Einträge).
|
|
||||||
|
|
||||||
**Ausnahme**: Erweiterte Navigationstasten (Pfeiltasten, Pos1, Ende, …) geben via `MapVirtualKey` den Numpad-Scan-Code zurück. Für diese gibt es eine direkte `Keys → HID`-Tabelle (`s_extVkToHid`).
|
|
||||||
|
|
||||||
**Anzeige**: `HidKeyName(hid)` gibt den lokalisierten Tastennamen zurück:
|
|
||||||
- Sondertasten: fest eingetragene deutsche Namen ("Esc", "→", "F5", …)
|
|
||||||
- Zeichentasten: `GetKeyNameText(scanCode << 16, ...)` → Windows gibt den Namen laut aktivem Layout zurück ("Ö" auf QWERTZ, ";" auf US)
|
|
||||||
|
|
||||||
## Consumer-Aktionen
|
|
||||||
|
|
||||||
12 HID Consumer Usage IDs als feste Liste:
|
|
||||||
|
|
||||||
| Usage | Name |
|
|
||||||
|---|---|
|
|
||||||
| 0x00CD | Play / Pause |
|
|
||||||
| 0x00B5 | Nächster Titel |
|
|
||||||
| 0x00B6 | Vorheriger Titel |
|
|
||||||
| 0x00B7 | Stop |
|
|
||||||
| 0x00E9 | Lauter |
|
|
||||||
| 0x00EA | Leiser |
|
|
||||||
| 0x00E2 | Stummschalten |
|
|
||||||
| 0x0192 | Taschenrechner |
|
|
||||||
| 0x0223 | Browser – Startseite |
|
|
||||||
| 0x0224 | Browser – Zurück |
|
|
||||||
| 0x0225 | Browser – Vor |
|
|
||||||
| 0x00B0 | Aufnahme |
|
|
||||||
|
|
||||||
## LED-Einstellungen
|
|
||||||
|
|
||||||
Nur wenn `showColor=true` (MX-Buttons). Enthält:
|
|
||||||
- **Farbpicker**: `ColorDialog` → `_colorBtn.BackColor`
|
|
||||||
- **Animations-Dropdown**: Statisch / Blinken / Pulsieren / Regenbogen
|
|
||||||
- **Periode-Dropdown**: Presets von "Sehr langsam (8 s)" bis "Sehr schnell (250 ms)"
|
|
||||||
|
|
||||||
Bei "Regenbogen" wird der Farbpicker ausgeblendet (Farbe irrelevant).
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
# ConfigJson (Import / Export)
|
|
||||||
|
|
||||||
**Datei:** `ConfigJson.cs`
|
|
||||||
|
|
||||||
## Format
|
|
||||||
|
|
||||||
Menschenlesbares JSON mit `System.Text.Json` (`WriteIndented=true`, Enums als Strings via `JsonStringEnumConverter`).
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": 2,
|
|
||||||
"buttons": [
|
|
||||||
{
|
|
||||||
"index": 0,
|
|
||||||
"action": { "type": "HidKey", "data": 260 },
|
|
||||||
"led": { "r": 80, "g": 40, "b": 0, "anim": "ColorCycle", "period_ms": 4000 }
|
|
||||||
},
|
|
||||||
...
|
|
||||||
],
|
|
||||||
"encoders": [
|
|
||||||
{
|
|
||||||
"index": 0,
|
|
||||||
"sw": { "type": "None", "data": 0 },
|
|
||||||
"cw": { "type": "None", "data": 0 },
|
|
||||||
"ccw": { "type": "None", "data": 0 }
|
|
||||||
},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Serialisierung
|
|
||||||
|
|
||||||
`ConfigJson.Serialize(cfg)` → JSON-String. Exportiert alle 20 Buttons + 4 Encoder vollständig.
|
|
||||||
|
|
||||||
## Deserialisierung
|
|
||||||
|
|
||||||
`ConfigJson.Deserialize(json, cfg)`:
|
|
||||||
- Prüft `version` – wirft `InvalidDataException` bei Mismatch
|
|
||||||
- Schreibt in bestehendes `DeviceConfig`-Objekt (kein `new`)
|
|
||||||
- Fehlende `buttons`/`encoders`-Arrays werden ignoriert (partial import möglich)
|
|
||||||
- Ungültige `index`-Werte werden übersprungen
|
|
||||||
|
|
||||||
## Anmerkungen
|
|
||||||
|
|
||||||
- `MacroTable` wird **nicht** exportiert (kein JSON-Format für Makros definiert)
|
|
||||||
- `data` enthält den `ushort`-Wert direkt (für HidKey z.B. `Keycode | (Modifier << 8)`)
|
|
||||||
- Die Datei ist kein Binärformat und kann manuell bearbeitet werden
|
|
||||||
25
doc/INDEX.md
25
doc/INDEX.md
@ -1,25 +0,0 @@
|
|||||||
# VersaGUI – Dokumentations-Index
|
|
||||||
|
|
||||||
Jede Datei deckt eine GUI-Komponente ab. Für Claude: die relevante(n) Dateien zu Beginn einer Aufgabe lesen statt die gesamten Quelldateien zu scannen.
|
|
||||||
|
|
||||||
| Datei | Inhalt |
|
|
||||||
|---|---|
|
|
||||||
| [00_architecture.md](00_architecture.md) | Threading-Modell, Datenfluss, Verbindungslebenszyklus, globale Constraints (DTR, IOException, Packed-Layout) |
|
|
||||||
| [01_serial_manager.md](01_serial_manager.md) | WMI-Erkennung, TryConnect, ReadLoop, Sende-Methoden, Reconnect-Backoff |
|
|
||||||
| [02_device_config.md](02_device_config.md) | DeviceConfig + MacroTable: Felder, Byte-Layout (223/256 B), CRC16, LedAnimType |
|
|
||||||
| [03_tray_app.md](03_tray_app.md) | ApplicationContext, Tray-Icon, Board-Event-Routing, Config/Makro-Dump-Empfang, TODOs |
|
|
||||||
| [04_config_form.md](04_config_form.md) | Grid-Layout, mx_idx-Formel, RefreshMxButton, OnSave (Task.Run), Import/Export |
|
|
||||||
| [05_action_dialog.md](05_action_dialog.md) | Panels je Typ, ProcessCmdKey-Capture, layout-unabhängiger Scan-Code-Lookup, Consumer-Liste |
|
|
||||||
| [06_config_json.md](06_config_json.md) | JSON-Format, Serialize/Deserialize, Einschränkungen |
|
|
||||||
|
|
||||||
## Schnell-Referenz: Was steht wo?
|
|
||||||
|
|
||||||
- **DtrEnable-Problem** → [00_architecture.md](00_architecture.md), [01_serial_manager.md](01_serial_manager.md)
|
|
||||||
- **IOException ≠ Disconnect (.NET 7)** → [00_architecture.md](00_architecture.md), [01_serial_manager.md](01_serial_manager.md)
|
|
||||||
- **Byte-Layout der 223-Byte-Config** → [02_device_config.md](02_device_config.md)
|
|
||||||
- **Warum ProcessCmdKey statt OnKeyDown?** → [05_action_dialog.md](05_action_dialog.md)
|
|
||||||
- **Warum Scan-Codes statt VK-Codes (Umlaut-Problem)?** → [05_action_dialog.md](05_action_dialog.md)
|
|
||||||
- **mx_idx ↔ key_id-Umrechnung** → [04_config_form.md](04_config_form.md)
|
|
||||||
- **Makro-Slot-Konvention** → [02_device_config.md](02_device_config.md)
|
|
||||||
- **HOST_COMMAND noch nicht implementiert** → [03_tray_app.md](03_tray_app.md)
|
|
||||||
- **Task.Run beim Speichern** → [04_config_form.md](04_config_form.md)
|
|
||||||
Loading…
x
Reference in New Issue
Block a user