fix(multimodal): harden playback and client lifecycle handling
- retry the server connection prompt after transient playback failures - serialize multimodal client and session start-stop operations - tag downstream responses to ignore stale packets and playback events - complete bounded teardown and clean callbacks after partial startup
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
@@ -40,6 +41,9 @@
|
||||
#define CONTROL_PLANE_IDLE_TRANSPORT_SHUTDOWN_MS 30000
|
||||
#define CONTROL_PLANE_WATCHDOG_TASK_STACK_SIZE 6144
|
||||
#define CONTROL_PLANE_SERVER_SUCCESS_PROMPT_FILE "/littlefs/audio_prompts/server_connect_success.opus"
|
||||
#define CONTROL_PLANE_CLIENT_LIFECYCLE_WAIT_MS 30000U
|
||||
#define CONTROL_PLANE_SERVER_SUCCESS_PROMPT_RETRY_MS 1000U
|
||||
#define CONTROL_PLANE_SERVER_SUCCESS_PROMPT_MAX_ATTEMPTS 3U
|
||||
|
||||
#ifndef CONFIG_TQ_NETWORK_TASK_CORE
|
||||
#define CONFIG_TQ_NETWORK_TASK_CORE 0
|
||||
@@ -57,6 +61,10 @@ static bool s_multimodal_watchdog_stop_requested;
|
||||
static bool s_multimodal_watchdog_task_starting;
|
||||
static bool s_model_command_dispatched;
|
||||
static bool s_server_success_prompt_played;
|
||||
static bool s_server_success_prompt_pending;
|
||||
static bool s_server_success_prompt_in_progress;
|
||||
static uint8_t s_server_success_prompt_attempts;
|
||||
static uint32_t s_server_success_prompt_next_attempt_ms;
|
||||
static bool s_cloud_chouqian_pending;
|
||||
static bool s_downstream_stall_recovery_pending;
|
||||
static bool s_downstream_stall_stop_sent;
|
||||
@@ -64,6 +72,45 @@ static uint32_t s_downstream_stall_recovery_started_ms;
|
||||
static control_plane_model_command_callback_t s_model_command_cb;
|
||||
static void *s_model_command_cb_user_data;
|
||||
static portMUX_TYPE s_multimodal_runtime_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static SemaphoreHandle_t s_client_lifecycle_mutex;
|
||||
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
static StaticSemaphore_t s_client_lifecycle_mutex_buf;
|
||||
#endif
|
||||
static portMUX_TYPE s_client_lifecycle_init_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
static bool lock_client_lifecycle(void)
|
||||
{
|
||||
portENTER_CRITICAL(&s_client_lifecycle_init_lock);
|
||||
if (s_client_lifecycle_mutex == NULL) {
|
||||
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
s_client_lifecycle_mutex =
|
||||
xSemaphoreCreateMutexStatic(&s_client_lifecycle_mutex_buf);
|
||||
#else
|
||||
s_client_lifecycle_mutex = xSemaphoreCreateMutex();
|
||||
#endif
|
||||
}
|
||||
SemaphoreHandle_t mutex = s_client_lifecycle_mutex;
|
||||
portEXIT_CRITICAL(&s_client_lifecycle_init_lock);
|
||||
if (mutex == NULL) {
|
||||
return false;
|
||||
}
|
||||
TickType_t timeout_ticks = pdMS_TO_TICKS(CONTROL_PLANE_CLIENT_LIFECYCLE_WAIT_MS);
|
||||
if (timeout_ticks == 0) {
|
||||
timeout_ticks = 1;
|
||||
}
|
||||
if (xSemaphoreTake(mutex, timeout_ticks) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Timeout acquiring multimodal client lifecycle lock");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void unlock_client_lifecycle(void)
|
||||
{
|
||||
if (s_client_lifecycle_mutex != NULL) {
|
||||
(void)xSemaphoreGive(s_client_lifecycle_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static TaskHandle_t get_multimodal_task_handle(TaskHandle_t *slot)
|
||||
{
|
||||
@@ -171,6 +218,74 @@ static void control_plane_clear_downstream_stall_recovery(void)
|
||||
portEXIT_CRITICAL(&s_multimodal_runtime_lock);
|
||||
}
|
||||
|
||||
static void control_plane_schedule_server_success_prompt(void)
|
||||
{
|
||||
uint32_t now_ms = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
|
||||
portENTER_CRITICAL(&s_multimodal_runtime_lock);
|
||||
if (!s_server_success_prompt_played &&
|
||||
s_server_success_prompt_attempts <
|
||||
CONTROL_PLANE_SERVER_SUCCESS_PROMPT_MAX_ATTEMPTS) {
|
||||
s_server_success_prompt_pending = true;
|
||||
s_server_success_prompt_next_attempt_ms = now_ms;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_multimodal_runtime_lock);
|
||||
}
|
||||
|
||||
static void control_plane_try_server_success_prompt(
|
||||
const control_plane_multimodal_runtime_state_t *state,
|
||||
uint32_t now_ms)
|
||||
{
|
||||
if (state == NULL || !state->connected || state->has_active_task) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool should_attempt = false;
|
||||
uint8_t attempt = 0U;
|
||||
portENTER_CRITICAL(&s_multimodal_runtime_lock);
|
||||
if (s_server_success_prompt_pending &&
|
||||
!s_server_success_prompt_played &&
|
||||
!s_server_success_prompt_in_progress &&
|
||||
s_server_success_prompt_attempts <
|
||||
CONTROL_PLANE_SERVER_SUCCESS_PROMPT_MAX_ATTEMPTS &&
|
||||
(int32_t)(now_ms - s_server_success_prompt_next_attempt_ms) >= 0) {
|
||||
s_server_success_prompt_in_progress = true;
|
||||
s_server_success_prompt_attempts++;
|
||||
attempt = s_server_success_prompt_attempts;
|
||||
should_attempt = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_multimodal_runtime_lock);
|
||||
if (!should_attempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = domain_runtime_behavior_play_prompt_file(
|
||||
CONTROL_PLANE_SERVER_SUCCESS_PROMPT_FILE);
|
||||
|
||||
portENTER_CRITICAL(&s_multimodal_runtime_lock);
|
||||
s_server_success_prompt_in_progress = false;
|
||||
if (err == ESP_OK) {
|
||||
s_server_success_prompt_played = true;
|
||||
s_server_success_prompt_pending = false;
|
||||
} else if (s_server_success_prompt_attempts >=
|
||||
CONTROL_PLANE_SERVER_SUCCESS_PROMPT_MAX_ATTEMPTS) {
|
||||
s_server_success_prompt_pending = false;
|
||||
} else {
|
||||
s_server_success_prompt_next_attempt_ms =
|
||||
now_ms + CONTROL_PLANE_SERVER_SUCCESS_PROMPT_RETRY_MS;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_multimodal_runtime_lock);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Server success prompt played on attempt %u", (unsigned)attempt);
|
||||
} else {
|
||||
ESP_LOGW(TAG,
|
||||
"Server success prompt attempt %u/%u failed: %s",
|
||||
(unsigned)attempt,
|
||||
(unsigned)CONTROL_PLANE_SERVER_SUCCESS_PROMPT_MAX_ATTEMPTS,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
static bool try_begin_multimodal_task_start(TaskHandle_t *slot, bool *start_flag)
|
||||
{
|
||||
bool can_start = false;
|
||||
@@ -507,6 +622,7 @@ static void multimodal_watchdog_task(void *arg)
|
||||
}
|
||||
|
||||
uint32_t now_ms = control_plane_now_ms();
|
||||
control_plane_try_server_success_prompt(&state, now_ms);
|
||||
bool stall_stop_sent = false;
|
||||
uint32_t stall_recovery_started_ms = 0U;
|
||||
if (control_plane_get_downstream_stall_recovery(&stall_stop_sent,
|
||||
@@ -559,7 +675,7 @@ static void multimodal_watchdog_task(void *arg)
|
||||
"Idle multimodal transport timeout, stopping client after %u ms",
|
||||
(unsigned)CONTROL_PLANE_IDLE_TRANSPORT_SHUTDOWN_MS);
|
||||
control_plane_multimodal_stop_uplink();
|
||||
control_plane_multimodal_stop();
|
||||
(void)control_plane_multimodal_stop();
|
||||
idle_transport_started_ms = 0U;
|
||||
}
|
||||
continue;
|
||||
@@ -627,16 +743,7 @@ static void control_plane_multimodal_event_bridge(control_plane_multimodal_event
|
||||
|
||||
switch (event) {
|
||||
case CONTROL_PLANE_MULTIMODAL_EVENT_TRANSPORT_CONNECTED: {
|
||||
bool should_play_prompt = false;
|
||||
portENTER_CRITICAL(&s_multimodal_runtime_lock);
|
||||
if (!s_server_success_prompt_played) {
|
||||
s_server_success_prompt_played = true;
|
||||
should_play_prompt = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_multimodal_runtime_lock);
|
||||
if (should_play_prompt) {
|
||||
(void)domain_runtime_behavior_play_prompt_file(CONTROL_PLANE_SERVER_SUCCESS_PROMPT_FILE);
|
||||
}
|
||||
control_plane_schedule_server_success_prompt();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -718,7 +825,7 @@ static void control_plane_multimodal_event_bridge(control_plane_multimodal_event
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t control_plane_start_multimodal_client(void)
|
||||
static esp_err_t control_plane_start_multimodal_client_locked(void)
|
||||
{
|
||||
control_plane_clear_downstream_stall_recovery();
|
||||
|
||||
@@ -768,18 +875,35 @@ esp_err_t control_plane_start_multimodal_client(void)
|
||||
esp_err_t err = control_plane_multimodal_start();
|
||||
if (err != ESP_OK) {
|
||||
stop_watchdog_task();
|
||||
(void)control_plane_multimodal_register_downstream_audio_callback(NULL, NULL);
|
||||
(void)control_plane_multimodal_register_event_callback(NULL, NULL);
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void control_plane_stop_multimodal_client(void)
|
||||
esp_err_t control_plane_start_multimodal_client(void)
|
||||
{
|
||||
if (!lock_client_lifecycle()) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
esp_err_t err = control_plane_start_multimodal_client_locked();
|
||||
unlock_client_lifecycle();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t control_plane_stop_multimodal_client(void)
|
||||
{
|
||||
if (!lock_client_lifecycle()) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
control_plane_clear_downstream_stall_recovery();
|
||||
control_plane_clear_cloud_chouqian_pending("multimodal client stopped");
|
||||
stop_watchdog_task();
|
||||
control_plane_multimodal_stop();
|
||||
esp_err_t err = control_plane_multimodal_stop();
|
||||
unlock_client_lifecycle();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t control_plane_register_model_command_callback(
|
||||
|
||||
Reference in New Issue
Block a user