96 lines
3.5 KiB
Markdown
96 lines
3.5 KiB
Markdown
# 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) |
|