diff --git a/src/ActionDialog.cs b/src/ActionDialog.cs index a1b6432..2ce9c7a 100644 --- a/src/ActionDialog.cs +++ b/src/ActionDialog.cs @@ -35,6 +35,10 @@ public class ActionDialog : Form private readonly Panel _cmdPanel; private readonly TextBox _cmdBox; + // Profil wechseln + private readonly Panel _profilePanel; + private readonly ComboBox _profileCombo; + // LED-Farbe private readonly Panel _colorPanel; private readonly Button _colorBtn; @@ -50,11 +54,11 @@ public class ActionDialog : Form private readonly MacroTable _macros; private readonly int _macroSlot; // Je Step: [CaptureBtn, CheckCtrl, CheckShift, CheckAlt] - private readonly Button[] _stepBtns = new Button[4]; - private readonly CheckBox[] _stepCtrl = new CheckBox[4]; - private readonly CheckBox[] _stepShift = new CheckBox[4]; - private readonly CheckBox[] _stepAlt = new CheckBox[4]; - private readonly byte[] _stepKeycodes = new byte[4]; + private readonly Button[] _stepBtns = new Button[8]; + private readonly CheckBox[] _stepCtrl = new CheckBox[8]; + private readonly CheckBox[] _stepShift = new CheckBox[8]; + private readonly CheckBox[] _stepAlt = new CheckBox[8]; + private readonly byte[] _stepKeycodes = new byte[8]; private int _captureStep = -1; // -1 = nicht im Capture-Modus // Capture-Zustand @@ -220,7 +224,7 @@ public class ActionDialog : Form _showColor = showColor; // Bestehende Makro-Steps aus der Tabelle laden - for (int i = 0; i < 4; i++) + for (int i = 0; i < 8; i++) _stepKeycodes[i] = _macros.Steps[_macroSlot, i].Keycode; // Formular-Grundeinstellungen @@ -240,7 +244,7 @@ public class ActionDialog : Form DropDownStyle = ComboBoxStyle.DropDownList, }; _typeCombo.Items.AddRange(new object[] - { "Keine Aktion", "HID Tastatur", "HID Consumer", "Host Command", "Makro" }); + { "Keine Aktion", "HID Tastatur", "HID Consumer", "Host Command", "Makro", "Profil wechseln" }); _typeCombo.SelectedIndex = (int)action.Type; _typeCombo.SelectedIndexChanged += OnTypeChanged; @@ -320,6 +324,22 @@ public class ActionDialog : Form }; _cmdPanel.Controls.AddRange(new Control[] { cmdLabel, _cmdBox }); + // ── Profil-Panel ────────────────────────────────────────────────────── + _profilePanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 30) }; + var profileLabel = new Label { Text = "Ziel-Profil:", Location = new Point(0, 8), AutoSize = true }; + _profileCombo = new ComboBox + { + Location = new Point(90, 4), + Width = 200, + DropDownStyle = ComboBoxStyle.DropDownList, + }; + _profileCombo.Items.AddRange(new object[] + { "Nächstes Profil (Zyklus)", "Profil 1", "Profil 2", "Profil 3" }); + _profileCombo.SelectedIndex = action.Type == ActionType.ProfileSwitch + ? (action.Data == 0xFFFF ? 0 : Math.Clamp((int)action.Data + 1, 1, 3)) + : 0; + _profilePanel.Controls.AddRange(new Control[] { profileLabel, _profileCombo }); + // ── Farb-Panel ──────────────────────────────────────────────────────── _colorPanel = new Panel { @@ -378,8 +398,8 @@ public class ActionDialog : Form _animPanel.Controls.AddRange(new Control[] { animLabel, _animCombo, periodLabel, _periodCombo }); - // ── Makro-Panel (4 Steps) ───────────────────────────────────────────── - _macroPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 4 * 30 + 22) }; + // ── Makro-Panel (8 Steps) ───────────────────────────────────────────── + _macroPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 8 * 30 + 22) }; var macroHint = new Label { @@ -391,7 +411,7 @@ public class ActionDialog : Form }; _macroPanel.Controls.Add(macroHint); - for (int step = 0; step < 4; step++) + for (int step = 0; step < 8; step++) { int s = step; int y2 = 30 + step * 30; @@ -454,7 +474,7 @@ public class ActionDialog : Form Controls.AddRange(new Control[] { typeLabel, _typeCombo, - _hidKeyPanel, _consumerPanel, _cmdPanel, _macroPanel, + _hidKeyPanel, _consumerPanel, _cmdPanel, _profilePanel, _macroPanel, _colorPanel, _animPanel, _okBtn, _cancelBtn, }); @@ -481,6 +501,7 @@ public class ActionDialog : Form _hidKeyPanel.Visible = t == ActionType.HidKey; _consumerPanel.Visible = t == ActionType.HidConsumer; _cmdPanel.Visible = t == ActionType.HostCommand; + _profilePanel.Visible = t == ActionType.ProfileSwitch; _macroPanel.Visible = t == ActionType.Macro; _colorPanel.Visible = _showColor; _animPanel.Visible = _showColor; @@ -496,11 +517,12 @@ public class ActionDialog : Form int contentH = t switch { - ActionType.HidKey => 110, - ActionType.HidConsumer => 30, - ActionType.HostCommand => 30, - ActionType.Macro => 4 * 30 + 22, - _ => 0, + ActionType.HidKey => 110, + ActionType.HidConsumer => 30, + ActionType.HostCommand => 30, + ActionType.ProfileSwitch => 30, + ActionType.Macro => 8 * 30 + 22, + _ => 0, }; int ledY = 48 + contentH + 10; @@ -710,12 +732,18 @@ public class ActionDialog : Form return; } break; + + case ActionType.ProfileSwitch: + data = _profileCombo.SelectedIndex == 0 + ? (ushort)0xFFFF // Zyklus: Firmware rechnet (current+1)%3 + : (ushort)(_profileCombo.SelectedIndex - 1); // Profil 0/1/2 + break; } if (type == ActionType.Macro) { // Steps in die Makro-Tabelle schreiben - for (int i = 0; i < 4; i++) + for (int i = 0; i < 8; i++) { byte mod = 0; if (_stepCtrl[i].Checked) mod |= 0x01; diff --git a/src/ConfigForm.cs b/src/ConfigForm.cs index 3f3967d..e15583b 100644 --- a/src/ConfigForm.cs +++ b/src/ConfigForm.cs @@ -26,6 +26,7 @@ public class ConfigForm : Form private readonly Button[] _btnGrid = new Button[20]; private readonly Button[,] _encBtns = new Button[4, 3]; private Button _saveBtn = null!; + private ComboBox _profileCombo = null!; public ConfigForm(DeviceConfig config, MacroTable macros, SerialManager serial) { @@ -40,6 +41,7 @@ public class ConfigForm : Form ClientSize = new Size(520, 480); int y = 12; + y = BuildProfileBar(y); y = BuildButtonGrid(y); y = BuildEncoderPanel(y); BuildFooter(y); @@ -50,6 +52,35 @@ public class ConfigForm : Form _saveBtn.Enabled = _serial.IsConnected; } + // ── Profil-Leiste ───────────────────────────────────────────────────────── + + private int BuildProfileBar(int startY) + { + var label = new Label + { + Text = "Profil:", + Location = new Point(12, startY + 4), + AutoSize = true, + }; + + _profileCombo = new ComboBox + { + Location = new Point(60, startY), + Width = 160, + DropDownStyle = ComboBoxStyle.DropDownList, + }; + _profileCombo.Items.AddRange(new object[] { "Profil 1", "Profil 2", "Profil 3" }); + _profileCombo.SelectedIndex = _config.ActiveProfileIndex; + _profileCombo.SelectedIndexChanged += (_, _) => + { + _config.ActiveProfileIndex = (byte)_profileCombo.SelectedIndex; + RefreshAll(); + }; + + Controls.AddRange(new Control[] { label, _profileCombo }); + return startY + 34; + } + // ── Tasten-Grid (4 Spalten × 5 Zeilen) ─────────────────────────────────── private int BuildButtonGrid(int startY) @@ -390,9 +421,11 @@ internal static class Extensions public static string TypeDescription(this DeviceAction a) => a.Type switch { - ActionType.HidKey => "HID Tastatur-Keycode", - ActionType.HidConsumer => "HID Consumer Control (Media/Volume)", - ActionType.HostCommand => "Host Command – App führt aus", - _ => "Keine Aktion", + ActionType.HidKey => "HID Tastatur-Keycode", + ActionType.HidConsumer => "HID Consumer Control (Media/Volume)", + ActionType.HostCommand => "Host Command – App führt aus", + ActionType.Macro => "Makro-Sequenz", + ActionType.ProfileSwitch => "Profil wechseln", + _ => "Keine Aktion", }; } diff --git a/src/ConfigJson.cs b/src/ConfigJson.cs index fea8024..8bf0a59 100644 --- a/src/ConfigJson.cs +++ b/src/ConfigJson.cs @@ -1,12 +1,14 @@ // ConfigJson.cs // JSON-Import/Export für DeviceConfig. // -// Format (menschenlesbar, kommentiert mit Feldnamen): +// Format (menschenlesbar): // { -// "version": 2, +// "version": 3, +// "profile": 0, // "buttons": [ // { "index": 0, "action": { "type": "HidKey", "data": 260 }, -// "led": { "r": 80, "g": 40, "b": 0, "anim": "ColorCycle", "period_ms": 4000 } }, +// "led": { "r": 80, "g": 40, "b": 0, "brightness": 255, +// "anim": "ColorCycle", "period_ms": 4000 } }, // ... // ], // "encoders": [ @@ -17,6 +19,8 @@ // ... // ] // } +// +// Import/Export bezieht sich immer auf das aktive Profil. using System.Text.Json; using System.Text.Json.Serialization; @@ -37,6 +41,7 @@ internal static class ConfigJson var doc = new ConfigJsonDoc { Version = DeviceConfig.Version, + Profile = cfg.ActiveProfileIndex, Buttons = new ButtonEntry[20], Encoders = new EncoderEntry[4], }; @@ -49,11 +54,12 @@ internal static class ConfigJson Action = ActionToJson(cfg.MxActions[i]), Led = new LedEntry { - R = cfg.LedBase[i].R, - G = cfg.LedBase[i].G, - B = cfg.LedBase[i].B, - Anim = cfg.LedAnim[i], - PeriodMs = cfg.LedPeriod[i], + R = cfg.LedBase[i].R, + G = cfg.LedBase[i].G, + B = cfg.LedBase[i].B, + Brightness = cfg.LedBrightness[i], + Anim = cfg.LedAnim[i], + PeriodMs = cfg.LedPeriod[i], }, }; } @@ -89,9 +95,10 @@ internal static class ConfigJson ActionFromJson(b.Action, cfg.MxActions[b.Index]); if (b.Led != null) { - cfg.LedBase[b.Index] = Color.FromArgb(b.Led.R, b.Led.G, b.Led.B); - cfg.LedAnim[b.Index] = b.Led.Anim; - cfg.LedPeriod[b.Index] = b.Led.PeriodMs; + cfg.LedBase[b.Index] = Color.FromArgb(b.Led.R, b.Led.G, b.Led.B); + cfg.LedBrightness[b.Index] = b.Led.Brightness; + cfg.LedAnim[b.Index] = b.Led.Anim; + cfg.LedPeriod[b.Index] = b.Led.PeriodMs; } } } @@ -124,8 +131,9 @@ internal static class ConfigJson private class ConfigJsonDoc { - [JsonPropertyName("version")] public byte Version { get; set; } - [JsonPropertyName("buttons")] public ButtonEntry[]? Buttons { get; set; } + [JsonPropertyName("version")] public byte Version { get; set; } + [JsonPropertyName("profile")] public byte Profile { get; set; } + [JsonPropertyName("buttons")] public ButtonEntry[]? Buttons { get; set; } [JsonPropertyName("encoders")] public EncoderEntry[]? Encoders { get; set; } } @@ -152,10 +160,11 @@ internal static class ConfigJson private class LedEntry { - [JsonPropertyName("r")] public byte R { get; set; } - [JsonPropertyName("g")] public byte G { get; set; } - [JsonPropertyName("b")] public byte B { get; set; } - [JsonPropertyName("anim")] public LedAnimType Anim { get; set; } - [JsonPropertyName("period_ms")] public ushort PeriodMs { get; set; } + [JsonPropertyName("r")] public byte R { get; set; } + [JsonPropertyName("g")] public byte G { get; set; } + [JsonPropertyName("b")] public byte B { get; set; } + [JsonPropertyName("brightness")] public byte Brightness { get; set; } = 255; + [JsonPropertyName("anim")] public LedAnimType Anim { get; set; } + [JsonPropertyName("period_ms")] public ushort PeriodMs { get; set; } } } diff --git a/src/DeviceConfig.cs b/src/DeviceConfig.cs index 10b9312..aa02aec 100644 --- a/src/DeviceConfig.cs +++ b/src/DeviceConfig.cs @@ -1,17 +1,32 @@ // DeviceConfig.cs -// C#-Spiegel der SDeviceConfig-Struktur aus der Firmware (nvm_config.h). +// C#-Spiegel der Firmware-Structs. Muss byte-kompatibel mit SDeviceConfig (nvm_config.h) +// und SMacroTable (macro_config.h) sein. // -// Serialisiertes Layout (packed, 223 Bytes): -// Offset 0 4B Magic 0x56503202 -// Offset 4 1B Version 2 -// Offset 5 2B CRC16 (CCITT über Bytes 7–222) -// Offset 7 60B mx_actions[20] je 3B: type(1) + data_lo(1) + data_hi(1) -// Offset 67 36B enc_actions[4][3] je 3B -// Offset103 20B led_r[20] -// Offset123 20B led_g[20] -// Offset143 20B led_b[20] -// Offset163 20B led_anim[20] je 1B (LedAnimType) -// Offset183 40B led_period_ms[20] je 2B (uint16, little-endian) +// Serialisiertes Config-Layout (740 B, packed): +// ── Globaler Header (32B) ────────────────────────────────────────────────── +// Offset 0 4B Magic 0x56503203 +// Offset 4 1B Version = 3 +// Offset 5 2B CRC16-CCITT über Bytes 7–739 +// Offset 7 1B ActiveProfile (0–2) +// Offset 8 1B GlobalBrightness (0–255) +// Offset 9 4B EncSensitivity[4] +// Offset 13 19B Reserve +// ── Profil 0 (236B, ab Offset 32) ───────────────────────────────────────── +// ── Profil 1 (236B, ab Offset 268) ──────────────────────────────────────── +// ── Profil 2 (236B, ab Offset 504) ──────────────────────────────────────── +// +// Pro Profil (236B): +// Offset 0 60B MxActions[20] je 3B: type(1) + data_lo(1) + data_hi(1) +// Offset 60 36B EncActions[4][3] je 3B +// Offset 96 20B LedBase[i].R +// Offset116 20B LedBase[i].G +// Offset136 20B LedBase[i].B +// Offset156 20B LedBrightness[i] per-LED 0–255 +// Offset176 20B LedAnim[i] +// Offset196 40B LedPeriod[i] uint16 little-endian +// +// MacroTable-Layout (512 B): +// 32 Slots × 8 Steps × 2B = 512B using System.Drawing; @@ -19,11 +34,12 @@ namespace VersaGUI; public enum ActionType : byte { - None = 0, - HidKey = 1, // data = Keycode (Low-Byte) + Modifier (High-Byte) - HidConsumer = 2, // data = HID Consumer Usage ID - HostCommand = 3, // data = Command-ID → App führt aus - Macro = 4, // data = Makro-Slot-Index (0–31) + None = 0, + HidKey = 1, // data = Keycode (Low-Byte) + Modifier (High-Byte) + HidConsumer = 2, // data = HID Consumer Usage ID + HostCommand = 3, // data = Command-ID → App führt aus + Macro = 4, // data = Makro-Slot-Index (0–31) + ProfileSwitch = 5, // data = Profil-Index (0–2) } // Ein Step in einem Makro (keycode=0 → leerer/letzter Step) @@ -36,11 +52,11 @@ public class MacroStep public MacroStep Clone() => new() { Keycode = Keycode, Modifier = Modifier }; } -// Makro-Tabelle: 32 Slots × 4 Steps (spiegelt SMacroTable aus macro_config.h) +// Makro-Tabelle: 32 Slots × 8 Steps (spiegelt SMacroTable aus macro_config.h) public class MacroTable { public const int Slots = 32; - public const int MaxSteps = 4; + public const int MaxSteps = 8; // [slot][step] public MacroStep[,] Steps { get; } = new MacroStep[Slots, MaxSteps]; @@ -53,13 +69,13 @@ public class MacroTable } // Slot-Index für MX-Buttons und Encoder-Aktionen - public static int SlotForMx(int mxIdx) => mxIdx; // 0–19 - public static int SlotForEncoder(int enc, int actIdx) => 20 + enc * 3 + actIdx; // 20–31 + public static int SlotForMx(int mxIdx) => mxIdx; + public static int SlotForEncoder(int enc, int actIdx) => 20 + enc * 3 + actIdx; - // Serialisierung: 32 × 4 × 2 = 256 Bytes + // Serialisierung: 32 × 8 × 2 = 512 Bytes public byte[] ToBytes() { - var buf = new byte[256]; + var buf = new byte[512]; int pos = 0; for (int s = 0; s < Slots; s++) for (int i = 0; i < MaxSteps; i++) @@ -72,7 +88,7 @@ public class MacroTable public void FromBytes(byte[] buf) { - if (buf.Length < 256) return; + if (buf.Length < 512) return; int pos = 0; for (int s = 0; s < Slots; s++) for (int i = 0; i < MaxSteps; i++) @@ -83,13 +99,13 @@ public class MacroTable } } -// Spiegelt LEDAnim aus CButton.h – nur die in der GUI konfigurierbaren Werte +// Spiegelt LEDAnim aus CButton.h public enum LedAnimType : byte { Static = 0, Blink = 1, Pulse = 2, - ColorCycle = 5, // Regenbogen (entspricht LEDAnim::COLOR_CYCLE in Firmware) + ColorCycle = 5, } public class DeviceAction @@ -130,6 +146,8 @@ public class DeviceAction } if (Type == ActionType.Macro) return $"Makro {Data}"; + if (Type == ActionType.ProfileSwitch) + return Data == 0xFFFF ? "→ Nächstes Profil" : $"→ Profil {Data + 1}"; return $"CMD {Data}"; } } @@ -152,31 +170,19 @@ public class DeviceAction public DeviceAction Clone() => new() { Type = Type, Data = Data }; } -public class DeviceConfig +// ── Pro-Profil-Daten (spiegelt SDeviceProfile) ──────────────────────────────── + +public class DeviceProfile { - public const uint Magic = 0x56503202; - public const byte Version = 2; - - public const int EncSw = 0; - public const int EncCw = 1; - public const int EncCcw = 2; - public DeviceAction[] MxActions { get; } = Enumerable.Range(0, 20).Select(_ => new DeviceAction()).ToArray(); public DeviceAction[,] EncActions { get; } = InitEncActions(); - // Base-LED-Farben (Default: warm-weiß wie Firmware) - public Color[] LedBase { get; } = - Enumerable.Repeat(Color.FromArgb(80, 40, 0), 20).ToArray(); - - // LED-Animationen (Default: Regenbogen) - public LedAnimType[] LedAnim { get; } = - Enumerable.Repeat(LedAnimType.ColorCycle, 20).ToArray(); - - // Animationsperiode in ms (Default: 4000ms) - public ushort[] LedPeriod { get; } = - Enumerable.Repeat((ushort)4000, 20).ToArray(); + public Color[] LedBase { get; } = Enumerable.Repeat(Color.FromArgb(80, 40, 0), 20).ToArray(); + public byte[] LedBrightness { get; } = Enumerable.Repeat((byte)255, 20).ToArray(); + public LedAnimType[] LedAnim { get; } = Enumerable.Repeat(LedAnimType.ColorCycle, 20).ToArray(); + public ushort[] LedPeriod { get; } = Enumerable.Repeat((ushort)4000, 20).ToArray(); private static DeviceAction[,] InitEncActions() { @@ -187,79 +193,43 @@ public class DeviceConfig return a; } - // ── Serialisierung ──────────────────────────────────────────────────────── - - public byte[] ToBytes() + // Serialisiert ein Profil in 236 Bytes + public void ToBytes(byte[] buf, int offset) { - const int size = 223; - var buf = new byte[size]; - int pos = 0; - - WriteU32(buf, ref pos, Magic); - buf[pos++] = Version; - int crcOffset = pos; - pos += 2; - - foreach (var a in MxActions) - WriteAction(buf, ref pos, a); - + int pos = offset; + foreach (var a in MxActions) WriteAction(buf, ref pos, a); for (int e = 0; e < 4; e++) - for (int i = 0; i < 3; i++) - WriteAction(buf, ref pos, EncActions[e, i]); - + for (int i = 0; i < 3; i++) WriteAction(buf, ref pos, EncActions[e, i]); for (int i = 0; i < 20; i++) buf[pos++] = LedBase[i].R; for (int i = 0; i < 20; i++) buf[pos++] = LedBase[i].G; for (int i = 0; i < 20; i++) buf[pos++] = LedBase[i].B; - + for (int i = 0; i < 20; i++) buf[pos++] = LedBrightness[i]; for (int i = 0; i < 20; i++) buf[pos++] = (byte)LedAnim[i]; - for (int i = 0; i < 20; i++) { buf[pos++] = (byte)(LedPeriod[i] & 0xFF); buf[pos++] = (byte)(LedPeriod[i] >> 8); } - - ushort crc = Crc16(buf, 7, size - 7); - buf[crcOffset] = (byte)(crc & 0xFF); - buf[crcOffset + 1] = (byte)(crc >> 8); - - return buf; } - public bool FromBytes(byte[] buf) + // Deserialisiert ein Profil aus 236 Bytes + public void FromBytes(byte[] buf, int offset) { - if (buf.Length < 223) return false; - - uint magic = ReadU32(buf, 0); - if (magic != Magic) return false; - if (buf[4] != Version) return false; - - ushort storedCrc = (ushort)(buf[5] | (buf[6] << 8)); - ushort computedCrc = Crc16(buf, 7, 223 - 7); - if (storedCrc != computedCrc) return false; - - int pos = 7; - for (int i = 0; i < 20; i++) ReadAction(buf, ref pos, MxActions[i]); + int pos = offset; + for (int i = 0; i < 20; i++) ReadAction(buf, ref pos, MxActions[i]); for (int e = 0; e < 4; e++) - for (int i = 0; i < 3; i++) - ReadAction(buf, ref pos, EncActions[e, i]); - + for (int i = 0; i < 3; i++) ReadAction(buf, ref pos, EncActions[e, i]); for (int i = 0; i < 20; i++) LedBase[i] = Color.FromArgb(buf[pos + i], buf[pos + 20 + i], buf[pos + 40 + i]); pos += 60; - + for (int i = 0; i < 20; i++) LedBrightness[i] = buf[pos++]; for (int i = 0; i < 20; i++) LedAnim[i] = (LedAnimType)buf[pos++]; - for (int i = 0; i < 20; i++) { LedPeriod[i] = (ushort)(buf[pos] | (buf[pos + 1] << 8)); pos += 2; } - - return true; } - // ── Hilfsmethoden ───────────────────────────────────────────────────────── - private static void WriteAction(byte[] buf, ref int pos, DeviceAction a) { buf[pos++] = (byte)a.Type; @@ -273,6 +243,92 @@ public class DeviceConfig a.Data = (ushort)(buf[pos] | (buf[pos + 1] << 8)); pos += 2; } +} + +// ── Gesamt-Config (spiegelt SDeviceConfig) ──────────────────────────────────── + +public class DeviceConfig +{ + public const uint Magic = 0x56503203; + public const byte Version = 3; + + public const int EncSw = 0; + public const int EncCw = 1; + public const int EncCcw = 2; + + // Globale Felder + public byte ActiveProfileIndex { get; set; } = 0; + public byte GlobalBrightness { get; set; } = 255; + public byte[] EncSensitivity { get; } = new byte[] { 1, 1, 1, 1 }; + + // 3 Profile + public DeviceProfile[] Profiles { get; } = + Enumerable.Range(0, 3).Select(_ => new DeviceProfile()).ToArray(); + + // ── Shortcuts auf aktives Profil (ConfigForm braucht keine Änderung) ────── + + public DeviceAction[] MxActions => Profiles[ActiveProfileIndex].MxActions; + public DeviceAction[,] EncActions => Profiles[ActiveProfileIndex].EncActions; + public Color[] LedBase => Profiles[ActiveProfileIndex].LedBase; + public byte[] LedBrightness => Profiles[ActiveProfileIndex].LedBrightness; + public LedAnimType[] LedAnim => Profiles[ActiveProfileIndex].LedAnim; + public ushort[] LedPeriod => Profiles[ActiveProfileIndex].LedPeriod; + + // ── Serialisierung ──────────────────────────────────────────────────────── + + public byte[] ToBytes() + { + const int size = 740; + var buf = new byte[size]; + int pos = 0; + + WriteU32(buf, ref pos, Magic); + buf[pos++] = Version; + int crcOffset = pos; + pos += 2; // CRC-Platzhalter + + buf[pos++] = ActiveProfileIndex; + buf[pos++] = GlobalBrightness; + for (int i = 0; i < 4; i++) buf[pos++] = EncSensitivity[i]; + pos += 19; // Reserve (bleibt 0) + + // 3 Profile à 236B + for (int p = 0; p < 3; p++) + Profiles[p].ToBytes(buf, pos + p * 236); + pos += 3 * 236; + + ushort crc = Crc16(buf, 7, size - 7); + buf[crcOffset] = (byte)(crc & 0xFF); + buf[crcOffset + 1] = (byte)(crc >> 8); + + return buf; + } + + public bool FromBytes(byte[] buf) + { + if (buf.Length < 740) return false; + + uint magic = ReadU32(buf, 0); + if (magic != Magic) return false; + if (buf[4] != Version) return false; + + ushort storedCrc = (ushort)(buf[5] | (buf[6] << 8)); + ushort computedCrc = Crc16(buf, 7, 740 - 7); + if (storedCrc != computedCrc) return false; + + int pos = 7; + ActiveProfileIndex = Math.Min(buf[pos++], (byte)2); + GlobalBrightness = buf[pos++]; + for (int i = 0; i < 4; i++) EncSensitivity[i] = buf[pos++]; + pos += 19; // Reserve überspringen + + for (int p = 0; p < 3; p++) + Profiles[p].FromBytes(buf, pos + p * 236); + + return true; + } + + // ── Hilfsmethoden ───────────────────────────────────────────────────────── private static void WriteU32(byte[] buf, ref int pos, uint v) { diff --git a/src/TrayApp.cs b/src/TrayApp.cs index ec440aa..b5df7de 100644 --- a/src/TrayApp.cs +++ b/src/TrayApp.cs @@ -139,7 +139,7 @@ public class TrayApp : ApplicationContext // ── Config-Dump vom Board ───────────────────────────────────────── case Protocol.EvtConfigBegin: - _rxConfigBuf = new byte[223]; // sizeof(SDeviceConfig) = 223 (Version 2) + _rxConfigBuf = new byte[740]; // sizeof(SDeviceConfig) = 740 (Version 3) break; case Protocol.EvtConfigData: @@ -168,7 +168,7 @@ public class TrayApp : ApplicationContext break; case Protocol.EvtMacroBegin: - _rxMacroBuf = new byte[256]; + _rxMacroBuf = new byte[512]; // sizeof(SMacroTable) = 512 (8 Steps) break; case Protocol.EvtMacroData: