Semi working profiles and longer macros
This commit is contained in:
parent
851e548166
commit
e01666ab59
@ -35,6 +35,10 @@ public class ActionDialog : Form
|
|||||||
private readonly Panel _cmdPanel;
|
private readonly Panel _cmdPanel;
|
||||||
private readonly TextBox _cmdBox;
|
private readonly TextBox _cmdBox;
|
||||||
|
|
||||||
|
// Profil wechseln
|
||||||
|
private readonly Panel _profilePanel;
|
||||||
|
private readonly ComboBox _profileCombo;
|
||||||
|
|
||||||
// LED-Farbe
|
// LED-Farbe
|
||||||
private readonly Panel _colorPanel;
|
private readonly Panel _colorPanel;
|
||||||
private readonly Button _colorBtn;
|
private readonly Button _colorBtn;
|
||||||
@ -50,11 +54,11 @@ public class ActionDialog : Form
|
|||||||
private readonly MacroTable _macros;
|
private readonly MacroTable _macros;
|
||||||
private readonly int _macroSlot;
|
private readonly int _macroSlot;
|
||||||
// Je Step: [CaptureBtn, CheckCtrl, CheckShift, CheckAlt]
|
// Je Step: [CaptureBtn, CheckCtrl, CheckShift, CheckAlt]
|
||||||
private readonly Button[] _stepBtns = new Button[4];
|
private readonly Button[] _stepBtns = new Button[8];
|
||||||
private readonly CheckBox[] _stepCtrl = new CheckBox[4];
|
private readonly CheckBox[] _stepCtrl = new CheckBox[8];
|
||||||
private readonly CheckBox[] _stepShift = new CheckBox[4];
|
private readonly CheckBox[] _stepShift = new CheckBox[8];
|
||||||
private readonly CheckBox[] _stepAlt = new CheckBox[4];
|
private readonly CheckBox[] _stepAlt = new CheckBox[8];
|
||||||
private readonly byte[] _stepKeycodes = new byte[4];
|
private readonly byte[] _stepKeycodes = new byte[8];
|
||||||
private int _captureStep = -1; // -1 = nicht im Capture-Modus
|
private int _captureStep = -1; // -1 = nicht im Capture-Modus
|
||||||
|
|
||||||
// Capture-Zustand
|
// Capture-Zustand
|
||||||
@ -220,7 +224,7 @@ public class ActionDialog : Form
|
|||||||
_showColor = showColor;
|
_showColor = showColor;
|
||||||
|
|
||||||
// Bestehende Makro-Steps aus der Tabelle laden
|
// 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;
|
_stepKeycodes[i] = _macros.Steps[_macroSlot, i].Keycode;
|
||||||
|
|
||||||
// Formular-Grundeinstellungen
|
// Formular-Grundeinstellungen
|
||||||
@ -240,7 +244,7 @@ public class ActionDialog : Form
|
|||||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||||
};
|
};
|
||||||
_typeCombo.Items.AddRange(new object[]
|
_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.SelectedIndex = (int)action.Type;
|
||||||
_typeCombo.SelectedIndexChanged += OnTypeChanged;
|
_typeCombo.SelectedIndexChanged += OnTypeChanged;
|
||||||
|
|
||||||
@ -320,6 +324,22 @@ public class ActionDialog : Form
|
|||||||
};
|
};
|
||||||
_cmdPanel.Controls.AddRange(new Control[] { cmdLabel, _cmdBox });
|
_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 ────────────────────────────────────────────────────────
|
// ── Farb-Panel ────────────────────────────────────────────────────────
|
||||||
_colorPanel = new Panel
|
_colorPanel = new Panel
|
||||||
{
|
{
|
||||||
@ -378,8 +398,8 @@ public class ActionDialog : Form
|
|||||||
_animPanel.Controls.AddRange(new Control[]
|
_animPanel.Controls.AddRange(new Control[]
|
||||||
{ animLabel, _animCombo, periodLabel, _periodCombo });
|
{ animLabel, _animCombo, periodLabel, _periodCombo });
|
||||||
|
|
||||||
// ── Makro-Panel (4 Steps) ─────────────────────────────────────────────
|
// ── Makro-Panel (8 Steps) ─────────────────────────────────────────────
|
||||||
_macroPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 4 * 30 + 22) };
|
_macroPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 8 * 30 + 22) };
|
||||||
|
|
||||||
var macroHint = new Label
|
var macroHint = new Label
|
||||||
{
|
{
|
||||||
@ -391,7 +411,7 @@ public class ActionDialog : Form
|
|||||||
};
|
};
|
||||||
_macroPanel.Controls.Add(macroHint);
|
_macroPanel.Controls.Add(macroHint);
|
||||||
|
|
||||||
for (int step = 0; step < 4; step++)
|
for (int step = 0; step < 8; step++)
|
||||||
{
|
{
|
||||||
int s = step;
|
int s = step;
|
||||||
int y2 = 30 + step * 30;
|
int y2 = 30 + step * 30;
|
||||||
@ -454,7 +474,7 @@ public class ActionDialog : Form
|
|||||||
Controls.AddRange(new Control[]
|
Controls.AddRange(new Control[]
|
||||||
{
|
{
|
||||||
typeLabel, _typeCombo,
|
typeLabel, _typeCombo,
|
||||||
_hidKeyPanel, _consumerPanel, _cmdPanel, _macroPanel,
|
_hidKeyPanel, _consumerPanel, _cmdPanel, _profilePanel, _macroPanel,
|
||||||
_colorPanel, _animPanel,
|
_colorPanel, _animPanel,
|
||||||
_okBtn, _cancelBtn,
|
_okBtn, _cancelBtn,
|
||||||
});
|
});
|
||||||
@ -481,6 +501,7 @@ public class ActionDialog : Form
|
|||||||
_hidKeyPanel.Visible = t == ActionType.HidKey;
|
_hidKeyPanel.Visible = t == ActionType.HidKey;
|
||||||
_consumerPanel.Visible = t == ActionType.HidConsumer;
|
_consumerPanel.Visible = t == ActionType.HidConsumer;
|
||||||
_cmdPanel.Visible = t == ActionType.HostCommand;
|
_cmdPanel.Visible = t == ActionType.HostCommand;
|
||||||
|
_profilePanel.Visible = t == ActionType.ProfileSwitch;
|
||||||
_macroPanel.Visible = t == ActionType.Macro;
|
_macroPanel.Visible = t == ActionType.Macro;
|
||||||
_colorPanel.Visible = _showColor;
|
_colorPanel.Visible = _showColor;
|
||||||
_animPanel.Visible = _showColor;
|
_animPanel.Visible = _showColor;
|
||||||
@ -496,11 +517,12 @@ public class ActionDialog : Form
|
|||||||
|
|
||||||
int contentH = t switch
|
int contentH = t switch
|
||||||
{
|
{
|
||||||
ActionType.HidKey => 110,
|
ActionType.HidKey => 110,
|
||||||
ActionType.HidConsumer => 30,
|
ActionType.HidConsumer => 30,
|
||||||
ActionType.HostCommand => 30,
|
ActionType.HostCommand => 30,
|
||||||
ActionType.Macro => 4 * 30 + 22,
|
ActionType.ProfileSwitch => 30,
|
||||||
_ => 0,
|
ActionType.Macro => 8 * 30 + 22,
|
||||||
|
_ => 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
int ledY = 48 + contentH + 10;
|
int ledY = 48 + contentH + 10;
|
||||||
@ -710,12 +732,18 @@ public class ActionDialog : Form
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
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)
|
if (type == ActionType.Macro)
|
||||||
{
|
{
|
||||||
// Steps in die Makro-Tabelle schreiben
|
// Steps in die Makro-Tabelle schreiben
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
{
|
{
|
||||||
byte mod = 0;
|
byte mod = 0;
|
||||||
if (_stepCtrl[i].Checked) mod |= 0x01;
|
if (_stepCtrl[i].Checked) mod |= 0x01;
|
||||||
|
|||||||
@ -26,6 +26,7 @@ public class ConfigForm : Form
|
|||||||
private readonly Button[] _btnGrid = new Button[20];
|
private readonly Button[] _btnGrid = new Button[20];
|
||||||
private readonly Button[,] _encBtns = new Button[4, 3];
|
private readonly Button[,] _encBtns = new Button[4, 3];
|
||||||
private Button _saveBtn = null!;
|
private Button _saveBtn = null!;
|
||||||
|
private ComboBox _profileCombo = null!;
|
||||||
|
|
||||||
public ConfigForm(DeviceConfig config, MacroTable macros, SerialManager serial)
|
public ConfigForm(DeviceConfig config, MacroTable macros, SerialManager serial)
|
||||||
{
|
{
|
||||||
@ -40,6 +41,7 @@ public class ConfigForm : Form
|
|||||||
ClientSize = new Size(520, 480);
|
ClientSize = new Size(520, 480);
|
||||||
|
|
||||||
int y = 12;
|
int y = 12;
|
||||||
|
y = BuildProfileBar(y);
|
||||||
y = BuildButtonGrid(y);
|
y = BuildButtonGrid(y);
|
||||||
y = BuildEncoderPanel(y);
|
y = BuildEncoderPanel(y);
|
||||||
BuildFooter(y);
|
BuildFooter(y);
|
||||||
@ -50,6 +52,35 @@ public class ConfigForm : Form
|
|||||||
_saveBtn.Enabled = _serial.IsConnected;
|
_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) ───────────────────────────────────
|
// ── Tasten-Grid (4 Spalten × 5 Zeilen) ───────────────────────────────────
|
||||||
|
|
||||||
private int BuildButtonGrid(int startY)
|
private int BuildButtonGrid(int startY)
|
||||||
@ -390,9 +421,11 @@ internal static class Extensions
|
|||||||
|
|
||||||
public static string TypeDescription(this DeviceAction a) => a.Type switch
|
public static string TypeDescription(this DeviceAction a) => a.Type switch
|
||||||
{
|
{
|
||||||
ActionType.HidKey => "HID Tastatur-Keycode",
|
ActionType.HidKey => "HID Tastatur-Keycode",
|
||||||
ActionType.HidConsumer => "HID Consumer Control (Media/Volume)",
|
ActionType.HidConsumer => "HID Consumer Control (Media/Volume)",
|
||||||
ActionType.HostCommand => "Host Command – App führt aus",
|
ActionType.HostCommand => "Host Command – App führt aus",
|
||||||
_ => "Keine Aktion",
|
ActionType.Macro => "Makro-Sequenz",
|
||||||
|
ActionType.ProfileSwitch => "Profil wechseln",
|
||||||
|
_ => "Keine Aktion",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
// ConfigJson.cs
|
// ConfigJson.cs
|
||||||
// JSON-Import/Export für DeviceConfig.
|
// JSON-Import/Export für DeviceConfig.
|
||||||
//
|
//
|
||||||
// Format (menschenlesbar, kommentiert mit Feldnamen):
|
// Format (menschenlesbar):
|
||||||
// {
|
// {
|
||||||
// "version": 2,
|
// "version": 3,
|
||||||
|
// "profile": 0,
|
||||||
// "buttons": [
|
// "buttons": [
|
||||||
// { "index": 0, "action": { "type": "HidKey", "data": 260 },
|
// { "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": [
|
// "encoders": [
|
||||||
@ -17,6 +19,8 @@
|
|||||||
// ...
|
// ...
|
||||||
// ]
|
// ]
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
|
// Import/Export bezieht sich immer auf das aktive Profil.
|
||||||
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@ -37,6 +41,7 @@ internal static class ConfigJson
|
|||||||
var doc = new ConfigJsonDoc
|
var doc = new ConfigJsonDoc
|
||||||
{
|
{
|
||||||
Version = DeviceConfig.Version,
|
Version = DeviceConfig.Version,
|
||||||
|
Profile = cfg.ActiveProfileIndex,
|
||||||
Buttons = new ButtonEntry[20],
|
Buttons = new ButtonEntry[20],
|
||||||
Encoders = new EncoderEntry[4],
|
Encoders = new EncoderEntry[4],
|
||||||
};
|
};
|
||||||
@ -49,11 +54,12 @@ internal static class ConfigJson
|
|||||||
Action = ActionToJson(cfg.MxActions[i]),
|
Action = ActionToJson(cfg.MxActions[i]),
|
||||||
Led = new LedEntry
|
Led = new LedEntry
|
||||||
{
|
{
|
||||||
R = cfg.LedBase[i].R,
|
R = cfg.LedBase[i].R,
|
||||||
G = cfg.LedBase[i].G,
|
G = cfg.LedBase[i].G,
|
||||||
B = cfg.LedBase[i].B,
|
B = cfg.LedBase[i].B,
|
||||||
Anim = cfg.LedAnim[i],
|
Brightness = cfg.LedBrightness[i],
|
||||||
PeriodMs = cfg.LedPeriod[i],
|
Anim = cfg.LedAnim[i],
|
||||||
|
PeriodMs = cfg.LedPeriod[i],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -89,9 +95,10 @@ internal static class ConfigJson
|
|||||||
ActionFromJson(b.Action, cfg.MxActions[b.Index]);
|
ActionFromJson(b.Action, cfg.MxActions[b.Index]);
|
||||||
if (b.Led != null)
|
if (b.Led != null)
|
||||||
{
|
{
|
||||||
cfg.LedBase[b.Index] = Color.FromArgb(b.Led.R, b.Led.G, b.Led.B);
|
cfg.LedBase[b.Index] = Color.FromArgb(b.Led.R, b.Led.G, b.Led.B);
|
||||||
cfg.LedAnim[b.Index] = b.Led.Anim;
|
cfg.LedBrightness[b.Index] = b.Led.Brightness;
|
||||||
cfg.LedPeriod[b.Index] = b.Led.PeriodMs;
|
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
|
private class ConfigJsonDoc
|
||||||
{
|
{
|
||||||
[JsonPropertyName("version")] public byte Version { get; set; }
|
[JsonPropertyName("version")] public byte Version { get; set; }
|
||||||
[JsonPropertyName("buttons")] public ButtonEntry[]? Buttons { get; set; }
|
[JsonPropertyName("profile")] public byte Profile { get; set; }
|
||||||
|
[JsonPropertyName("buttons")] public ButtonEntry[]? Buttons { get; set; }
|
||||||
[JsonPropertyName("encoders")] public EncoderEntry[]? Encoders { get; set; }
|
[JsonPropertyName("encoders")] public EncoderEntry[]? Encoders { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,10 +160,11 @@ internal static class ConfigJson
|
|||||||
|
|
||||||
private class LedEntry
|
private class LedEntry
|
||||||
{
|
{
|
||||||
[JsonPropertyName("r")] public byte R { get; set; }
|
[JsonPropertyName("r")] public byte R { get; set; }
|
||||||
[JsonPropertyName("g")] public byte G { get; set; }
|
[JsonPropertyName("g")] public byte G { get; set; }
|
||||||
[JsonPropertyName("b")] public byte B { get; set; }
|
[JsonPropertyName("b")] public byte B { get; set; }
|
||||||
[JsonPropertyName("anim")] public LedAnimType Anim { get; set; }
|
[JsonPropertyName("brightness")] public byte Brightness { get; set; } = 255;
|
||||||
[JsonPropertyName("period_ms")] public ushort PeriodMs { get; set; }
|
[JsonPropertyName("anim")] public LedAnimType Anim { get; set; }
|
||||||
|
[JsonPropertyName("period_ms")] public ushort PeriodMs { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,32 @@
|
|||||||
// DeviceConfig.cs
|
// 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):
|
// Serialisiertes Config-Layout (740 B, packed):
|
||||||
// Offset 0 4B Magic 0x56503202
|
// ── Globaler Header (32B) ──────────────────────────────────────────────────
|
||||||
// Offset 4 1B Version 2
|
// Offset 0 4B Magic 0x56503203
|
||||||
// Offset 5 2B CRC16 (CCITT über Bytes 7–222)
|
// Offset 4 1B Version = 3
|
||||||
// Offset 7 60B mx_actions[20] je 3B: type(1) + data_lo(1) + data_hi(1)
|
// Offset 5 2B CRC16-CCITT über Bytes 7–739
|
||||||
// Offset 67 36B enc_actions[4][3] je 3B
|
// Offset 7 1B ActiveProfile (0–2)
|
||||||
// Offset103 20B led_r[20]
|
// Offset 8 1B GlobalBrightness (0–255)
|
||||||
// Offset123 20B led_g[20]
|
// Offset 9 4B EncSensitivity[4]
|
||||||
// Offset143 20B led_b[20]
|
// Offset 13 19B Reserve
|
||||||
// Offset163 20B led_anim[20] je 1B (LedAnimType)
|
// ── Profil 0 (236B, ab Offset 32) ─────────────────────────────────────────
|
||||||
// Offset183 40B led_period_ms[20] je 2B (uint16, little-endian)
|
// ── 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;
|
using System.Drawing;
|
||||||
|
|
||||||
@ -19,11 +34,12 @@ namespace VersaGUI;
|
|||||||
|
|
||||||
public enum ActionType : byte
|
public enum ActionType : byte
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
HidKey = 1, // data = Keycode (Low-Byte) + Modifier (High-Byte)
|
HidKey = 1, // data = Keycode (Low-Byte) + Modifier (High-Byte)
|
||||||
HidConsumer = 2, // data = HID Consumer Usage ID
|
HidConsumer = 2, // data = HID Consumer Usage ID
|
||||||
HostCommand = 3, // data = Command-ID → App führt aus
|
HostCommand = 3, // data = Command-ID → App führt aus
|
||||||
Macro = 4, // data = Makro-Slot-Index (0–31)
|
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)
|
// 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 };
|
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 class MacroTable
|
||||||
{
|
{
|
||||||
public const int Slots = 32;
|
public const int Slots = 32;
|
||||||
public const int MaxSteps = 4;
|
public const int MaxSteps = 8;
|
||||||
|
|
||||||
// [slot][step]
|
// [slot][step]
|
||||||
public MacroStep[,] Steps { get; } = new MacroStep[Slots, MaxSteps];
|
public MacroStep[,] Steps { get; } = new MacroStep[Slots, MaxSteps];
|
||||||
@ -53,13 +69,13 @@ public class MacroTable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Slot-Index für MX-Buttons und Encoder-Aktionen
|
// Slot-Index für MX-Buttons und Encoder-Aktionen
|
||||||
public static int SlotForMx(int mxIdx) => mxIdx; // 0–19
|
public static int SlotForMx(int mxIdx) => mxIdx;
|
||||||
public static int SlotForEncoder(int enc, int actIdx) => 20 + enc * 3 + actIdx; // 20–31
|
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()
|
public byte[] ToBytes()
|
||||||
{
|
{
|
||||||
var buf = new byte[256];
|
var buf = new byte[512];
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
for (int s = 0; s < Slots; s++)
|
for (int s = 0; s < Slots; s++)
|
||||||
for (int i = 0; i < MaxSteps; i++)
|
for (int i = 0; i < MaxSteps; i++)
|
||||||
@ -72,7 +88,7 @@ public class MacroTable
|
|||||||
|
|
||||||
public void FromBytes(byte[] buf)
|
public void FromBytes(byte[] buf)
|
||||||
{
|
{
|
||||||
if (buf.Length < 256) return;
|
if (buf.Length < 512) return;
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
for (int s = 0; s < Slots; s++)
|
for (int s = 0; s < Slots; s++)
|
||||||
for (int i = 0; i < MaxSteps; i++)
|
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
|
public enum LedAnimType : byte
|
||||||
{
|
{
|
||||||
Static = 0,
|
Static = 0,
|
||||||
Blink = 1,
|
Blink = 1,
|
||||||
Pulse = 2,
|
Pulse = 2,
|
||||||
ColorCycle = 5, // Regenbogen (entspricht LEDAnim::COLOR_CYCLE in Firmware)
|
ColorCycle = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DeviceAction
|
public class DeviceAction
|
||||||
@ -130,6 +146,8 @@ public class DeviceAction
|
|||||||
}
|
}
|
||||||
if (Type == ActionType.Macro)
|
if (Type == ActionType.Macro)
|
||||||
return $"Makro {Data}";
|
return $"Makro {Data}";
|
||||||
|
if (Type == ActionType.ProfileSwitch)
|
||||||
|
return Data == 0xFFFF ? "→ Nächstes Profil" : $"→ Profil {Data + 1}";
|
||||||
return $"CMD {Data}";
|
return $"CMD {Data}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,31 +170,19 @@ public class DeviceAction
|
|||||||
public DeviceAction Clone() => new() { Type = Type, Data = Data };
|
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; } =
|
public DeviceAction[] MxActions { get; } =
|
||||||
Enumerable.Range(0, 20).Select(_ => new DeviceAction()).ToArray();
|
Enumerable.Range(0, 20).Select(_ => new DeviceAction()).ToArray();
|
||||||
|
|
||||||
public DeviceAction[,] EncActions { get; } = InitEncActions();
|
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();
|
||||||
public Color[] LedBase { get; } =
|
public byte[] LedBrightness { get; } = Enumerable.Repeat((byte)255, 20).ToArray();
|
||||||
Enumerable.Repeat(Color.FromArgb(80, 40, 0), 20).ToArray();
|
public LedAnimType[] LedAnim { get; } = Enumerable.Repeat(LedAnimType.ColorCycle, 20).ToArray();
|
||||||
|
public ushort[] LedPeriod { get; } = Enumerable.Repeat((ushort)4000, 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();
|
|
||||||
|
|
||||||
private static DeviceAction[,] InitEncActions()
|
private static DeviceAction[,] InitEncActions()
|
||||||
{
|
{
|
||||||
@ -187,79 +193,43 @@ public class DeviceConfig
|
|||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Serialisierung ────────────────────────────────────────────────────────
|
// Serialisiert ein Profil in 236 Bytes
|
||||||
|
public void ToBytes(byte[] buf, int offset)
|
||||||
public byte[] ToBytes()
|
|
||||||
{
|
{
|
||||||
const int size = 223;
|
int pos = offset;
|
||||||
var buf = new byte[size];
|
foreach (var a in MxActions) WriteAction(buf, ref pos, a);
|
||||||
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);
|
|
||||||
|
|
||||||
for (int e = 0; e < 4; e++)
|
for (int e = 0; e < 4; e++)
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++) WriteAction(buf, ref pos, EncActions[e, 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].R;
|
||||||
for (int i = 0; i < 20; i++) buf[pos++] = LedBase[i].G;
|
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++] = 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)LedAnim[i];
|
||||||
|
|
||||||
for (int i = 0; i < 20; i++)
|
for (int i = 0; i < 20; i++)
|
||||||
{
|
{
|
||||||
buf[pos++] = (byte)(LedPeriod[i] & 0xFF);
|
buf[pos++] = (byte)(LedPeriod[i] & 0xFF);
|
||||||
buf[pos++] = (byte)(LedPeriod[i] >> 8);
|
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;
|
int pos = offset;
|
||||||
|
for (int i = 0; i < 20; i++) ReadAction(buf, ref pos, MxActions[i]);
|
||||||
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]);
|
|
||||||
for (int e = 0; e < 4; e++)
|
for (int e = 0; e < 4; e++)
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++) ReadAction(buf, ref pos, EncActions[e, 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]);
|
for (int i = 0; i < 20; i++) LedBase[i] = Color.FromArgb(buf[pos + i], buf[pos + 20 + i], buf[pos + 40 + i]);
|
||||||
pos += 60;
|
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++) LedAnim[i] = (LedAnimType)buf[pos++];
|
||||||
|
|
||||||
for (int i = 0; i < 20; i++)
|
for (int i = 0; i < 20; i++)
|
||||||
{
|
{
|
||||||
LedPeriod[i] = (ushort)(buf[pos] | (buf[pos + 1] << 8));
|
LedPeriod[i] = (ushort)(buf[pos] | (buf[pos + 1] << 8));
|
||||||
pos += 2;
|
pos += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private static void WriteAction(byte[] buf, ref int pos, DeviceAction a)
|
private static void WriteAction(byte[] buf, ref int pos, DeviceAction a)
|
||||||
{
|
{
|
||||||
buf[pos++] = (byte)a.Type;
|
buf[pos++] = (byte)a.Type;
|
||||||
@ -273,6 +243,92 @@ public class DeviceConfig
|
|||||||
a.Data = (ushort)(buf[pos] | (buf[pos + 1] << 8));
|
a.Data = (ushort)(buf[pos] | (buf[pos + 1] << 8));
|
||||||
pos += 2;
|
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)
|
private static void WriteU32(byte[] buf, ref int pos, uint v)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -139,7 +139,7 @@ public class TrayApp : ApplicationContext
|
|||||||
|
|
||||||
// ── Config-Dump vom Board ─────────────────────────────────────────
|
// ── Config-Dump vom Board ─────────────────────────────────────────
|
||||||
case Protocol.EvtConfigBegin:
|
case Protocol.EvtConfigBegin:
|
||||||
_rxConfigBuf = new byte[223]; // sizeof(SDeviceConfig) = 223 (Version 2)
|
_rxConfigBuf = new byte[740]; // sizeof(SDeviceConfig) = 740 (Version 3)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Protocol.EvtConfigData:
|
case Protocol.EvtConfigData:
|
||||||
@ -168,7 +168,7 @@ public class TrayApp : ApplicationContext
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Protocol.EvtMacroBegin:
|
case Protocol.EvtMacroBegin:
|
||||||
_rxMacroBuf = new byte[256];
|
_rxMacroBuf = new byte[512]; // sizeof(SMacroTable) = 512 (8 Steps)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Protocol.EvtMacroData:
|
case Protocol.EvtMacroData:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user