258 lines
8.5 KiB
C
258 lines
8.5 KiB
C
#include "voice_interaction_internal.h"
|
|
|
|
#include <inttypes.h>
|
|
|
|
#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;
|
|
}
|