# Aktions-Engine **Dateien:** `config/action.h`, `CButton.h/.cpp`, `CMainController.cpp` (`processEvents`, `execute_action_down`, `execute_action_up`) ## SAction-Struct ```cpp struct __attribute__((packed)) SAction { ActionType type; // 1 Byte uint16_t data; // 2 Bytes (Keycode, Consumer-Code, Command-ID oder Slot-Index) }; // Gesamt: 3 Bytes (packed! ohne packed wären es 4 durch Alignment) ``` `packed` ist zwingend damit `sizeof(SDeviceConfig) == 740` mit der C#-Serialisierung in VersaGUI übereinstimmt. ## ActionType | Typ | Wert | Bedeutung | data-Inhalt | |---|---|---|---| | `NONE` | 0 | Keine Aktion | — | | `HID_KEY` | 1 | Tastendruck via USB HID Keyboard | Low-Byte = HID Keycode, High-Byte = Modifier | | `HID_CONSUMER` | 2 | Consumer Control (Volume, Media, …) | Consumer Usage ID | | `HOST_COMMAND` | 3 | Event an VersaGUI senden, App führt aus | Command-ID (frei definiert) | | `MACRO` | 4 | Makro-Sequenz aus NVM-Tabelle | Slot-Index 0–31 | | `PROFILE_SWITCH` | 5 | Aktives Profil wechseln | 0–2 = Ziel-Profil, 0xFF = nächstes Profil (Zyklus 0→1→2→0) | ## execute_action_down() — Taste gedrückt (Hold-Start) | ActionType | Verhalten | |---|---| | `HID_KEY` | `usb_hid_send_key(keycode, modifier)` — Taste bleibt gedrückt bis `execute_action_up()` | | `HID_CONSUMER` | `usb_hid_send_consumer(usage_id)` — bleibt aktiv bis `execute_action_up()` | | `HOST_COMMAND` | `usb_serial_send(USB_EVT_KEY_DOWN, key_id)` | | `MACRO` | Volle Sequenz ausführen (Steps[slot], keycode==0 = Ende, delay 10+20 ms) | | `PROFILE_SWITCH` | NVM laden → `active_profile` setzen → CRC neu berechnen → NVM speichern → `init_buttons()` | | `NONE` | nop | ## execute_action_up() — Taste losgelassen (Hold-Ende) | ActionType | Verhalten | |---|---| | `HID_KEY` | `usb_hid_release_key()` | | `HID_CONSUMER` | `usb_hid_release_consumer()` | | `HOST_COMMAND` | — (optional: könnte `USB_EVT_KEY_UP` senden) | | `MACRO`/`PROFILE_SWITCH`/`NONE` | nop | ## PROFILE_SWITCH — Ablauf ```cpp SDeviceConfig cfg; nvm_config_load(cfg); // komplette Config aus NVM uint8_t target = (uint8_t)action.data; if (target == 0xFF) target = (cfg.active_profile + 1) % 3; // Zyklus cfg.active_profile = target; cfg.crc = nvm_config_crc(cfg); // CRC MUSS nach Änderung neu berechnet werden! if (nvm_config_save(cfg)) // bool: false = NVM-Timeout init_buttons(); ``` > **Wichtig:** `active_profile` liegt im CRC-geschützten Bereich (ab Byte 7). Wird die CRC nicht aktualisiert, findet das nächste `nvm_config_load()` einen CRC-Fehler und lädt die Defaults (alle Aktionen NONE, alle LEDs Regenbogen). ## Hold-Modell (HID-Keys und Consumer Controls) Normale Tasten- und Media-Aktionen folgen dem **Hold-Modell**: ``` KEY_DOWN-Event vom Board → execute_action_down() → HID Key-Down senden [Taste bleibt physisch gedrückt...] KEY_UP-Event vom Board → execute_action_up() → HID Key-Up senden ``` Das OS erkennt die gedrückte Taste und startet sein eigenes Key-Repeat nach ~500 ms — wie auf einer normalen Tastatur. ## Tap-Modell (Encoder CW/CCW) Encoder-Bewegungen sind diskret (kein Halten möglich) und verwenden das **Tap-Modell**: ``` ENC_CW/ENC_CCW-Event → execute_action_down() + delay(10) + execute_action_up() ``` (Atomare Sequenz für jeden Encoder-Schritt.) ## Work-Loop-Reihenfolge ```cpp void work() { matrix_scan(); // → Events in Queue (KEY_DOWN, KEY_UP, ENC_CW, ENC_CCW) poll_vendor(); // Serial-Pakete verarbeiten (PC↔Board Kommandos) processEvents(); // → execute_action_down/up() aufrufen updateLEDs(); // Dirty-LEDs aktualisieren } ``` **processEvents() verarbeitet:** - `KEY_DOWN` → `execute_action_down()` - `KEY_UP` → `execute_action_up()` - `ENC_CW` / `ENC_CCW` → `execute_action_down()` + `delay(10)` + `execute_action_up()`