From 8d3d66c45f7221badede9fab76a9be077a2106c0 Mon Sep 17 00:00:00 2001 From: chenjintao Date: Thu, 20 Aug 2026 12:02:17 +0800 Subject: [PATCH] fix(control-plane): finalize ptt lifecycle on errors --- .../runtime/control_plane_runtime_workers.c | 284 +++++++++++------- 1 file changed, 183 insertions(+), 101 deletions(-) diff --git a/components/control_plane/src/runtime/control_plane_runtime_workers.c b/components/control_plane/src/runtime/control_plane_runtime_workers.c index dc83d77..1df20d3 100644 --- a/components/control_plane/src/runtime/control_plane_runtime_workers.c +++ b/components/control_plane/src/runtime/control_plane_runtime_workers.c @@ -230,7 +230,6 @@ static bool s_ws_start_requested; static uint32_t s_multimodal_network_failure_count; static bool s_wifi_reassociation_requested; #endif -static bool s_ptt_active; static ptt_runtime_t s_ptt_runtime; static TaskHandle_t s_lcd_render_loop_task; static TaskHandle_t s_wifi_state_task; @@ -572,88 +571,121 @@ static void control_plane_request_ptt_expression( } } -static void control_plane_set_ptt_active_with_trigger( - bool active, - domain_expression_trigger_t listening_ui_trigger) +static bool control_plane_deactivate_ptt( + bool require_generation, + uint32_t expected_generation, + const char *reason) { - if (active) { - control_plane_local_wake_pause_for_foreground(); - } - bool notify_worker = false; + bool uplink_running = control_plane_multimodal_is_uplink_running(); + bool restore_idle_ui = false; + domain_expression_trigger_t expression_trigger = + DOMAIN_EXPRESSION_TRIGGER_PTT_BUTTON; 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"; - bool uplink_running = !active && control_plane_multimodal_is_uplink_running(); - bool request_listening_ui = false; - bool restore_idle_ui = false; - domain_expression_trigger_t expression_trigger = listening_ui_trigger; + uint32_t generation = 0U; + const char *transition_reason = reason != NULL ? reason : "deactivate"; + portENTER_CRITICAL(&s_runtime_lock); + if (require_generation && + s_ptt_runtime.generation != expected_generation) { + portEXIT_CRITICAL(&s_runtime_lock); + return false; + } + if (require_generation && + !s_ptt_runtime.active && + !s_ptt_runtime.listening_ui_active && + s_ptt_runtime.state == PTT_STATE_IDLE) { + portEXIT_CRITICAL(&s_runtime_lock); + return false; + } + 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; - s_ptt_runtime.start_action_sent = false; - s_ptt_runtime.speech_action_sent = false; - s_ptt_runtime.listening_ui_active = true; - s_ptt_runtime.listening_ui_trigger = listening_ui_trigger; - request_listening_ui = true; - 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++; - restore_idle_ui = s_ptt_runtime.listening_ui_active; - expression_trigger = s_ptt_runtime.listening_ui_trigger; - s_ptt_runtime.listening_ui_active = false; - if (uplink_running || s_ptt_runtime.state == PTT_STATE_STREAMING) { - control_plane_ptt_transition_locked(PTT_STATE_TEARDOWN_LOCAL, "release_streaming"); - transition_reason = "release_streaming"; - } else 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_WAKE_PROMPT || - 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"; - } - notify_worker = true; + s_ptt_runtime.active = false; + s_ptt_runtime.press_tick = 0U; + s_ptt_runtime.foreground_connect = false; + s_ptt_runtime.foreground_attempts = 0; + s_ptt_runtime.pending_press_after_teardown = false; + s_ptt_runtime.generation++; + restore_idle_ui = s_ptt_runtime.listening_ui_active; + expression_trigger = s_ptt_runtime.listening_ui_trigger; + s_ptt_runtime.listening_ui_active = false; + if (uplink_running || s_ptt_runtime.state == PTT_STATE_STREAMING) { + control_plane_ptt_transition_locked(PTT_STATE_TEARDOWN_LOCAL, + transition_reason); + } else 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_WAKE_PROMPT || + s_ptt_runtime.state == PTT_STATE_WAIT_SPEECH_STARTED || + s_ptt_runtime.state == PTT_STATE_DENIED) { + control_plane_ptt_transition_locked(PTT_STATE_IDLE, + transition_reason); } 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 (request_listening_ui) { - control_plane_request_ptt_expression( - DOMAIN_EXPRESSION_LISTENING, - expression_trigger); - } else if (restore_idle_ui) { - control_plane_request_ptt_expression( - DOMAIN_EXPRESSION_IDLE, - expression_trigger); - } - if (notify_worker) { - control_plane_notify_wifi_state_task(); + + control_plane_ptt_log_transition(old_state, + new_state, + generation, + transition_reason); + ESP_LOGI(TAG, + "PTT lifecycle deactivated: generation=%u reason=%s state=%s", + (unsigned)generation, + transition_reason, + ptt_state_name(new_state)); + if (restore_idle_ui) { + control_plane_request_ptt_expression(DOMAIN_EXPRESSION_IDLE, + expression_trigger); } + control_plane_notify_wifi_state_task(); + control_plane_local_wake_resume_after_foreground(); + return true; +} + +static void control_plane_set_ptt_active_with_trigger( + bool active, + domain_expression_trigger_t listening_ui_trigger) +{ if (!active) { - control_plane_local_wake_resume_after_foreground(); + (void)control_plane_deactivate_ptt(false, 0U, "release"); + return; } + + control_plane_local_wake_pause_for_foreground(); + ptt_state_t old_state = PTT_STATE_IDLE; + ptt_state_t new_state = PTT_STATE_IDLE; + uint32_t generation = 0; + portENTER_CRITICAL(&s_runtime_lock); + old_state = s_ptt_runtime.state; + 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; + s_ptt_runtime.start_action_sent = false; + s_ptt_runtime.speech_action_sent = false; + s_ptt_runtime.listening_ui_active = true; + s_ptt_runtime.listening_ui_trigger = listening_ui_trigger; + 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"); + } + 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, + "press"); + control_plane_request_ptt_expression(DOMAIN_EXPRESSION_LISTENING, + listening_ui_trigger); + control_plane_notify_wifi_state_task(); } void control_plane_set_ptt_active(bool active) @@ -709,7 +741,7 @@ static bool control_plane_get_ptt_active(void) { bool active = false; portENTER_CRITICAL(&s_runtime_lock); - active = s_ptt_active; + active = s_ptt_runtime.active; portEXIT_CRITICAL(&s_runtime_lock); return active; } @@ -1626,12 +1658,14 @@ static void control_plane_handle_ptt_teardown(void) release_err == ESP_ERR_INVALID_STATE) { bool pending_press = false; bool active = false; + bool listening_ui_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; + listening_ui_active = s_ptt_runtime.listening_ui_active; s_ptt_runtime.pending_press_after_teardown = false; control_plane_ptt_transition_locked(PTT_STATE_IDLE, "teardown_complete"); if (pending_press && active) { @@ -1647,11 +1681,85 @@ static void control_plane_handle_ptt_teardown(void) 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"); + if (!(pending_press && active) && (active || listening_ui_active)) { + (void)control_plane_deactivate_ptt(true, + generation, + "teardown_complete"); + } } } +static bool control_plane_service_ptt_lifecycle(void) +{ + ptt_runtime_t ptt = control_plane_ptt_snapshot(); + if (ptt.state == PTT_STATE_IDLE) { + if (ptt.active || ptt.listening_ui_active) { + ESP_LOGW(TAG, + "Recover inconsistent PTT idle state: generation=%u active=%d listening_ui=%d", + (unsigned)ptt.generation, + ptt.active ? 1 : 0, + ptt.listening_ui_active ? 1 : 0); + (void)control_plane_deactivate_ptt(true, + ptt.generation, + "idle_invariant_recovery"); + return true; + } + return false; + } + + if (ptt.state == PTT_STATE_DENIED) { + (void)control_plane_deactivate_ptt(true, + ptt.generation, + "denied"); + return true; + } + + if (!ptt.active) { + return false; + } + + TickType_t now = xTaskGetTickCount(); + bool local_wake_prompt_gate = control_plane_local_wake_prompt_gate_active(); +#if CONFIG_TQ_PTT_MODE_CLICK_TO_TALK + TickType_t capture_timeout_ticks = + pdMS_TO_TICKS(CONFIG_TQ_PTT_CLICK_CAPTURE_TIMEOUT_MS); + if (capture_timeout_ticks == 0) { + capture_timeout_ticks = 1; + } + if (!local_wake_prompt_gate && + ptt.press_tick != 0 && + (now - ptt.press_tick) >= capture_timeout_ticks) { + (void)control_plane_deactivate_ptt(true, + ptt.generation, + "capture_timeout"); + return true; + } +#endif + + TickType_t denied_ticks = pdMS_TO_TICKS(CONTROL_PLANE_PTT_DENIED_TIMEOUT_MS); + if (denied_ticks == 0) { + denied_ticks = 1; + } + if (!local_wake_prompt_gate && + ptt.press_tick != 0 && + (now - ptt.press_tick) >= denied_ticks && + ptt.state != PTT_STATE_STREAMING) { + control_plane_ptt_set_state(PTT_STATE_DENIED, "wait_timeout"); + (void)control_plane_deactivate_ptt(true, + ptt.generation, + "wait_timeout"); + return true; + } + + return false; +} + static void control_plane_drive_ptt_state_machine(TickType_t *last_ws_start_attempt_tick) { + if (control_plane_service_ptt_lifecycle()) { + return; + } + ptt_runtime_t ptt = control_plane_ptt_snapshot(); if (ptt.state == PTT_STATE_IDLE) { return; @@ -1670,37 +1778,8 @@ static void control_plane_drive_ptt_state_machine(TickType_t *last_ws_start_atte return; } - if (ptt.state == PTT_STATE_DENIED) { - return; - } - TickType_t now = xTaskGetTickCount(); bool local_wake_prompt_gate = control_plane_local_wake_prompt_gate_active(); -#if CONFIG_TQ_PTT_MODE_CLICK_TO_TALK - TickType_t capture_timeout_ticks = pdMS_TO_TICKS(CONFIG_TQ_PTT_CLICK_CAPTURE_TIMEOUT_MS); - if (capture_timeout_ticks == 0) { - capture_timeout_ticks = 1; - } - if (!local_wake_prompt_gate && - ptt.press_tick != 0 && - (now - ptt.press_tick) >= capture_timeout_ticks) { - control_plane_set_ptt_active(false); - return; - } -#endif - - TickType_t denied_ticks = pdMS_TO_TICKS(CONTROL_PLANE_PTT_DENIED_TIMEOUT_MS); - if (denied_ticks == 0) { - denied_ticks = 1; - } - if (!local_wake_prompt_gate && - 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"); @@ -1890,6 +1969,8 @@ static void control_plane_wifi_state_task(void *arg) control_plane_memory_monitor_sample(now_tick, false); #endif + (void)control_plane_service_ptt_lifecycle(); + if (!connected) { if (!disconnect_pending) { disconnect_pending = true; @@ -1949,6 +2030,7 @@ static void control_plane_wifi_state_task(void *arg) if (reconnect_due && !provisioning_running && has_saved_credentials) { esp_err_t reconnect_err = control_plane_attempt_wifi_reconnect(); now_tick = xTaskGetTickCount(); + (void)control_plane_service_ptt_lifecycle(); reconnect_attempted = true; last_reconnect_attempt_tick = now_tick; if (ptt_foreground) {