diff --git a/src/keymap/key_state_machine.c b/src/keymap/key_state_machine.c index 007fb00..335108f 100644 --- a/src/keymap/key_state_machine.c +++ b/src/keymap/key_state_machine.c @@ -1,6 +1,25 @@ #include "key_state_machine.h" #include "app_config.h" #include +#include +#include + +static SemaphoreHandle_t s_key_engine_mutex = NULL; + +static void key_engine_lock(void) { + if (!s_key_engine_mutex) { + s_key_engine_mutex = xSemaphoreCreateRecursiveMutex(); + } + if (s_key_engine_mutex) { + xSemaphoreTakeRecursive(s_key_engine_mutex, portMAX_DELAY); + } +} + +static void key_engine_unlock(void) { + if (s_key_engine_mutex) { + xSemaphoreGiveRecursive(s_key_engine_mutex); + } +} static int find_binding_index(const key_mapper_engine_t *engine, uint8_t raw_key) { if (!engine) return -1; @@ -184,37 +203,47 @@ void key_engine_load_defaults(key_mapper_engine_t *engine) { bool key_engine_set_binding(key_mapper_engine_t *engine, const key_binding_t *binding) { if (!engine || !binding) return false; + key_engine_lock(); int idx = find_binding_index(engine, binding->source_vk); if (idx >= 0) { engine->bindings[idx] = *binding; + key_engine_unlock(); return true; } if (engine->binding_count < MAX_KEY_BINDINGS) { engine->bindings[engine->binding_count++] = *binding; + key_engine_unlock(); return true; } + key_engine_unlock(); return false; } bool key_engine_get_binding(const key_mapper_engine_t *engine, uint8_t source_vk, key_binding_t *out_binding) { if (!engine || !out_binding) return false; + key_engine_lock(); int idx = find_binding_index(engine, source_vk); if (idx >= 0) { *out_binding = engine->bindings[idx]; + key_engine_unlock(); return true; } + key_engine_unlock(); return false; } void key_engine_init(key_mapper_engine_t *engine, key_output_callback_t cb) { if (!engine) return; + key_engine_lock(); memset(engine, 0, sizeof(key_mapper_engine_t)); engine->output_cb = cb; key_engine_load_defaults(engine); + key_engine_unlock(); } void key_engine_feed_key(key_mapper_engine_t *engine, uint8_t raw_key_code, bool is_pressed, uint32_t now_ms) { if (!engine) return; + key_engine_lock(); // Immediately record telemetry on EVERY single button state change engine->last_telemetry.source_vk = raw_key_code; @@ -222,7 +251,10 @@ void key_engine_feed_key(key_mapper_engine_t *engine, uint8_t raw_key_code, bool engine->last_telemetry.timestamp = now_ms; int idx = find_binding_index(engine, raw_key_code); - if (idx < 0) return; + if (idx < 0) { + key_engine_unlock(); + return; + } key_binding_t *b = &engine->bindings[idx]; key_slot_state_t *s = &engine->states[idx]; @@ -279,10 +311,12 @@ void key_engine_feed_key(key_mapper_engine_t *engine, uint8_t raw_key_code, bool } } } + key_engine_unlock(); } void key_engine_tick(key_mapper_engine_t *engine, uint32_t now_ms) { if (!engine) return; + key_engine_lock(); for (size_t i = 0; i < engine->binding_count; i++) { key_binding_t *b = &engine->bindings[i]; @@ -315,10 +349,12 @@ void key_engine_tick(key_mapper_engine_t *engine, uint32_t now_ms) { } } } + key_engine_unlock(); } void key_engine_release_all(key_mapper_engine_t *engine, uint32_t now_ms) { if (!engine) return; + key_engine_lock(); for (size_t i = 0; i < engine->binding_count; i++) { key_binding_t *b = &engine->bindings[i]; @@ -330,4 +366,5 @@ void key_engine_release_all(key_mapper_engine_t *engine, uint32_t now_ms) { s->waiting_double = false; s->press_count = 0; } + key_engine_unlock(); } diff --git a/src/led_indicator.cpp b/src/led_indicator.cpp index da3fdab..70061c7 100644 --- a/src/led_indicator.cpp +++ b/src/led_indicator.cpp @@ -59,7 +59,9 @@ void led_indicator_init(void) { void led_indicator_set(led_state_t state) { if (state == LED_STATE_HID_KEY_PRESS || state == LED_STATE_MIC_KEY_PRESS) return; // Use trigger for flashes + s_is_flashing = false; s_current_base_state = state; + update_hardware_led(state); } void led_indicator_trigger_key(bool is_voice_key) { diff --git a/src/main.cpp b/src/main.cpp index ce3c21d..1d17854 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ static void ble_task_core0(void* param) { while (true) { ble_remote_task(); + key_engine_tick(&g_key_engine, millis()); vTaskDelay(pdMS_TO_TICKS(5)); } } @@ -80,16 +81,13 @@ void loop() { // 1. Service TinyUSB & Audio push usb_composite_task(); - // 2. Service Key State Machine timers (long press, double click, repeat) - key_engine_tick(&g_key_engine, now); - - // 3. Service Wi-Fi & DNS tasks + // 2. Service Wi-Fi & DNS tasks wifi_manager_task(); - // 4. Service HTTP Web Server + // 3. Service HTTP Web Server web_server_task(); - // 5. Service Serial / WebSerial CLI commands + // 4. Service Serial / WebSerial CLI commands cli_manager_task(); // No delay here — USB audio task handles its own timing via vTaskDelayUntil diff --git a/src/usb/uac_microphone.cpp b/src/usb/uac_microphone.cpp index 076813b..999b0c3 100644 --- a/src/usb/uac_microphone.cpp +++ b/src/usb/uac_microphone.cpp @@ -150,12 +150,6 @@ static bool uac_driver_control_xfer_cb(uint8_t rhport, uint8_t stage, s_uac_alt = alt; s_uac_streaming = (alt == 1); - if (s_uac_streaming) { - led_indicator_set(LED_STATE_MIC_STREAMING); - } else { - led_indicator_set(LED_STATE_CONNECTED); - } - // Force release endpoint to clear stuck busy flags from aborted transfers usbd_edpt_close(rhport, (uint8_t)(s_uac_ep_in | 0x80)); diff --git a/src/usb/usb_composite.cpp b/src/usb/usb_composite.cpp index 54f676a..bea3284 100644 --- a/src/usb/usb_composite.cpp +++ b/src/usb/usb_composite.cpp @@ -45,18 +45,41 @@ void usb_composite_task(void) { uac_microphone_task(); } +static SemaphoreHandle_t s_hid_mutex = NULL; + +static void hid_lock(void) { + if (!s_hid_mutex) { + s_hid_mutex = xSemaphoreCreateMutex(); + } + if (s_hid_mutex) { + xSemaphoreTake(s_hid_mutex, portMAX_DELAY); + } +} + +static void hid_unlock(void) { + if (s_hid_mutex) { + xSemaphoreGive(s_hid_mutex); + } +} + bool usb_hid_keyboard_press(uint8_t modifier, uint8_t keycode) { if (!s_usb_ready) return false; + hid_lock(); KeyReport report = {0}; report.modifiers = modifier; report.keys[0] = keycode; s_keyboard.sendReport(&report); + hid_unlock(); return true; } bool usb_hid_keyboard_release(void) { if (!s_usb_ready) return false; + hid_lock(); + KeyReport report = {0}; // All zeroes + s_keyboard.sendReport(&report); s_keyboard.releaseAll(); + hid_unlock(); return true; } @@ -70,21 +93,27 @@ bool usb_hid_keyboard_tap(uint8_t modifier, uint8_t keycode) { bool usb_hid_consumer_press(uint16_t usage_code) { if (!s_usb_ready) return false; + hid_lock(); s_consumer.press(usage_code); + hid_unlock(); return true; } bool usb_hid_consumer_release(void) { if (!s_usb_ready) return false; + hid_lock(); s_consumer.release(); + hid_unlock(); return true; } bool usb_hid_consumer_tap(uint16_t usage_code) { if (!s_usb_ready) return false; + hid_lock(); s_consumer.press(usage_code); delay(15); s_consumer.release(); + hid_unlock(); return true; } @@ -94,10 +123,12 @@ void usb_hid_dispatch_action(const key_action_t *action) { app_log("USB_HID", "Emit Action: type=%d, mod=0x%02X, key=0x%02X, cons=0x%04X", action->type, action->modifier, action->key_code, action->consumer_code); - if (action->type == ACTION_VOICE_HOLD || action->type == ACTION_VOICE_RELEASE) { - led_indicator_trigger_key(true); // Voice key + if (action->type == ACTION_VOICE_HOLD) { + led_indicator_set(LED_STATE_MIC_STREAMING); // Solid Blue while voice recording + } else if (action->type == ACTION_VOICE_RELEASE) { + led_indicator_set(LED_STATE_CONNECTED); // Solid Green when voice recording ends } else { - led_indicator_trigger_key(false); // Other key + led_indicator_trigger_key(false); // Yellow flash on ordinary HID key actions } switch (action->type) {