8 Step macro and profile switching fully working
This commit is contained in:
parent
e01666ab59
commit
24fe162833
@ -8,6 +8,7 @@
|
|||||||
- Verbindungsaufbau und automatischer Reconnect
|
- Verbindungsaufbau und automatischer Reconnect
|
||||||
- Hintergrund-Lese-Thread mit Paket-Assembler
|
- Hintergrund-Lese-Thread mit Paket-Assembler
|
||||||
- Sende-Methoden für alle Protokoll-Operationen
|
- Sende-Methoden für alle Protokoll-Operationen
|
||||||
|
- ACK/NACK-Synchronisation zwischen ReadLoop-Thread und `SendConfig`/`SendMacros`-Task
|
||||||
|
|
||||||
## COM-Port-Erkennung
|
## COM-Port-Erkennung
|
||||||
|
|
||||||
@ -52,20 +53,38 @@ Bei IOException:
|
|||||||
else: break → Disconnect
|
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
|
## Sende-Methoden
|
||||||
|
|
||||||
| Methode | Funktion |
|
| Methode | Rückgabe | Funktion |
|
||||||
|---|---|
|
|---|---|---|
|
||||||
| `Send(pkt)` | Rohe 8-Byte-Übertragung (fire-and-forget, ignoriert Sendefehler) |
|
| `Send(pkt)` | void | Rohe 8-Byte-Übertragung (fire-and-forget) |
|
||||||
| `SetLedOverride(keyId, r, g, b)` | CMD 0x01 |
|
| `SetLedOverride(keyId, r, g, b)` | void | CMD 0x01 |
|
||||||
| `ClearLedOverride(keyId)` | CMD 0x02 |
|
| `ClearLedOverride(keyId)` | void | CMD 0x02 |
|
||||||
| `SetLedBase(keyId, r, g, b)` | CMD 0x03 |
|
| `SetLedBase(keyId, r, g, b)` | void | CMD 0x03 |
|
||||||
| `RequestConfig()` | CMD 0x13 – Board antwortet mit Config-Dump |
|
| `RequestConfig()` | void | CMD 0x13 – Board antwortet mit Config-Dump |
|
||||||
| `RequestMacros()` | CMD 0x23 – Board antwortet mit Makro-Dump |
|
| `RequestMacros()` | void | CMD 0x23 – Board antwortet mit Makro-Dump |
|
||||||
| `SendConfig(cfg)` | BEGIN(0x10) → 38×DATA(0x11) → COMMIT(0x12), 5 ms zwischen Chunks |
|
| `SendConfig(cfg)` | bool | BEGIN(0x10) → 124×DATA(0x11) → COMMIT(0x12), 5 ms zwischen Chunks, wartet auf ACK/NACK |
|
||||||
| `SendMacros(macros)` | BEGIN(0x20) → 43×DATA(0x21) → COMMIT(0x22), 5 ms zwischen Chunks |
|
| `SendMacros(macros)` | bool | BEGIN(0x20) → 86×DATA(0x21) → COMMIT(0x22), 5 ms zwischen Chunks, wartet auf ACK/NACK |
|
||||||
|
|
||||||
`SendConfig` und `SendMacros` blockieren ~400 ms → werden in `Task.Run()` aus `ConfigForm.OnSave()` aufgerufen.
|
`SendConfig` und `SendMacros` blockieren ~1,5 s (Chunks + NVM-Zeit) → werden in `Task.Run()` aus `ConfigForm.OnSave()` aufgerufen.
|
||||||
|
|
||||||
## Events
|
## Events
|
||||||
|
|
||||||
|
|||||||
@ -14,43 +14,58 @@ C#-Spiegel der Firmware-Structs. Muss byte-kompatibel mit `SDeviceConfig` (nvm_c
|
|||||||
|
|
||||||
| Feld | Typ | Inhalt |
|
| Feld | Typ | Inhalt |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `MxActions[20]` | `DeviceAction[]` | Aktionen für MX-Buttons 0–19 |
|
| `ActiveProfileIndex` | `byte` | Aktives Profil (0–2) |
|
||||||
| `EncActions[4,3]` | `DeviceAction[,]` | Encoder [0–3][SW=0/CW=1/CCW=2] |
|
| `GlobalBrightness` | `byte` | Globale LED-Helligkeit (0–255) |
|
||||||
| `LedBase[20]` | `Color[]` | RGB-Basis-LED-Farbe je Button |
|
| `MxActions[20]` | `DeviceAction[]` | Aktionen für MX-Buttons 0–19 (aus aktivem Profil) |
|
||||||
| `LedAnim[20]` | `LedAnimType[]` | Animation je Button |
|
| `EncActions[4,3]` | `DeviceAction[,]` | Encoder [0–3][SW=0/CW=1/CCW=2] (aus aktivem Profil) |
|
||||||
| `LedPeriod[20]` | `ushort[]` | Animationsperiode in ms |
|
| `LedBase[20]` | `Color[]` | RGB-Basis-LED-Farbe je Button (aus aktivem Profil) |
|
||||||
|
| `LedAnim[20]` | `LedAnimType[]` | Animation je Button (aus aktivem Profil) |
|
||||||
|
| `LedPeriod[20]` | `ushort[]` | Animationsperiode in ms (aus aktivem Profil) |
|
||||||
|
|
||||||
### Serialisierungs-Layout (ToBytes / FromBytes, 223 B)
|
### Serialisierungs-Layout (ToBytes / FromBytes, 740 B)
|
||||||
|
|
||||||
```
|
```
|
||||||
Offset 0 4B Magic 0x56503202 (little-endian)
|
Offset 0 4B Magic 0x56503203 (little-endian)
|
||||||
Offset 4 1B Version = 2
|
Offset 4 1B Version = 3
|
||||||
Offset 5 2B CRC16-CCITT über Bytes 7–222 (little-endian)
|
Offset 5 2B CRC16-CCITT über Bytes 7–739 (little-endian)
|
||||||
Offset 7 60B MxActions[20] je 3B: type(1) + data_lo(1) + data_hi(1)
|
Offset 7 1B active_profile (0–2)
|
||||||
Offset 67 36B EncActions[4][3] je 3B
|
Offset 8 1B global_brightness
|
||||||
Offset103 20B LedBase[i].R
|
Offset 9 4B enc_sensitivity[4]
|
||||||
Offset123 20B LedBase[i].G
|
Offset 13 19B Reserve (_reserve)
|
||||||
Offset143 20B LedBase[i].B
|
|
||||||
Offset163 20B LedAnim[i] als byte
|
Profil 0 (Offset 32, 236 B):
|
||||||
Offset183 40B LedPeriod[i] als uint16 little-endian
|
Offset 32 60B MxActions[20] je 3B: type(1) + data_lo(1) + data_hi(1)
|
||||||
|
Offset 92 36B EncActions[4][3] je 3B
|
||||||
|
Offset 128 20B LedBase[i].R
|
||||||
|
Offset 148 20B LedBase[i].G
|
||||||
|
Offset 168 20B LedBase[i].B
|
||||||
|
Offset 188 20B LedBrightness[i]
|
||||||
|
Offset 208 20B LedAnim[i] als byte
|
||||||
|
Offset 228 40B LedPeriod[i] als uint16 little-endian
|
||||||
|
|
||||||
|
Profil 1 (Offset 268, 236 B): identisches Layout
|
||||||
|
Profil 2 (Offset 504, 236 B): identisches Layout
|
||||||
```
|
```
|
||||||
|
|
||||||
### CRC16-CCITT
|
### 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.
|
Polynom `0x1021`, Init `0xFFFF`, über Bytes 7–739 (nach dem CRC-Feld selbst, einschließlich `active_profile`). Muss identisch mit Firmware-Implementierung sein. `DeviceConfig.Crc16()` ist statisch und direkt testbar.
|
||||||
|
|
||||||
### Defaults (entspricht Firmware-Defaults)
|
### Defaults (entspricht Firmware-Defaults)
|
||||||
|
|
||||||
- Alle Aktionen: `None`
|
- Alle Aktionen: `None`
|
||||||
- LEDs: warm-weiß (R=80, G=40, B=0)
|
- LEDs: warm-weiß (R=80, G=40, B=0), Helligkeit 255
|
||||||
- Animation: `ColorCycle` (Regenbogen), Period 4000 ms
|
- Animation: `ColorCycle` (Regenbogen), Period 4000 ms
|
||||||
|
- `active_profile = 0`, `global_brightness = 255`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## DeviceAction
|
## DeviceAction
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public enum ActionType : byte { None=0, HidKey=1, HidConsumer=2, HostCommand=3, Macro=4 }
|
public enum ActionType : byte {
|
||||||
|
None=0, HidKey=1, HidConsumer=2, HostCommand=3, Macro=4, ProfileSwitch=5
|
||||||
|
}
|
||||||
|
|
||||||
public class DeviceAction {
|
public class DeviceAction {
|
||||||
public ActionType Type { get; set; }
|
public ActionType Type { get; set; }
|
||||||
@ -59,10 +74,11 @@ public class DeviceAction {
|
|||||||
// HidConsumer: Consumer Usage ID
|
// HidConsumer: Consumer Usage ID
|
||||||
// HostCommand: Command-ID
|
// HostCommand: Command-ID
|
||||||
// Macro: Slot-Index 0–31
|
// Macro: Slot-Index 0–31
|
||||||
|
// ProfileSwitch: 0–2 = Ziel-Profil, 0xFFFF = nächstes Profil (Zyklus)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`DeviceAction.Display` gibt einen lesbaren String zurück (z.B. `"Strg+C"`, `"Play/Pause"`, `"Makro 3"`).
|
`DeviceAction.Display` gibt einen lesbaren String zurück (z.B. `"Strg+C"`, `"Play/Pause"`, `"→ Profil 2"`, `"→ Nächstes Profil"`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -71,7 +87,7 @@ public class DeviceAction {
|
|||||||
```csharp
|
```csharp
|
||||||
public class MacroTable {
|
public class MacroTable {
|
||||||
public const int Slots = 32;
|
public const int Slots = 32;
|
||||||
public const int MaxSteps = 4;
|
public const int MaxSteps = 8;
|
||||||
public MacroStep[,] Steps { get; } // [slot][step]
|
public MacroStep[,] Steps { get; } // [slot][step]
|
||||||
}
|
}
|
||||||
public class MacroStep {
|
public class MacroStep {
|
||||||
@ -87,9 +103,9 @@ public class MacroStep {
|
|||||||
| 0–19 | MX-Button `mxIdx` (`MacroTable.SlotForMx(mxIdx)`) |
|
| 0–19 | MX-Button `mxIdx` (`MacroTable.SlotForMx(mxIdx)`) |
|
||||||
| 20–31 | Encoder: `20 + enc * 3 + actIdx` (`MacroTable.SlotForEncoder(enc, actIdx)`) |
|
| 20–31 | Encoder: `20 + enc * 3 + actIdx` (`MacroTable.SlotForEncoder(enc, actIdx)`) |
|
||||||
|
|
||||||
### Serialisierung (256 B)
|
### Serialisierung (512 B)
|
||||||
|
|
||||||
32 Slots × 4 Steps × 2 B = 256 B. Keycode zuerst, dann Modifier. Kein Magic/CRC (Board akzeptiert jeden Inhalt).
|
32 Slots × 8 Steps × 2 B = 512 B. Keycode zuerst, dann Modifier. Kein Magic/CRC (Board akzeptiert jeden Inhalt).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -101,4 +117,4 @@ public enum LedAnimType : byte {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
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).
|
Werte entsprechen `LEDAnim` in der Firmware. `FADE_IN` (3) und `FADE_OUT` (4) existieren in der Firmware aber nicht in der GUI (nicht konfigurierbar).
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
- Tray-Icon mit Verbindungsstatus und Kontextmenü
|
- Tray-Icon mit Verbindungsstatus und Kontextmenü
|
||||||
- Empfang und Routing aller Board-Events (via `SerialManager.PacketReceived`)
|
- Empfang und Routing aller Board-Events (via `SerialManager.PacketReceived`)
|
||||||
- Config/Makro-Dump-Empfang (chunked, via `_rxConfigBuf` / `_rxMacroBuf`)
|
- Config/Makro-Dump-Empfang (chunked, via `_rxConfigBuf` / `_rxMacroBuf`)
|
||||||
|
- ACK/NACK-Weiterleitung an SerialManager
|
||||||
- Öffnen des `ConfigForm` (nur eine Instanz gleichzeitig)
|
- Öffnen des `ConfigForm` (nur eine Instanz gleichzeitig)
|
||||||
|
|
||||||
## Tray-Menü
|
## Tray-Menü
|
||||||
@ -30,19 +31,22 @@ Icon und Tooltip spiegeln den Verbindungsstatus:
|
|||||||
|
|
||||||
| Event-ID | Aktion |
|
| Event-ID | Aktion |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `EvtConfigBegin` | `_rxConfigBuf = new byte[223]` |
|
| `EvtConfigBegin` | `_rxConfigBuf = new byte[740]` |
|
||||||
| `EvtConfigData` | Chunk in `_rxConfigBuf` eintragen (`KeyId * 6` = Byte-Offset) |
|
| `EvtConfigData` | Chunk in `_rxConfigBuf` eintragen (`KeyId * 6` = Byte-Offset) |
|
||||||
| `EvtConfigEnd` | `DeviceConfig.FromBytes()` → `ConfigForm.RefreshAll()` |
|
| `EvtConfigEnd` | `DeviceConfig.FromBytes()` → `ConfigForm.RefreshAll()` |
|
||||||
| `EvtMacroBegin` | `_rxMacroBuf = new byte[256]` |
|
| `EvtConfigAck` | `serial.SignalConfigAck()` — gibt SendConfig()-Thread frei |
|
||||||
|
| `EvtConfigNack` | `serial.SignalConfigNack()` — gibt SendConfig()-Thread frei (Fehler) |
|
||||||
|
| `EvtMacroBegin` | `_rxMacroBuf = new byte[512]` |
|
||||||
| `EvtMacroData` | Chunk in `_rxMacroBuf` eintragen |
|
| `EvtMacroData` | Chunk in `_rxMacroBuf` eintragen |
|
||||||
| `EvtMacroEnd` | `MacroTable.FromBytes()` |
|
| `EvtMacroEnd` | `MacroTable.FromBytes()` |
|
||||||
|
| `EvtMacroAck` | `serial.SignalMacroAck()` — gibt SendMacros()-Thread frei |
|
||||||
|
| `EvtMacroNack` | `serial.SignalMacroNack()` — gibt SendMacros()-Thread frei (Fehler) |
|
||||||
| `EvtPong` | MessageBox "Ping OK" |
|
| `EvtPong` | MessageBox "Ping OK" |
|
||||||
| `EvtConfigAck` | MessageBox "Config gespeichert" |
|
|
||||||
| `EvtConfigNack` | MessageBox "Config FEHLER" |
|
|
||||||
| `EvtMacroAck` | MessageBox "Makros gespeichert" |
|
|
||||||
| `EvtKeyDown` | TODO: HOST_COMMAND-Aktion ausführen |
|
| `EvtKeyDown` | TODO: HOST_COMMAND-Aktion ausführen |
|
||||||
| `EvtEncCw/Ccw` | TODO: Encoder HOST_COMMAND |
|
| `EvtEncCw/Ccw` | TODO: Encoder HOST_COMMAND |
|
||||||
|
|
||||||
|
ACK/NACK-Events zeigen keine eigene MessageBox mehr — das Ergebnis wird nach Abschluss beider Transfers gebündelt in `ConfigForm.OnSave()` angezeigt.
|
||||||
|
|
||||||
## Verbindungslebenszyklus
|
## Verbindungslebenszyklus
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@ -22,13 +22,34 @@ public ushort ResultPeriod { get; } // Periode in ms
|
|||||||
| HID Tastatur | `_hidKeyPanel` | Capture-Button + Strg/Shift/Alt/Win-Checkboxen |
|
| HID Tastatur | `_hidKeyPanel` | Capture-Button + Strg/Shift/Alt/Win-Checkboxen |
|
||||||
| HID Consumer | `_consumerPanel` | Dropdown mit 12 Medien-Aktionen |
|
| HID Consumer | `_consumerPanel` | Dropdown mit 12 Medien-Aktionen |
|
||||||
| Host Command | `_cmdPanel` | TextBox für numerische Command-ID |
|
| Host Command | `_cmdPanel` | TextBox für numerische Command-ID |
|
||||||
| Makro | `_macroPanel` | 4 Step-Buttons + je Strg/Shift/Alt-Checkboxen |
|
| Makro | `_macroPanel` | 8 Step-Buttons + je Strg/Shift/Alt-Checkboxen |
|
||||||
|
| Profil wechseln | `_profilePanel` | Dropdown: "Nächstes Profil (Zyklus)" / "Profil 1" / "Profil 2" / "Profil 3" |
|
||||||
| Keine | — | Alle Panels ausgeblendet |
|
| Keine | — | Alle Panels ausgeblendet |
|
||||||
|
|
||||||
LED-Panels (`_colorPanel`, `_animPanel`) erscheinen zusätzlich wenn `showColor=true` (nur MX-Buttons, nicht Encoder).
|
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.
|
`UpdateLayout()` repositioniert LED-Panels und passt `ClientSize` dynamisch an wenn der Typ gewechselt wird.
|
||||||
|
|
||||||
|
## Profil-Panel
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Items in _profileCombo:
|
||||||
|
// Index 0: "Nächstes Profil (Zyklus)" → Data = 0xFFFF
|
||||||
|
// Index 1: "Profil 1" → Data = 0
|
||||||
|
// Index 2: "Profil 2" → Data = 1
|
||||||
|
// Index 3: "Profil 3" → Data = 2
|
||||||
|
|
||||||
|
// Initialbelegung:
|
||||||
|
_profileCombo.SelectedIndex = action.Data == 0xFFFF ? 0 : action.Data + 1;
|
||||||
|
|
||||||
|
// In OnOk():
|
||||||
|
data = _profileCombo.SelectedIndex == 0
|
||||||
|
? (ushort)0xFFFF
|
||||||
|
: (ushort)(_profileCombo.SelectedIndex - 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
Im Board wird `0xFFFF` als `(uint8_t)0xFF` gespeichert und in der Firmware als "nächstes Profil" interpretiert.
|
||||||
|
|
||||||
## Tasten-Capture (HID-Modus)
|
## Tasten-Capture (HID-Modus)
|
||||||
|
|
||||||
1. Benutzer klickt "Taste drücken..."
|
1. Benutzer klickt "Taste drücken..."
|
||||||
@ -41,7 +62,7 @@ WinForms behandelt Pfeil- und Enter-Tasten als "Dialog Keys" in `ProcessDialogKe
|
|||||||
|
|
||||||
## Makro-Capture
|
## 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]`.
|
Jeder der 8 Steps hat einen eigenen Capture-Button. `_captureStep` (0–7, -1 = inaktiv) zeigt welcher Step gerade aufnimmt. Capture-Logik identisch mit HID-Modus, schreibt in `_stepKeycodes[captureStep]`.
|
||||||
|
|
||||||
## Schlüssellookup (layout-unabhängig)
|
## Schlüssellookup (layout-unabhängig)
|
||||||
|
|
||||||
|
|||||||
@ -291,13 +291,25 @@ public class ConfigForm : Form
|
|||||||
// SendConfig + SendMacros blockieren ~400ms → Background-Thread
|
// SendConfig + SendMacros blockieren ~400ms → Background-Thread
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
_serial.SendConfig(_config);
|
bool cfgOk = _serial.SendConfig(_config); // wartet intern auf CONFIG_ACK/NACK
|
||||||
Thread.Sleep(50); // kurze Pause zwischen Config- und Makro-Transfer
|
bool macroOk = _serial.SendMacros(_macros); // wartet intern auf MACRO_ACK/NACK
|
||||||
_serial.SendMacros(_macros);
|
|
||||||
InvokeOnUi(() =>
|
InvokeOnUi(() =>
|
||||||
{
|
{
|
||||||
_saveBtn.Text = "Auf Board speichern";
|
_saveBtn.Text = "Auf Board speichern";
|
||||||
_saveBtn.Enabled = _serial.IsConnected;
|
_saveBtn.Enabled = _serial.IsConnected;
|
||||||
|
if (cfgOk && macroOk)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Konfiguration erfolgreich gespeichert.",
|
||||||
|
"VersaPad", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string detail = (!cfgOk && !macroOk) ? "Config und Makros"
|
||||||
|
: !cfgOk ? "Config"
|
||||||
|
: "Makros";
|
||||||
|
MessageBox.Show($"Speichern fehlgeschlagen ({detail}).\nBoard hat NACK gesendet oder nicht geantwortet.",
|
||||||
|
"VersaPad – Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,6 +51,7 @@ public static class Protocol
|
|||||||
public const byte EvtMacroBegin = 0x96; // Beginn Makro-Dump (Data[1] = Chunks)
|
public const byte EvtMacroBegin = 0x96; // Beginn Makro-Dump (Data[1] = Chunks)
|
||||||
public const byte EvtMacroData = 0x97; // Makro-Chunk (Data[1] = Index, Data[2..7] = 6B)
|
public const byte EvtMacroData = 0x97; // Makro-Chunk (Data[1] = Index, Data[2..7] = 6B)
|
||||||
public const byte EvtMacroEnd = 0x98; // Makro-Dump vollständig
|
public const byte EvtMacroEnd = 0x98; // Makro-Dump vollständig
|
||||||
|
public const byte EvtMacroNack = 0x99; // Makro-Tabelle: NVM-Fehler
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ein 8-Byte-Paket mit benannten Accessoren.
|
// Ein 8-Byte-Paket mit benannten Accessoren.
|
||||||
|
|||||||
@ -39,6 +39,12 @@ public class SerialManager : IDisposable
|
|||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private bool _waitingAfterDisconnect; // Backoff nach Trennung aktiv
|
private bool _waitingAfterDisconnect; // Backoff nach Trennung aktiv
|
||||||
|
|
||||||
|
// Synchronisation: Board-ACK/NACK abwarten bevor nächster Transfer startet
|
||||||
|
private readonly SemaphoreSlim _configAckGate = new(0, 1);
|
||||||
|
private readonly SemaphoreSlim _macroAckGate = new(0, 1);
|
||||||
|
private volatile bool _configAckOk;
|
||||||
|
private volatile bool _macroAckOk;
|
||||||
|
|
||||||
public bool IsConnected => _port?.IsOpen == true;
|
public bool IsConnected => _port?.IsOpen == true;
|
||||||
|
|
||||||
public SerialManager()
|
public SerialManager()
|
||||||
@ -221,8 +227,35 @@ public class SerialManager : IDisposable
|
|||||||
public void RequestMacros()
|
public void RequestMacros()
|
||||||
=> Send(new SerialPacket(Protocol.CmdMacroRead));
|
=> Send(new SerialPacket(Protocol.CmdMacroRead));
|
||||||
|
|
||||||
|
// Wird von TrayApp aufgerufen wenn das Board CONFIG_ACK/NACK oder MACRO_ACK/NACK sendet.
|
||||||
|
// Gibt den wartenden SendConfig()/SendMacros()-Thread frei.
|
||||||
|
public void SignalConfigAck()
|
||||||
|
{
|
||||||
|
_configAckOk = true;
|
||||||
|
if (_configAckGate.CurrentCount == 0) _configAckGate.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SignalConfigNack()
|
||||||
|
{
|
||||||
|
_configAckOk = false;
|
||||||
|
if (_configAckGate.CurrentCount == 0) _configAckGate.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SignalMacroAck()
|
||||||
|
{
|
||||||
|
_macroAckOk = true;
|
||||||
|
if (_macroAckGate.CurrentCount == 0) _macroAckGate.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SignalMacroNack()
|
||||||
|
{
|
||||||
|
_macroAckOk = false;
|
||||||
|
if (_macroAckGate.CurrentCount == 0) _macroAckGate.Release();
|
||||||
|
}
|
||||||
|
|
||||||
// Makro-Tabelle (256 Bytes) in 6-Byte-Chunks senden.
|
// Makro-Tabelle (256 Bytes) in 6-Byte-Chunks senden.
|
||||||
public void SendMacros(MacroTable macros)
|
// Gibt true zurück wenn das Board MACRO_ACK gesendet hat.
|
||||||
|
public bool SendMacros(MacroTable macros)
|
||||||
{
|
{
|
||||||
byte[] data = macros.ToBytes();
|
byte[] data = macros.ToBytes();
|
||||||
const int payload = 6;
|
const int payload = 6;
|
||||||
@ -252,14 +285,24 @@ public class SerialManager : IDisposable
|
|||||||
File.AppendAllText(Path.Combine(Path.GetTempPath(), "versapad_rx.txt"),
|
File.AppendAllText(Path.Combine(Path.GetTempPath(), "versapad_rx.txt"),
|
||||||
$"{DateTime.Now:HH:mm:ss.fff} TX MacroCommit (sent {chunks} chunks)\n");
|
$"{DateTime.Now:HH:mm:ss.fff} TX MacroCommit (sent {chunks} chunks)\n");
|
||||||
|
|
||||||
|
// Sicherstellen dass das Gate leer ist bevor wir warten
|
||||||
|
while (_macroAckGate.CurrentCount > 0) _macroAckGate.Wait(0);
|
||||||
|
|
||||||
Thread.Sleep(10);
|
Thread.Sleep(10);
|
||||||
Send(new SerialPacket(Protocol.CmdMacroCommit));
|
Send(new SerialPacket(Protocol.CmdMacroCommit));
|
||||||
|
|
||||||
|
// Auf MACRO_ACK/NACK warten – Board braucht bis zu ~600ms für NVM-Erase + Write (2 Rows)
|
||||||
|
bool gateAcquired = _macroAckGate.Wait(3000);
|
||||||
|
bool success = gateAcquired && _macroAckOk;
|
||||||
|
File.AppendAllText(Path.Combine(Path.GetTempPath(), "versapad_rx.txt"),
|
||||||
|
$"{DateTime.Now:HH:mm:ss.fff} TX MacroAck gateAcquired={gateAcquired} ok={success}\n");
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config in 6-Byte-Chunks an das Board senden.
|
// Config in 6-Byte-Chunks an das Board senden.
|
||||||
// Protokoll: BEGIN → n×DATA → COMMIT
|
// Protokoll: BEGIN → n×DATA → COMMIT
|
||||||
// Board schreibt nach COMMIT in den NVM (Firmware-Seite noch TODO).
|
// Gibt true zurück wenn das Board CONFIG_ACK gesendet hat.
|
||||||
public void SendConfig(DeviceConfig config)
|
public bool SendConfig(DeviceConfig config)
|
||||||
{
|
{
|
||||||
byte[] data = config.ToBytes();
|
byte[] data = config.ToBytes();
|
||||||
const int payload = 6; // Nutzbytes pro Paket (8 - 2 Header-Bytes)
|
const int payload = 6; // Nutzbytes pro Paket (8 - 2 Header-Bytes)
|
||||||
@ -289,8 +332,18 @@ public class SerialManager : IDisposable
|
|||||||
File.AppendAllText(Path.Combine(Path.GetTempPath(), "versapad_rx.txt"),
|
File.AppendAllText(Path.Combine(Path.GetTempPath(), "versapad_rx.txt"),
|
||||||
$"{DateTime.Now:HH:mm:ss.fff} TX ConfigCommit (sent {chunks} chunks)\n");
|
$"{DateTime.Now:HH:mm:ss.fff} TX ConfigCommit (sent {chunks} chunks)\n");
|
||||||
|
|
||||||
|
// Sicherstellen dass das Gate leer ist bevor wir warten
|
||||||
|
while (_configAckGate.CurrentCount > 0) _configAckGate.Wait(0);
|
||||||
|
|
||||||
Thread.Sleep(10);
|
Thread.Sleep(10);
|
||||||
Send(new SerialPacket(Protocol.CmdConfigCommit));
|
Send(new SerialPacket(Protocol.CmdConfigCommit));
|
||||||
|
|
||||||
|
// Auf CONFIG_ACK/NACK warten – Board braucht bis zu ~1s für NVM-Erase + Write (3 Rows)
|
||||||
|
bool gateAcquired = _configAckGate.Wait(3000);
|
||||||
|
bool success = gateAcquired && _configAckOk;
|
||||||
|
File.AppendAllText(Path.Combine(Path.GetTempPath(), "versapad_rx.txt"),
|
||||||
|
$"{DateTime.Now:HH:mm:ss.fff} TX ConfigAck gateAcquired={gateAcquired} ok={success}\n");
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── IDisposable ───────────────────────────────────────────────────────────
|
// ── IDisposable ───────────────────────────────────────────────────────────
|
||||||
|
|||||||
@ -128,13 +128,11 @@ public class TrayApp : ApplicationContext
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Protocol.EvtConfigAck:
|
case Protocol.EvtConfigAck:
|
||||||
MessageBox.Show("Config erfolgreich gespeichert!",
|
_serial.SignalConfigAck(); // SendConfig()-Thread freigeben
|
||||||
"VersaPad", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Protocol.EvtConfigNack:
|
case Protocol.EvtConfigNack:
|
||||||
MessageBox.Show("Config FEHLER: CRC/Magic ungültig.\nÜbertragung fehlgeschlagen.",
|
_serial.SignalConfigNack(); // SendConfig()-Thread freigeben (Fehler)
|
||||||
"VersaPad", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// ── Config-Dump vom Board ─────────────────────────────────────────
|
// ── Config-Dump vom Board ─────────────────────────────────────────
|
||||||
@ -163,8 +161,11 @@ public class TrayApp : ApplicationContext
|
|||||||
|
|
||||||
// ── Makro-Dump vom Board ──────────────────────────────────────────
|
// ── Makro-Dump vom Board ──────────────────────────────────────────
|
||||||
case Protocol.EvtMacroAck:
|
case Protocol.EvtMacroAck:
|
||||||
MessageBox.Show("Makros erfolgreich gespeichert!",
|
_serial.SignalMacroAck(); // SendMacros()-Thread freigeben
|
||||||
"VersaPad", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
break;
|
||||||
|
|
||||||
|
case Protocol.EvtMacroNack:
|
||||||
|
_serial.SignalMacroNack(); // SendMacros()-Thread freigeben (Fehler)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Protocol.EvtMacroBegin:
|
case Protocol.EvtMacroBegin:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user