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 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,26 +571,94 @@ static void control_plane_request_ptt_expression(
}
}
static bool control_plane_deactivate_ptt(
bool require_generation,
uint32_t expected_generation,
const char *reason)
{
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 = 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_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);
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_pause_for_foreground();
if (!active) {
(void)control_plane_deactivate_ptt(false, 0U, "release");
return;
}
bool notify_worker = false;
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;
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;
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;
@@ -602,7 +669,6 @@ static void control_plane_set_ptt_active_with_trigger(
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;
@@ -610,50 +676,16 @@ static void control_plane_set_ptt_active_with_trigger(
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;
}
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_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();
}
if (!active) {
control_plane_local_wake_resume_after_foreground();
}
}
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) {