#include "voice_interaction_internal.h" #include #include "esp_log.h" #include "freertos/queue.h" #define VOICE_MIC_KEY_TASK_STACK 6144 #define VOICE_MIC_KEY_QUEUE_LEN 8 #define VOICE_MIC_KEY_SESSION_READY_WAIT_MS 2000 #define VOICE_MIC_KEY_SESSION_READY_POLL_MS 30 #define VOICE_MIC_KEY_STATUS_PREVIEW_TIMEOUT_MS 3000 typedef enum { VOICE_MIC_KEY_CMD_PRESS_DOWN = 0, VOICE_MIC_KEY_CMD_PRESS_UP, } voice_mic_key_cmd_t; static const char *TAG = "voice_mic_key"; static QueueHandle_t s_mic_key_queue; static TaskHandle_t s_mic_key_task; static bool s_mic_key_task_with_caps; static bool s_mic_key_ready; static void voice_mic_key_show_status(const char *text) { if (text == NULL || text[0] == '\0') { return; } char preview_err[96] = {0}; esp_err_t preview_rc = screen_preview_show_text(text, VOICE_MIC_KEY_STATUS_PREVIEW_TIMEOUT_MS, preview_err, sizeof(preview_err)); if (preview_rc == ESP_OK || preview_rc == ESP_ERR_NOT_SUPPORTED) { return; } ESP_LOGW(TAG, "mic_key_status_preview_failed: rc=0x%x msg=%s", (unsigned)preview_rc, preview_err[0] != '\0' ? preview_err : "show text failed"); } static bool voice_mic_key_round_busy(const voice_interaction_status_t *voice, const printer_runtime_status_t *printer) { if (voice == NULL || printer == NULL) { return false; } bool marker_busy = voice->marker_image_gen_running; bool print_busy = printer->busy || printer->queue_depth > 0; return marker_busy || print_busy; } static bool voice_mic_key_session_ready(const voice_interaction_status_t *voice) { return voice != NULL && voice->session_active && voice->ws_connected && voice->started; } static bool voice_mic_key_wait_session_ready(uint32_t timeout_ms) { int64_t deadline_ms = voice_now_ms() + (int64_t)timeout_ms; while (voice_now_ms() < deadline_ms) { voice_interaction_status_t voice = {0}; voice_interaction_get_status(&voice); if (voice_mic_key_session_ready(&voice)) { return true; } vTaskDelay(pdMS_TO_TICKS(VOICE_MIC_KEY_SESSION_READY_POLL_MS)); } return false; } static void voice_mic_key_handle_press_down(void) { voice_interaction_status_t voice = {0}; voice_interaction_get_status(&voice); if (voice.tap_active) { ESP_LOGI(TAG, "MIC_KEY press ignored: tap already active"); (void)screen_preview_show_event(SCREEN_PREVIEW_EVENT_VOICE_LISTENING, NULL); return; } printer_runtime_status_t printer = {0}; printer_protocol_get_runtime_status(&printer); if (voice_mic_key_round_busy(&voice, &printer)) { ESP_LOGI(TAG, "MIC_KEY press ignored: state=%s marker_busy=%d print_busy=%d queue=%" PRIu32, voice_interaction_dialog_state_str(voice.dialog_state), voice.marker_image_gen_running, printer.busy, printer.queue_depth); (void)screen_preview_show_event(SCREEN_PREVIEW_EVENT_DEVICE_BUSY, NULL); return; } if (!voice_mic_key_session_ready(&voice)) { (void)screen_preview_show_event(SCREEN_PREVIEW_EVENT_VOICE_CONNECTING, NULL); char start_err[128] = {0}; esp_err_t start_rc = voice_interaction_start_with_timeout(0, start_err, sizeof(start_err)); if (start_rc != ESP_OK) { ESP_LOGW(TAG, "MIC_KEY auto session start failed: %s (%s)", esp_err_to_name(start_rc), start_err[0] != '\0' ? start_err : "-"); (void)screen_preview_show_event(SCREEN_PREVIEW_EVENT_VOICE_ERROR, NULL); return; } } if (!voice_mic_key_wait_session_ready(VOICE_MIC_KEY_SESSION_READY_WAIT_MS)) { voice_interaction_get_status(&voice); ESP_LOGW(TAG, "MIC_KEY wait session ready timeout: active=%d ws=%d started=%d", voice.session_active, voice.ws_connected, voice.started); (void)screen_preview_show_event(SCREEN_PREVIEW_EVENT_VOICE_ERROR, NULL); return; } char tap_err[128] = {0}; esp_err_t tap_rc = voice_interaction_tap_start(tap_err, sizeof(tap_err)); if (tap_rc != ESP_OK) { ESP_LOGW(TAG, "MIC_KEY tap start failed: %s (%s)", esp_err_to_name(tap_rc), tap_err[0] != '\0' ? tap_err : "-"); voice_mic_key_show_status("开始录音失败\n请重试"); return; } (void)screen_preview_show_event(SCREEN_PREVIEW_EVENT_VOICE_LISTENING, NULL); ESP_LOGI(TAG, "MIC_KEY tap started"); } static void voice_mic_key_handle_press_up(void) { voice_interaction_status_t voice = {0}; voice_interaction_get_status(&voice); if (!voice.tap_active) { return; } char tap_err[128] = {0}; esp_err_t tap_rc = voice_interaction_tap_cancel(tap_err, sizeof(tap_err)); if (tap_rc != ESP_OK) { ESP_LOGW(TAG, "MIC_KEY tap stop failed: %s (%s)", esp_err_to_name(tap_rc), tap_err[0] != '\0' ? tap_err : "-"); voice_mic_key_show_status("结束录音失败\n请重试"); return; } (void)screen_preview_show_event(SCREEN_PREVIEW_EVENT_VOICE_PROCESSING, NULL); ESP_LOGI(TAG, "MIC_KEY tap stopped"); } static void voice_mic_key_task_main(void *arg) { (void)arg; while (true) { voice_mic_key_cmd_t cmd = VOICE_MIC_KEY_CMD_PRESS_UP; if (xQueueReceive(s_mic_key_queue, &cmd, portMAX_DELAY) != pdTRUE) { continue; } switch (cmd) { case VOICE_MIC_KEY_CMD_PRESS_DOWN: voice_mic_key_handle_press_down(); break; case VOICE_MIC_KEY_CMD_PRESS_UP: voice_mic_key_handle_press_up(); break; default: break; } } } static void voice_mic_key_platform_event_cb(platform_mic_key_event_t event, void *user_data) { (void)user_data; if (s_mic_key_queue == NULL) { return; } voice_mic_key_cmd_t cmd = VOICE_MIC_KEY_CMD_PRESS_UP; switch (event) { case PLATFORM_MIC_KEY_EVENT_PRESS_DOWN: cmd = VOICE_MIC_KEY_CMD_PRESS_DOWN; break; case PLATFORM_MIC_KEY_EVENT_PRESS_UP: cmd = VOICE_MIC_KEY_CMD_PRESS_UP; break; default: return; } BaseType_t queued = xQueueSend(s_mic_key_queue, &cmd, 0); if (queued == pdTRUE) { return; } voice_mic_key_cmd_t dropped = VOICE_MIC_KEY_CMD_PRESS_UP; (void)xQueueReceive(s_mic_key_queue, &dropped, 0); (void)xQueueSend(s_mic_key_queue, &cmd, 0); } esp_err_t voice_interaction_mic_key_init(void) { if (s_mic_key_ready) { return ESP_OK; } esp_err_t err = platform_mic_key_init(); if (err == ESP_ERR_NOT_SUPPORTED) { ESP_LOGW(TAG, "MIC_KEY trigger disabled by board config"); s_mic_key_ready = true; return ESP_OK; } if (err != ESP_OK) { return err; } s_mic_key_queue = xQueueCreate(VOICE_MIC_KEY_QUEUE_LEN, sizeof(voice_mic_key_cmd_t)); if (s_mic_key_queue == NULL) { platform_mic_key_deinit(); return ESP_ERR_NO_MEM; } BaseType_t ok = voice_create_task_prefer_psram(voice_mic_key_task_main, "voice_mic_key", VOICE_MIC_KEY_TASK_STACK, 4, &s_mic_key_task, &s_mic_key_task_with_caps); if (ok != pdPASS) { vQueueDelete(s_mic_key_queue); s_mic_key_queue = NULL; platform_mic_key_deinit(); return ESP_ERR_NO_MEM; } err = platform_mic_key_set_event_callback(voice_mic_key_platform_event_cb, NULL); if (err != ESP_OK) { voice_delete_task(s_mic_key_task, s_mic_key_task_with_caps); s_mic_key_task = NULL; s_mic_key_task_with_caps = false; vQueueDelete(s_mic_key_queue); s_mic_key_queue = NULL; platform_mic_key_deinit(); return err; } s_mic_key_ready = true; ESP_LOGI(TAG, "MIC_KEY trigger ready: press to speak, release to process"); return ESP_OK; }