feat(voice): migrate to push2talk and serialize websocket text sends

This commit is contained in:
admin
2026-03-01 01:21:18 +08:00
parent 9deeab738e
commit dd108e2658
19 changed files with 891 additions and 224 deletions

View File

@@ -0,0 +1,227 @@
#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
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 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");
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);
return;
}
if (!voice_mic_key_session_ready(&voice)) {
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 : "-");
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);
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 : "-");
return;
}
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 : "-");
return;
}
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;
}