fix(control-plane): finalize ptt lifecycle on errors

This commit is contained in:
chenjintao
2026-08-20 12:02:17 +08:00
parent e6ef4e7758
commit 8d3d66c45f
@@ -230,7 +230,6 @@ static bool s_ws_start_requested;
static uint32_t s_multimodal_network_failure_count; static uint32_t s_multimodal_network_failure_count;
static bool s_wifi_reassociation_requested; static bool s_wifi_reassociation_requested;
#endif #endif
static bool s_ptt_active;
static ptt_runtime_t s_ptt_runtime; static ptt_runtime_t s_ptt_runtime;
static TaskHandle_t s_lcd_render_loop_task; static TaskHandle_t s_lcd_render_loop_task;
static TaskHandle_t s_wifi_state_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( static bool control_plane_deactivate_ptt(
bool active, bool require_generation,
domain_expression_trigger_t listening_ui_trigger) uint32_t expected_generation,
const char *reason)
{ {
if (active) { bool uplink_running = control_plane_multimodal_is_uplink_running();
control_plane_local_wake_pause_for_foreground(); bool restore_idle_ui = false;
} domain_expression_trigger_t expression_trigger =
bool notify_worker = false; DOMAIN_EXPRESSION_TRIGGER_PTT_BUTTON;
ptt_state_t old_state = PTT_STATE_IDLE; ptt_state_t old_state = PTT_STATE_IDLE;
ptt_state_t new_state = PTT_STATE_IDLE; ptt_state_t new_state = PTT_STATE_IDLE;
uint32_t generation = 0; uint32_t generation = 0U;
const char *transition_reason = active ? "press" : "release"; const char *transition_reason = reason != NULL ? reason : "deactivate";
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;
portENTER_CRITICAL(&s_runtime_lock); 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; old_state = s_ptt_runtime.state;
s_ptt_active = active; s_ptt_runtime.active = false;
if (active) { s_ptt_runtime.press_tick = 0U;
s_ws_start_requested = true; s_ptt_runtime.foreground_connect = false;
s_ptt_runtime.generation++; s_ptt_runtime.foreground_attempts = 0;
s_ptt_runtime.active = true; s_ptt_runtime.pending_press_after_teardown = false;
s_ptt_runtime.press_tick = xTaskGetTickCount(); s_ptt_runtime.generation++;
s_ptt_runtime.foreground_connect = true; restore_idle_ui = s_ptt_runtime.listening_ui_active;
s_ptt_runtime.foreground_attempts = 0; expression_trigger = s_ptt_runtime.listening_ui_trigger;
s_ptt_runtime.start_action_sent = false; s_ptt_runtime.listening_ui_active = false;
s_ptt_runtime.speech_action_sent = false; if (uplink_running || s_ptt_runtime.state == PTT_STATE_STREAMING) {
s_ptt_runtime.listening_ui_active = true; control_plane_ptt_transition_locked(PTT_STATE_TEARDOWN_LOCAL,
s_ptt_runtime.listening_ui_trigger = listening_ui_trigger; transition_reason);
request_listening_ui = true; } else if (s_ptt_runtime.state == PTT_STATE_WAIT_WIFI ||
if (s_ptt_runtime.state == PTT_STATE_TEARDOWN_LOCAL || s_ptt_runtime.state == PTT_STATE_WAIT_WS ||
s_ptt_runtime.state == PTT_STATE_TEARDOWN_REMOTE) { s_ptt_runtime.state == PTT_STATE_WAIT_START_SENT ||
s_ptt_runtime.pending_press_after_teardown = true; s_ptt_runtime.state == PTT_STATE_WAIT_LISTENING ||
} else { s_ptt_runtime.state == PTT_STATE_WAIT_WAKE_PROMPT ||
s_ptt_runtime.pending_press_after_teardown = false; s_ptt_runtime.state == PTT_STATE_WAIT_SPEECH_STARTED ||
control_plane_ptt_transition_locked(PTT_STATE_WAIT_WIFI, "press"); s_ptt_runtime.state == PTT_STATE_DENIED) {
} control_plane_ptt_transition_locked(PTT_STATE_IDLE,
notify_worker = true; transition_reason);
} 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;
} }
new_state = s_ptt_runtime.state; new_state = s_ptt_runtime.state;
generation = s_ptt_runtime.generation; generation = s_ptt_runtime.generation;
portEXIT_CRITICAL(&s_runtime_lock); portEXIT_CRITICAL(&s_runtime_lock);
control_plane_ptt_log_transition(old_state, new_state, generation, transition_reason);
if (request_listening_ui) { control_plane_ptt_log_transition(old_state,
control_plane_request_ptt_expression( new_state,
DOMAIN_EXPRESSION_LISTENING, generation,
expression_trigger); transition_reason);
} else if (restore_idle_ui) { ESP_LOGI(TAG,
control_plane_request_ptt_expression( "PTT lifecycle deactivated: generation=%u reason=%s state=%s",
DOMAIN_EXPRESSION_IDLE, (unsigned)generation,
expression_trigger); transition_reason,
} ptt_state_name(new_state));
if (notify_worker) { if (restore_idle_ui) {
control_plane_notify_wifi_state_task(); 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) { 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) void control_plane_set_ptt_active(bool active)
@@ -709,7 +741,7 @@ static bool control_plane_get_ptt_active(void)
{ {
bool active = false; bool active = false;
portENTER_CRITICAL(&s_runtime_lock); portENTER_CRITICAL(&s_runtime_lock);
active = s_ptt_active; active = s_ptt_runtime.active;
portEXIT_CRITICAL(&s_runtime_lock); portEXIT_CRITICAL(&s_runtime_lock);
return active; return active;
} }
@@ -1626,12 +1658,14 @@ static void control_plane_handle_ptt_teardown(void)
release_err == ESP_ERR_INVALID_STATE) { release_err == ESP_ERR_INVALID_STATE) {
bool pending_press = false; bool pending_press = false;
bool active = false; bool active = false;
bool listening_ui_active = false;
ptt_state_t old_state = PTT_STATE_IDLE; ptt_state_t old_state = PTT_STATE_IDLE;
uint32_t generation = 0; uint32_t generation = 0;
portENTER_CRITICAL(&s_runtime_lock); portENTER_CRITICAL(&s_runtime_lock);
old_state = s_ptt_runtime.state; old_state = s_ptt_runtime.state;
pending_press = s_ptt_runtime.pending_press_after_teardown; pending_press = s_ptt_runtime.pending_press_after_teardown;
active = s_ptt_runtime.active; active = s_ptt_runtime.active;
listening_ui_active = s_ptt_runtime.listening_ui_active;
s_ptt_runtime.pending_press_after_teardown = false; s_ptt_runtime.pending_press_after_teardown = false;
control_plane_ptt_transition_locked(PTT_STATE_IDLE, "teardown_complete"); control_plane_ptt_transition_locked(PTT_STATE_IDLE, "teardown_complete");
if (pending_press && active) { 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; ptt_state_t next_state = s_ptt_runtime.state;
portEXIT_CRITICAL(&s_runtime_lock); portEXIT_CRITICAL(&s_runtime_lock);
control_plane_ptt_log_transition(old_state, next_state, generation, "teardown_complete"); 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) 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(); ptt_runtime_t ptt = control_plane_ptt_snapshot();
if (ptt.state == PTT_STATE_IDLE) { if (ptt.state == PTT_STATE_IDLE) {
return; return;
@@ -1670,37 +1778,8 @@ static void control_plane_drive_ptt_state_machine(TickType_t *last_ws_start_atte
return; return;
} }
if (ptt.state == PTT_STATE_DENIED) {
return;
}
TickType_t now = xTaskGetTickCount(); TickType_t now = xTaskGetTickCount();
bool local_wake_prompt_gate = control_plane_local_wake_prompt_gate_active(); 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()) { if (!control_plane_is_sta_connected()) {
control_plane_ptt_set_state(PTT_STATE_WAIT_WIFI, "wifi_unavailable"); 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); control_plane_memory_monitor_sample(now_tick, false);
#endif #endif
(void)control_plane_service_ptt_lifecycle();
if (!connected) { if (!connected) {
if (!disconnect_pending) { if (!disconnect_pending) {
disconnect_pending = true; disconnect_pending = true;
@@ -1949,6 +2030,7 @@ static void control_plane_wifi_state_task(void *arg)
if (reconnect_due && !provisioning_running && has_saved_credentials) { if (reconnect_due && !provisioning_running && has_saved_credentials) {
esp_err_t reconnect_err = control_plane_attempt_wifi_reconnect(); esp_err_t reconnect_err = control_plane_attempt_wifi_reconnect();
now_tick = xTaskGetTickCount(); now_tick = xTaskGetTickCount();
(void)control_plane_service_ptt_lifecycle();
reconnect_attempted = true; reconnect_attempted = true;
last_reconnect_attempt_tick = now_tick; last_reconnect_attempt_tick = now_tick;
if (ptt_foreground) { if (ptt_foreground) {