修复了第一次双击和对话没有反应问题

This commit is contained in:
CSBOOK\44744
2026-06-27 16:23:21 +08:00
parent 6aaf13d0a8
commit b829cd8ebc
6 changed files with 512 additions and 67 deletions
@@ -40,6 +40,11 @@
#define CONTROL_PLANE_USER_WEB_RETRY_MS 5000
#define CONTROL_PLANE_USER_WEB_STOP_TIMEOUT_MS 1500
#define CONTROL_PLANE_WS_START_RETRY_DELAY_MS 2000
#define CONTROL_PLANE_PTT_LISTENING_TIMEOUT_MS 3000
#define CONTROL_PLANE_PTT_SPEECH_STARTED_TIMEOUT_MS 1500
#define CONTROL_PLANE_PTT_TEARDOWN_TIMEOUT_MS 2000
#define CONTROL_PLANE_PTT_DENIED_TIMEOUT_MS 10000
#define CONTROL_PLANE_PTT_FOREGROUND_RECONNECT_MAX_ATTEMPTS 2
/* Liveness safety bound for the fixed two-cycle wealth spin intro (not the
* animation length). On timeout the worker proceeds to reveal + rotate anyway. */
#define CONTROL_PLANE_SPIN_INTRO_TIMEOUT_MS 6000
@@ -130,6 +135,30 @@ typedef struct {
char value[32];
} control_plane_model_command_t;
typedef enum {
PTT_STATE_IDLE = 0,
PTT_STATE_WAIT_WIFI,
PTT_STATE_WAIT_WS,
PTT_STATE_WAIT_START_SENT,
PTT_STATE_WAIT_LISTENING,
PTT_STATE_WAIT_SPEECH_STARTED,
PTT_STATE_STREAMING,
PTT_STATE_TEARDOWN_LOCAL,
PTT_STATE_TEARDOWN_REMOTE,
PTT_STATE_DENIED,
} ptt_state_t;
typedef struct {
ptt_state_t state;
uint32_t generation;
TickType_t state_enter_tick;
TickType_t press_tick;
bool active;
bool foreground_connect;
uint8_t foreground_attempts;
bool pending_press_after_teardown;
} ptt_runtime_t;
typedef struct {
TaskFunction_t task;
bool task_with_caps;
@@ -140,6 +169,7 @@ static bool s_model_cmd_queue_with_caps;
static TaskHandle_t s_model_cmd_task;
static bool s_ws_start_requested;
static bool s_ptt_active;
static ptt_runtime_t s_ptt_runtime;
#if CONFIG_TQ_DIRECTION_CONSOLE_ENABLE
static TaskHandle_t s_direction_console_task;
#endif
@@ -156,6 +186,8 @@ static uint32_t s_fortune_refresh_generation;
static volatile bool s_runtime_stop_requested;
static portMUX_TYPE s_runtime_lock = portMUX_INITIALIZER_UNLOCKED;
static TaskHandle_t control_plane_get_task_handle(TaskHandle_t *slot);
static bool control_plane_runtime_stop_requested(void)
{
bool stop_requested = false;
@@ -172,14 +204,125 @@ static void control_plane_set_runtime_stop_requested(bool stop_requested)
portEXIT_CRITICAL(&s_runtime_lock);
}
static const char *ptt_state_name(ptt_state_t state)
{
switch (state) {
case PTT_STATE_IDLE:
return "IDLE";
case PTT_STATE_WAIT_WIFI:
return "WAIT_WIFI";
case PTT_STATE_WAIT_WS:
return "WAIT_WS";
case PTT_STATE_WAIT_START_SENT:
return "WAIT_START_SENT";
case PTT_STATE_WAIT_LISTENING:
return "WAIT_LISTENING";
case PTT_STATE_WAIT_SPEECH_STARTED:
return "WAIT_SPEECH_STARTED";
case PTT_STATE_STREAMING:
return "STREAMING";
case PTT_STATE_TEARDOWN_LOCAL:
return "TEARDOWN_LOCAL";
case PTT_STATE_TEARDOWN_REMOTE:
return "TEARDOWN_REMOTE";
case PTT_STATE_DENIED:
return "DENIED";
default:
return "UNKNOWN";
}
}
static void control_plane_ptt_transition_locked(ptt_state_t next_state, const char *reason)
{
if (s_ptt_runtime.state == next_state) {
return;
}
(void)reason;
s_ptt_runtime.state = next_state;
s_ptt_runtime.state_enter_tick = xTaskGetTickCount();
if (next_state == PTT_STATE_IDLE ||
next_state == PTT_STATE_STREAMING ||
next_state == PTT_STATE_DENIED) {
s_ptt_runtime.foreground_connect = false;
}
}
static void control_plane_ptt_log_transition(ptt_state_t old_state,
ptt_state_t next_state,
uint32_t generation,
const char *reason)
{
if (old_state == next_state) {
return;
}
ESP_LOGI(TAG,
"PTT state %s -> %s generation=%u reason=%s",
ptt_state_name(old_state),
ptt_state_name(next_state),
(unsigned)generation,
reason != NULL ? reason : "-");
}
static void control_plane_notify_wifi_state_task(void)
{
TaskHandle_t task = control_plane_get_task_handle(&s_wifi_state_task);
if (task != NULL) {
xTaskNotifyGive(task);
}
}
void control_plane_set_ptt_active(bool active)
{
bool notify_worker = false;
ptt_state_t old_state = PTT_STATE_IDLE;
ptt_state_t new_state = PTT_STATE_IDLE;
uint32_t generation = 0;
const char *transition_reason = active ? "press" : "release";
portENTER_CRITICAL(&s_runtime_lock);
old_state = s_ptt_runtime.state;
s_ptt_active = active;
if (active) {
s_ws_start_requested = true;
s_ptt_runtime.generation++;
s_ptt_runtime.active = true;
s_ptt_runtime.press_tick = xTaskGetTickCount();
s_ptt_runtime.foreground_connect = true;
s_ptt_runtime.foreground_attempts = 0;
if (s_ptt_runtime.state == PTT_STATE_TEARDOWN_LOCAL ||
s_ptt_runtime.state == PTT_STATE_TEARDOWN_REMOTE) {
s_ptt_runtime.pending_press_after_teardown = true;
} else {
s_ptt_runtime.pending_press_after_teardown = false;
control_plane_ptt_transition_locked(PTT_STATE_WAIT_WIFI, "press");
}
notify_worker = true;
} else {
s_ptt_runtime.active = false;
s_ptt_runtime.foreground_connect = false;
s_ptt_runtime.foreground_attempts = 0;
s_ptt_runtime.pending_press_after_teardown = false;
s_ptt_runtime.generation++;
if (s_ptt_runtime.state == PTT_STATE_WAIT_WIFI ||
s_ptt_runtime.state == PTT_STATE_WAIT_WS ||
s_ptt_runtime.state == PTT_STATE_WAIT_START_SENT ||
s_ptt_runtime.state == PTT_STATE_WAIT_LISTENING ||
s_ptt_runtime.state == PTT_STATE_WAIT_SPEECH_STARTED ||
s_ptt_runtime.state == PTT_STATE_DENIED) {
control_plane_ptt_transition_locked(PTT_STATE_IDLE, "release_before_streaming");
transition_reason = "release_before_streaming";
} else if (s_ptt_runtime.state == PTT_STATE_STREAMING) {
control_plane_ptt_transition_locked(PTT_STATE_TEARDOWN_LOCAL, "release_streaming");
transition_reason = "release_streaming";
}
notify_worker = true;
}
new_state = s_ptt_runtime.state;
generation = s_ptt_runtime.generation;
portEXIT_CRITICAL(&s_runtime_lock);
control_plane_ptt_log_transition(old_state, new_state, generation, transition_reason);
if (notify_worker) {
control_plane_notify_wifi_state_task();
}
}
static bool control_plane_get_ptt_active(void)
@@ -984,17 +1127,19 @@ static esp_err_t control_plane_ensure_user_config_server(void)
#endif
}
static void control_plane_try_start_multimodal_from_state_machine(TickType_t *last_attempt_tick)
static bool control_plane_try_start_multimodal_from_state_machine(TickType_t *last_attempt_tick,
bool foreground)
{
TickType_t now = xTaskGetTickCount();
TickType_t retry_ticks = pdMS_TO_TICKS(CONTROL_PLANE_WS_START_RETRY_DELAY_MS);
if (retry_ticks == 0) {
retry_ticks = 1;
}
if (last_attempt_tick != NULL &&
if (!foreground &&
last_attempt_tick != NULL &&
*last_attempt_tick != 0 &&
(now - *last_attempt_tick) < retry_ticks) {
return;
return false;
}
if (last_attempt_tick != NULL) {
*last_attempt_tick = now;
@@ -1003,30 +1148,196 @@ static void control_plane_try_start_multimodal_from_state_machine(TickType_t *la
esp_err_t err = control_plane_start_multimodal_client();
if (err == ESP_OK) {
ESP_LOGI(TAG, "Multimodal client started");
return;
return true;
}
ESP_LOGW(TAG, "Multimodal client start failed: %s", esp_err_to_name(err));
return false;
}
static void control_plane_drive_ptt_state_machine(void)
static void control_plane_ptt_set_state(ptt_state_t next_state, const char *reason)
{
if (!control_plane_get_ptt_active()) {
ptt_state_t old_state = PTT_STATE_IDLE;
uint32_t generation = 0;
portENTER_CRITICAL(&s_runtime_lock);
old_state = s_ptt_runtime.state;
control_plane_ptt_transition_locked(next_state, reason);
generation = s_ptt_runtime.generation;
portEXIT_CRITICAL(&s_runtime_lock);
control_plane_ptt_log_transition(old_state, next_state, generation, reason);
}
static ptt_runtime_t control_plane_ptt_snapshot(void)
{
ptt_runtime_t snapshot = {0};
portENTER_CRITICAL(&s_runtime_lock);
snapshot = s_ptt_runtime;
portEXIT_CRITICAL(&s_runtime_lock);
return snapshot;
}
static void control_plane_handle_ptt_teardown(void)
{
ptt_runtime_t ptt = control_plane_ptt_snapshot();
if (ptt.state == PTT_STATE_TEARDOWN_LOCAL) {
control_plane_multimodal_stop_uplink();
control_plane_ptt_set_state(PTT_STATE_TEARDOWN_REMOTE, "uplink_stopped");
ptt = control_plane_ptt_snapshot();
}
if (ptt.state != PTT_STATE_TEARDOWN_REMOTE) {
return;
}
esp_err_t release_err = control_plane_multimodal_send_speech_stop();
if (release_err != ESP_OK && release_err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "PTT release failed: %s", esp_err_to_name(release_err));
}
TickType_t now = xTaskGetTickCount();
TickType_t timeout_ticks = pdMS_TO_TICKS(CONTROL_PLANE_PTT_TEARDOWN_TIMEOUT_MS);
if (timeout_ticks == 0) {
timeout_ticks = 1;
}
if (release_err == ESP_OK ||
(now - ptt.state_enter_tick) >= timeout_ticks ||
release_err == ESP_ERR_INVALID_STATE) {
bool pending_press = false;
bool active = false;
ptt_state_t old_state = PTT_STATE_IDLE;
uint32_t generation = 0;
portENTER_CRITICAL(&s_runtime_lock);
old_state = s_ptt_runtime.state;
pending_press = s_ptt_runtime.pending_press_after_teardown;
active = s_ptt_runtime.active;
s_ptt_runtime.pending_press_after_teardown = false;
control_plane_ptt_transition_locked(PTT_STATE_IDLE, "teardown_complete");
if (pending_press && active) {
s_ws_start_requested = true;
s_ptt_runtime.generation++;
s_ptt_runtime.foreground_connect = true;
s_ptt_runtime.foreground_attempts = 0;
s_ptt_runtime.press_tick = xTaskGetTickCount();
control_plane_ptt_transition_locked(PTT_STATE_WAIT_WIFI, "pending_press");
}
generation = s_ptt_runtime.generation;
ptt_state_t next_state = s_ptt_runtime.state;
portEXIT_CRITICAL(&s_runtime_lock);
control_plane_ptt_log_transition(old_state, next_state, generation, "teardown_complete");
}
}
static void control_plane_drive_ptt_state_machine(TickType_t *last_ws_start_attempt_tick)
{
ptt_runtime_t ptt = control_plane_ptt_snapshot();
if (ptt.state == PTT_STATE_IDLE) {
return;
}
if (ptt.state == PTT_STATE_TEARDOWN_LOCAL ||
ptt.state == PTT_STATE_TEARDOWN_REMOTE) {
control_plane_handle_ptt_teardown();
return;
}
if (!ptt.active) {
if (ptt.state != PTT_STATE_IDLE) {
control_plane_ptt_set_state(PTT_STATE_IDLE, "inactive");
}
return;
}
if (ptt.state == PTT_STATE_DENIED) {
return;
}
TickType_t now = xTaskGetTickCount();
TickType_t denied_ticks = pdMS_TO_TICKS(CONTROL_PLANE_PTT_DENIED_TIMEOUT_MS);
if (denied_ticks == 0) {
denied_ticks = 1;
}
if (ptt.press_tick != 0 &&
(now - ptt.press_tick) >= denied_ticks &&
ptt.state != PTT_STATE_STREAMING &&
ptt.state != PTT_STATE_DENIED) {
control_plane_ptt_set_state(PTT_STATE_DENIED, "wait_timeout");
return;
}
if (!control_plane_is_sta_connected()) {
control_plane_ptt_set_state(PTT_STATE_WAIT_WIFI, "wifi_unavailable");
return;
}
if (!control_plane_multimodal_is_connected()) {
control_plane_ptt_set_state(PTT_STATE_WAIT_WS, "ws_unavailable");
bool try_foreground = ptt.foreground_connect &&
ptt.foreground_attempts <
CONTROL_PLANE_PTT_FOREGROUND_RECONNECT_MAX_ATTEMPTS;
if (try_foreground) {
bool started = control_plane_try_start_multimodal_from_state_machine(
last_ws_start_attempt_tick,
true);
portENTER_CRITICAL(&s_runtime_lock);
s_ptt_runtime.foreground_attempts++;
portEXIT_CRITICAL(&s_runtime_lock);
if (!started &&
ptt.foreground_attempts + 1U >=
CONTROL_PLANE_PTT_FOREGROUND_RECONNECT_MAX_ATTEMPTS) {
control_plane_ptt_set_state(PTT_STATE_DENIED, "ws_foreground_attempts_exhausted");
}
} else {
(void)control_plane_schedule_websocket_wait_task();
}
return;
}
control_plane_multimodal_runtime_state_t state = {0};
if (control_plane_multimodal_get_runtime_state(&state) != ESP_OK) {
return;
}
if (!state.has_active_task) {
control_plane_ptt_set_state(PTT_STATE_WAIT_START_SENT, "send_start");
} else if (!state.server_listening) {
control_plane_ptt_set_state(PTT_STATE_WAIT_LISTENING, "wait_listening");
} else if (!state.client_speech_active) {
control_plane_ptt_set_state(PTT_STATE_WAIT_SPEECH_STARTED, "send_speech");
} else if (control_plane_multimodal_is_uplink_running()) {
control_plane_ptt_set_state(PTT_STATE_STREAMING, "uplink_running");
} else {
control_plane_ptt_set_state(PTT_STATE_STREAMING, "start_uplink");
}
ptt = control_plane_ptt_snapshot();
if ((ptt.state == PTT_STATE_WAIT_START_SENT ||
ptt.state == PTT_STATE_WAIT_LISTENING) &&
(now - ptt.state_enter_tick) >=
pdMS_TO_TICKS(CONTROL_PLANE_PTT_LISTENING_TIMEOUT_MS)) {
control_plane_multimodal_mark_transport_unavailable(
CONTROL_PLANE_TRANSPORT_LOSS_LISTENING_TIMEOUT,
__func__);
control_plane_ptt_set_state(PTT_STATE_WAIT_WS, "listening_timeout");
(void)control_plane_schedule_websocket_wait_task();
return;
}
if (ptt.state == PTT_STATE_WAIT_SPEECH_STARTED &&
(now - ptt.state_enter_tick) >=
pdMS_TO_TICKS(CONTROL_PLANE_PTT_SPEECH_STARTED_TIMEOUT_MS)) {
control_plane_multimodal_mark_transport_unavailable(
CONTROL_PLANE_TRANSPORT_LOSS_LISTENING_TIMEOUT,
__func__);
control_plane_ptt_set_state(PTT_STATE_WAIT_WS, "speech_started_timeout");
(void)control_plane_schedule_websocket_wait_task();
return;
}
esp_err_t press_err = control_plane_multimodal_handle_ptt_press_step();
if (press_err != ESP_OK &&
press_err != ESP_ERR_INVALID_STATE &&
press_err != ESP_ERR_NOT_SUPPORTED) {
if (press_err == ESP_OK) {
control_plane_multimodal_reset_uplink_failure_window();
} else if (press_err != ESP_ERR_INVALID_STATE &&
press_err != ESP_ERR_NOT_SUPPORTED) {
ESP_LOGW(TAG, "PTT press step failed: %s", esp_err_to_name(press_err));
}
}
@@ -1066,6 +1377,7 @@ static void control_plane_wifi_state_task(void *arg)
TickType_t last_ws_start_attempt_tick = 0;
uint32_t reconnect_delay_ms = CONTROL_PLANE_WIFI_RECONNECT_BASE_DELAY_MS;
bool reconnect_attempted = false;
uint8_t ptt_wifi_foreground_attempts = 0;
if (!was_connected) {
ESP_LOGI(TAG, "Wi-Fi STA unavailable at runtime start; requesting disconnected expression");
@@ -1088,7 +1400,7 @@ static void control_plane_wifi_state_task(void *arg)
TickType_t next_delay_ticks = control_plane_get_ptt_active() ?
pdMS_TO_TICKS(CONTROL_PLANE_PTT_ACTIVE_POLL_INTERVAL_MS) :
poll_ticks;
vTaskDelay(next_delay_ticks > 0 ? next_delay_ticks : 1);
(void)ulTaskNotifyTake(pdTRUE, next_delay_ticks > 0 ? next_delay_ticks : 1);
bool connected = control_plane_is_sta_connected();
TickType_t now_tick = xTaskGetTickCount();
@@ -1101,7 +1413,9 @@ static void control_plane_wifi_state_task(void *arg)
(unsigned)CONTROL_PLANE_WIFI_DISCONNECT_GRACE_MS);
}
bool ptt_foreground_disconnect = control_plane_get_ptt_active();
bool disconnect_grace_elapsed =
ptt_foreground_disconnect ||
disconnected_expression_active ||
(now_tick - disconnect_started_tick) >= disconnect_grace_ticks;
if (!disconnect_grace_elapsed) {
@@ -1132,20 +1446,31 @@ static void control_plane_wifi_state_task(void *arg)
if (reconnect_delay_ticks == 0) {
reconnect_delay_ticks = 1;
}
bool reconnect_due = !reconnect_attempted ||
bool ptt_foreground = control_plane_get_ptt_active() &&
ptt_wifi_foreground_attempts <
CONTROL_PLANE_PTT_FOREGROUND_RECONNECT_MAX_ATTEMPTS;
bool reconnect_due = ptt_foreground ||
!reconnect_attempted ||
(now_tick - last_reconnect_attempt_tick) >= reconnect_delay_ticks;
if (reconnect_due && domain_runtime_wifi_portal_has_saved_credentials()) {
esp_err_t reconnect_err = control_plane_attempt_wifi_reconnect();
now_tick = xTaskGetTickCount();
reconnect_attempted = true;
last_reconnect_attempt_tick = now_tick;
if (ptt_foreground) {
ptt_wifi_foreground_attempts++;
}
if (reconnect_err == ESP_OK) {
connected = true;
reconnect_delay_ms = CONTROL_PLANE_WIFI_RECONNECT_BASE_DELAY_MS;
ptt_wifi_foreground_attempts = 0;
} else {
reconnect_delay_ms = control_plane_next_wifi_reconnect_delay_ms(reconnect_delay_ms);
}
}
if (!control_plane_get_ptt_active()) {
ptt_wifi_foreground_attempts = 0;
}
}
if (connected && disconnect_pending && !disconnected_expression_active) {
@@ -1178,10 +1503,10 @@ static void control_plane_wifi_state_task(void *arg)
if (connected) {
bool ws_start_due = control_plane_consume_ws_start_requested();
if (ws_start_due && !control_plane_multimodal_is_connected()) {
control_plane_try_start_multimodal_from_state_machine(&last_ws_start_attempt_tick);
control_plane_try_start_multimodal_from_state_machine(&last_ws_start_attempt_tick, false);
now_tick = xTaskGetTickCount();
}
control_plane_drive_ptt_state_machine();
control_plane_drive_ptt_state_machine(&last_ws_start_attempt_tick);
TickType_t web_retry_ticks = pdMS_TO_TICKS(CONTROL_PLANE_USER_WEB_RETRY_MS);
if (web_retry_ticks == 0) {
@@ -135,10 +135,6 @@ static void control_plane_dispatch_key_event(const control_plane_key_event_t *ev
case CONTROL_PLANE_KEY_EVENT_RELEASED: {
control_plane_set_ptt_active(false);
esp_err_t release_err = control_plane_multimodal_handle_ptt_release();
if (release_err != ESP_OK && release_err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "PTT release failed: %s", esp_err_to_name(release_err));
}
break;
}