fix(control-plane): finalize ptt lifecycle on errors
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user