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 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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user