Added hold function and updated doc

This commit is contained in:
Julian Appel 2026-03-31 21:46:20 +02:00
parent d6ed7cb81f
commit 3e83758f05
8 changed files with 141 additions and 105 deletions

View file

@ -103,7 +103,7 @@ void CMainController::init_buttons()
// Encoder-SW-Buttons: nur SW-Aktion, kein LED (led_index = -1)
for (uint8_t enc = 0; enc < 4; enc++) {
m_buttons[enc].init(enc, -1, cfg.enc_actions[enc][ENC_ACTION_SW]);
m_buttons[enc].init(enc, -1, cfg.enc_actions[enc][ENC_ACTION_SW], RGB());
}
// MX-Buttons: LED-Index aus serpentiner Verdrahtung berechnen,
@ -115,7 +115,7 @@ void CMainController::init_buttons()
uint8_t row = key % MATRIX_ROWS;
int8_t led = static_cast<int8_t>(LED_INDEX(col, row));
uint8_t mx_idx = key - 5;
RGB base(cfg.led_r[mx_idx], cfg.led_g[mx_idx], cfg.led_b[mx_idx]);
RGB base(cfg.led_r[mx_idx], cfg.led_g[mx_idx], cfg.led_b[mx_idx]);
m_buttons[key].init(key, led, cfg.mx_actions[mx_idx], base);
LEDAnim anim = static_cast<LEDAnim>(cfg.led_anim[mx_idx]);
@ -142,10 +142,10 @@ void CMainController::init_buttons()
void CMainController::work()
{
matrix_scan(); // 1. Matrix scannen → Debounce → matrix_cb() → Queue
poll_vendor(); // 2. Eingehende Serial-Pakete (PC→Board) verarbeiten
processEvents(); // 3. Queue leeren: Aktionen ausführen, Buttons benachrichtigen
updateLEDs(); // 4. Geänderte LED-Zustände in WS2812-Buffer schreiben + show()
matrix_scan(); // 1. Matrix scannen → Debounce → matrix_cb() → Queue
poll_vendor(); // 2. Eingehende Serial-Pakete (PC→Board) verarbeiten
processEvents(); // 3. Queue leeren, Aktionen ausführen
updateLEDs(); // 4. Geänderte LED-Zustände in WS2812-Buffer schreiben + show()
}
// ─── Vendor-Kommunikation (PC → Board) ───────────────────────────────────────
@ -212,9 +212,9 @@ void CMainController::poll_vendor()
SDeviceConfig cfg;
nvm_config_load(cfg); // ungültige NVM → Defaults
const uint8_t* raw = reinterpret_cast<const uint8_t*>(&cfg);
const uint8_t sz = sizeof(SDeviceConfig); // 163
const uint8_t sz = sizeof(SDeviceConfig); // 223
const uint8_t payload = 6;
uint8_t chunks = (sz + payload - 1) / payload; // 28
uint8_t chunks = (sz + payload - 1) / payload; // 38
usb_serial_send(USB_EVT_CONFIG_BEGIN, chunks);
@ -316,43 +316,40 @@ void CMainController::poll_vendor()
// Verarbeitet alle Events in der Queue bis sie leer ist.
// Reihenfolge: ältestes Event zuerst (FIFO).
//
// HOST_COMMAND-Aktionen werden zusätzlich über Serial an die Windows-App
// gemeldet die App entscheidet dann was passiert (URL öffnen, Programm starten…).
// KEY_DOWN: execute_action_down() HID-Taste wird gedrückt, bleibt aktiv bis KEY_UP.
// KEY_UP: execute_action_up() HID-Taste wird losgelassen.
// Encoder CW/CCW: execute_action_down() + execute_action_up() für atomare TAP-Sequenz.
void CMainController::processEvents()
{
SEvent ev;
while (m_queue.pop(ev)) {
switch (ev.type) {
case EventType::KEY_DOWN:
if (ev.key_id < MATRIX_KEYS) {
m_buttons[ev.key_id].on_press();
execute_action(m_buttons[ev.key_id].action());
// Bei HOST_COMMAND: Event-ID an Windows-App senden
if (m_buttons[ev.key_id].action().type == ActionType::HOST_COMMAND)
usb_serial_send(USB_EVT_KEY_DOWN, ev.key_id);
}
if (ev.key_id < MATRIX_KEYS)
execute_action_down(m_buttons[ev.key_id].action(), ev.key_id);
break;
case EventType::KEY_UP:
if (ev.key_id < MATRIX_KEYS)
m_buttons[ev.key_id].on_release();
execute_action_up(m_buttons[ev.key_id].action(), ev.key_id);
break;
case EventType::ENC_CW:
if (ev.key_id < 4) {
execute_action(m_enc_cw[ev.key_id]);
if (m_enc_cw[ev.key_id].type == ActionType::HOST_COMMAND)
usb_serial_send(USB_EVT_ENC_CW, ev.key_id);
execute_action_down(m_enc_cw[ev.key_id], ev.key_id);
delay(10);
execute_action_up(m_enc_cw[ev.key_id], ev.key_id);
}
break;
case EventType::ENC_CCW:
if (ev.key_id < 4) {
execute_action(m_enc_ccw[ev.key_id]);
if (m_enc_ccw[ev.key_id].type == ActionType::HOST_COMMAND)
usb_serial_send(USB_EVT_ENC_CCW, ev.key_id);
execute_action_down(m_enc_ccw[ev.key_id], ev.key_id);
delay(10);
execute_action_up(m_enc_ccw[ev.key_id], ev.key_id);
}
break;
@ -362,35 +359,51 @@ void CMainController::processEvents()
}
}
// Führt eine einzelne Aktion aus.
// HID_KEY / HID_CONSUMER: direkt über USB HID gesendet (funktioniert ohne Windows-App).
// HOST_COMMAND: kein direkter Aufruf hier das Event wird in processEvents() via
// usb_serial_send() an die Windows-App weitergeleitet.
void CMainController::execute_action(SAction action)
// ─── Aktions-Ausführung ───────────────────────────────────────────────────────
//
// execute_action_down(): Taste wird gedrückt (Hold-Start).
// HID_KEY: sendet Key-Down, bleibt aktiv.
// HID_CONSUMER: sendet Consumer-Down, bleibt aktiv.
// HOST_COMMAND: sendet KEY_DOWN-Event an Windows-App.
// MACRO: führt volle Sequenz aus (Key-Down/Up jeweils mit Pause).
// NONE: keine Aktion.
//
// execute_action_up(): Taste wird losgelassen (Hold-Ende).
// HID_KEY: sendet Key-Up.
// HID_CONSUMER: sendet Consumer-Up.
// HOST_COMMAND: kann USB_EVT_KEY_UP senden.
// MACRO/NONE: keine Aktion.
void CMainController::execute_action_down(SAction action, uint8_t key_id)
{
switch (action.type) {
case ActionType::HID_KEY:
{
// data-Encoding: Low-Byte = Keycode, High-Byte = Modifier
usb_hid_send_key(static_cast<uint8_t>(action.data & 0xFF),
static_cast<uint8_t>(action.data >> 8));
delay(10); // Host braucht kurz Zeit zwischen Key-Down und Key-Up
usb_hid_release_key();
uint8_t keycode = static_cast<uint8_t>(action.data & 0xFF);
uint8_t modifier = static_cast<uint8_t>(action.data >> 8);
usb_hid_send_key(keycode, modifier);
// Taste bleibt gedrückt bis execute_action_up() aufgerufen wird
break;
}
case ActionType::HID_CONSUMER:
{
usb_hid_send_consumer(action.data);
usb_hid_release_consumer();
// Consumer-Control bleibt aktiv bis execute_action_up() aufgerufen wird
break;
}
case ActionType::HOST_COMMAND:
// Wird in processEvents() über Serial gesendet
// Windows-App übernimmt Ausführung; KEY_DOWN-Event senden
usb_serial_send(USB_EVT_KEY_DOWN, key_id);
break;
case ActionType::MACRO:
{
// Makro-Slot aus dem RAM ausführen (bei setup() aus NVM geladen).
// Steps mit keycode=0 werden übersprungen; erstes leeres Step stoppt.
// Makros sind Sequenzen Steps mit keycode=0 werden übersprungen;
// erstes leeres Step stoppt.
uint8_t slot = static_cast<uint8_t>(action.data);
if (slot >= MACRO_SLOTS) break;
for (uint8_t i = 0; i < MACRO_MAX_STEPS; i++) {
@ -410,6 +423,31 @@ void CMainController::execute_action(SAction action)
}
}
void CMainController::execute_action_up(SAction action, uint8_t key_id)
{
switch (action.type) {
case ActionType::HID_KEY:
usb_hid_release_key();
break;
case ActionType::HID_CONSUMER:
usb_hid_release_consumer();
break;
case ActionType::HOST_COMMAND:
// Optional: USB_EVT_KEY_UP senden (aktuell nicht implementiert)
break;
case ActionType::MACRO:
case ActionType::NONE:
default:
// MACRO: Sequenz ist in execute_action_down() komplett abgelaufen, nop hier
// NONE: keine Aktion
break;
}
}
// ─── LED-Rendering ────────────────────────────────────────────────────────────
//
// Fragt alle CButton-Instanzen ab. Jede Instanz mit dirty-Flag schreibt