# 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 - ACK/NACK-Synchronisation zwischen ReadLoop-Thread und `SendConfig`/`SendMacros`-Task ## 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 ``` ## ACK/NACK-Synchronisation `SendConfig` und `SendMacros` blockieren nach COMMIT auf ein `SemaphoreSlim`-Gate bis das Board antwortet: ``` _configAckGate / _macroAckGate – SemaphoreSlim(0, 1) _configAckOk / _macroAckOk – volatile bool (true = ACK, false = NACK) ``` | Methode | Aufruf durch | Wirkung | |---|---|---| | `SignalConfigAck()` | TrayApp bei EvtConfigAck | `_configAckOk = true`, Gate freigeben | | `SignalConfigNack()` | TrayApp bei EvtConfigNack | `_configAckOk = false`, Gate freigeben | | `SignalMacroAck()` | TrayApp bei EvtMacroAck | `_macroAckOk = true`, Gate freigeben | | `SignalMacroNack()` | TrayApp bei EvtMacroNack | `_macroAckOk = false`, Gate freigeben | `SendConfig()` und `SendMacros()` geben `bool` zurück (`true` = ACK erhalten, `false` = NACK oder Timeout nach 3 s). ## Sende-Methoden | Methode | Rückgabe | Funktion | |---|---|---| | `Send(pkt)` | void | Rohe 8-Byte-Übertragung (fire-and-forget) | | `SetLedOverride(keyId, r, g, b)` | void | CMD 0x01 | | `ClearLedOverride(keyId)` | void | CMD 0x02 | | `SetLedBase(keyId, r, g, b)` | void | CMD 0x03 | | `RequestConfig()` | void | CMD 0x13 – Board antwortet mit Config-Dump | | `RequestMacros()` | void | CMD 0x23 – Board antwortet mit Makro-Dump | | `SendConfig(cfg)` | bool | BEGIN(0x10) → 124×DATA(0x11) → COMMIT(0x12), 5 ms zwischen Chunks, wartet auf ACK/NACK | | `SendMacros(macros)` | bool | BEGIN(0x20) → 86×DATA(0x21) → COMMIT(0x22), 5 ms zwischen Chunks, wartet auf ACK/NACK | `SendConfig` und `SendMacros` blockieren ~1,5 s (Chunks + NVM-Zeit) → 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) |