修复了WS连接稳定性问题

This commit is contained in:
CSBOOK\44744
2026-06-27 20:29:09 +08:00
parent b829cd8ebc
commit caa4a7eded
16 changed files with 2197 additions and 248 deletions
@@ -66,6 +66,8 @@ typedef void (*control_plane_multimodal_downstream_audio_callback_t)(const uint8
typedef struct {
bool connected;
bool starting;
bool stopping;
bool has_active_task;
bool server_listening;
bool remote_responding;
@@ -86,6 +88,7 @@ typedef enum {
CONTROL_PLANE_TRANSPORT_LOSS_SEND_BINARY_FAILURE,
CONTROL_PLANE_TRANSPORT_LOSS_LISTENING_TIMEOUT,
CONTROL_PLANE_TRANSPORT_LOSS_NO_RESPONSE_TIMEOUT,
CONTROL_PLANE_TRANSPORT_LOSS_PROTOCOL_ERROR,
} control_plane_transport_loss_reason_t;
esp_err_t control_plane_multimodal_start(void);
@@ -119,6 +119,8 @@
static const char *TAG = "cp_mm_session";
static bool s_started;
static bool s_starting;
static bool s_stopping;
static control_plane_multimodal_auth_info_t s_auth;
static char s_task_id[40];
@@ -167,6 +169,7 @@ static StaticSemaphore_t s_state_mutex_buf;
typedef enum {
LOCAL_RESPONDING_EVENT_STARTED = 0,
LOCAL_RESPONDING_EVENT_ENDED,
LOCAL_RESPONDING_EVENT_QUIT,
} local_responding_event_type_t;
typedef struct {
@@ -418,6 +421,8 @@ static const char *transport_loss_reason_name(control_plane_transport_loss_reaso
return "listening_timeout";
case CONTROL_PLANE_TRANSPORT_LOSS_NO_RESPONSE_TIMEOUT:
return "no_response_timeout";
case CONTROL_PLANE_TRANSPORT_LOSS_PROTOCOL_ERROR:
return "protocol_error";
default:
return "unknown";
}
@@ -444,6 +449,8 @@ void control_plane_multimodal_mark_transport_unavailable(
if (lock_state()) {
s_started = false;
s_starting = false;
s_stopping = false;
s_transport_available = false;
memset(&s_auth, 0, sizeof(s_auth));
reset_session_state_locked();
@@ -451,6 +458,19 @@ void control_plane_multimodal_mark_transport_unavailable(
}
control_plane_multimodal_stop_uplink();
bool close_transport = reason == CONTROL_PLANE_TRANSPORT_LOSS_PROTOCOL_ERROR ||
reason == CONTROL_PLANE_TRANSPORT_LOSS_SEND_TEXT_FAILURE ||
reason == CONTROL_PLANE_TRANSPORT_LOSS_LISTENING_TIMEOUT ||
reason == CONTROL_PLANE_TRANSPORT_LOSS_NO_RESPONSE_TIMEOUT;
if (close_transport) {
esp_err_t stop_err = domain_runtime_multimodal_transport_stop(0);
if (stop_err != ESP_OK) {
ESP_LOGW(TAG,
"Async transport stop after unavailable ended with %s",
esp_err_to_name(stop_err));
}
}
}
static void reset_session_state(void)
@@ -523,7 +543,19 @@ static void local_responding_event_worker(void *arg)
idle_ticks = 1;
}
if (xQueueReceive(s_local_responding_event_queue, &msg, idle_ticks) != pdTRUE) {
s_local_responding_event_task = NULL;
if (lock_state()) {
s_local_responding_event_task = NULL;
unlock_state();
}
vTaskDelete(NULL);
return;
}
if (msg.type == LOCAL_RESPONDING_EVENT_QUIT) {
if (lock_state()) {
s_local_responding_event_task = NULL;
unlock_state();
}
vTaskDelete(NULL);
return;
}
@@ -594,6 +626,50 @@ static esp_err_t ensure_local_responding_event_worker(void)
return ESP_OK;
}
static void stop_local_responding_event_worker(void)
{
QueueHandle_t queue = NULL;
TaskHandle_t task = NULL;
if (lock_state()) {
queue = s_local_responding_event_queue;
task = s_local_responding_event_task;
s_local_responding_started_send_pending = false;
s_local_responding_ended_send_pending = false;
unlock_state();
}
if (queue != NULL && task != NULL) {
local_responding_event_msg_t quit = {
.type = LOCAL_RESPONDING_EVENT_QUIT,
};
(void)xQueueSend(queue, &quit, 0);
TickType_t start_tick = xTaskGetTickCount();
TickType_t wait_ticks = pdMS_TO_TICKS(300);
if (wait_ticks == 0) {
wait_ticks = 1;
}
while ((xTaskGetTickCount() - start_tick) < wait_ticks) {
bool running = false;
if (lock_state()) {
running = s_local_responding_event_task != NULL;
unlock_state();
}
if (!running) {
break;
}
vTaskDelay(pdMS_TO_TICKS(20));
}
}
if (lock_state()) {
s_local_responding_event_task = NULL;
if (s_local_responding_event_queue != NULL) {
xQueueReset(s_local_responding_event_queue);
}
unlock_state();
}
}
static void queue_local_responding_event(local_responding_event_type_t type)
{
char task_id[sizeof(s_task_id)] = {0};
@@ -1198,7 +1274,12 @@ static void process_text_json(const char *json_text)
request_id,
dialog_id[0] ? dialog_id : "-");
if (cJSON_IsString(failed_task_id) &&
bool protocol_error = strcmp(error_code, "InvalidParameter") == 0 ||
strcmp(error_name, "InvalidParameter") == 0 ||
strstr(error_message, "Invalid action") != NULL;
if (!protocol_error &&
cJSON_IsString(failed_task_id) &&
failed_task_id->valuestring != NULL &&
failed_task_id->valuestring[0] != '\0') {
bool matches_active_task = false;
@@ -1218,7 +1299,13 @@ static void process_text_json(const char *json_text)
}
stop_local_downstream_playback();
reset_session_state_if_task_matches(task_id);
if (protocol_error) {
control_plane_multimodal_mark_transport_unavailable(
CONTROL_PLANE_TRANSPORT_LOSS_PROTOCOL_ERROR,
__func__);
} else {
reset_session_state_if_task_matches(task_id);
}
notify_event(CONTROL_PLANE_MULTIMODAL_EVENT_TASK_FAILED);
} else if (strcmp(event->valuestring, "task-started") == 0) {
notify_event(CONTROL_PLANE_MULTIMODAL_EVENT_TASK_STARTED);
@@ -1378,8 +1465,10 @@ static void process_text_json(const char *json_text)
cJSON *error_message = cJSON_GetObjectItemCaseSensitive(output, "error_message");
cJSON *llm_request_id = cJSON_GetObjectItemCaseSensitive(output, "llm_request_id");
char dialog_id[sizeof(s_dialog_id)] = {0};
char task_id[sizeof(s_task_id)] = {0};
if (lock_state()) {
s_remote_responding = false;
strlcpy(task_id, s_task_id, sizeof(task_id));
strlcpy(dialog_id, s_dialog_id, sizeof(dialog_id));
unlock_state();
}
@@ -1390,6 +1479,11 @@ static void process_text_json(const char *json_text)
cJSON_IsString(error_message) && error_message->valuestring ? error_message->valuestring : "unknown",
cJSON_IsString(llm_request_id) && llm_request_id->valuestring ? llm_request_id->valuestring : "-",
dialog_id[0] ? dialog_id : "-");
control_plane_multimodal_stop_uplink();
stop_local_downstream_playback();
control_plane_multimodal_mark_transport_unavailable(
CONTROL_PLANE_TRANSPORT_LOSS_PROTOCOL_ERROR,
__func__);
notify_event(CONTROL_PLANE_MULTIMODAL_EVENT_OUTPUT_ERROR);
}
}
@@ -1409,6 +1503,9 @@ static void transport_event_bridge(const domain_multimodal_transport_event_t *ev
switch (event->type) {
case DOMAIN_MULTIMODAL_TRANSPORT_EVENT_CONNECTED:
if (lock_state()) {
s_started = true;
s_starting = false;
s_stopping = false;
s_transport_available = true;
s_last_heartbeat_tick = xTaskGetTickCount();
s_last_downstream_tick = s_last_heartbeat_tick;
@@ -1538,17 +1635,29 @@ esp_err_t control_plane_multimodal_start(void)
#endif
if (lock_state()) {
bool already_started = s_started;
unlock_state();
bool already_started = s_started || s_auth.token[0] != '\0';
bool starting = s_starting;
bool stopping = s_stopping;
if (already_started) {
unlock_state();
return ESP_OK;
}
if (starting || stopping) {
unlock_state();
return ESP_ERR_INVALID_STATE;
}
s_starting = true;
unlock_state();
}
control_plane_multimodal_auth_info_t auth = {0};
esp_err_t err = control_plane_multimodal_auth_fetch(&auth);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Auth fetch failed: %s", esp_err_to_name(err));
if (lock_state()) {
s_starting = false;
unlock_state();
}
return err;
}
@@ -1557,6 +1666,10 @@ esp_err_t control_plane_multimodal_start(void)
NULL);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Register downstream playback callback failed: %s", esp_err_to_name(err));
if (lock_state()) {
s_starting = false;
unlock_state();
}
return err;
}
@@ -1564,12 +1677,15 @@ esp_err_t control_plane_multimodal_start(void)
if (err != ESP_OK) {
ESP_LOGW(TAG, "Transport start failed: %s", esp_err_to_name(err));
(void)domain_runtime_multimodal_audio_register_downstream_playback_callback(NULL, NULL);
if (lock_state()) {
s_starting = false;
unlock_state();
}
return err;
}
if (lock_state()) {
s_auth = auth;
s_started = true;
unlock_state();
}
return ESP_OK;
@@ -1577,7 +1693,22 @@ esp_err_t control_plane_multimodal_start(void)
void control_plane_multimodal_stop(void)
{
bool already_stopping = false;
if (lock_state()) {
already_stopping = s_stopping;
if (!s_stopping) {
s_stopping = true;
}
s_starting = false;
unlock_state();
}
if (already_stopping) {
ESP_LOGI(TAG, "Multimodal stop already in progress");
return;
}
control_plane_multimodal_stop_uplink();
stop_local_responding_event_worker();
(void)control_plane_multimodal_register_downstream_audio_callback(NULL, NULL);
(void)control_plane_multimodal_register_event_callback(NULL, NULL);
(void)domain_runtime_multimodal_audio_register_downstream_playback_callback(NULL, NULL);
@@ -1589,6 +1720,9 @@ void control_plane_multimodal_stop(void)
if (lock_state()) {
s_started = false;
s_starting = false;
s_stopping = false;
s_transport_available = false;
memset(&s_auth, 0, sizeof(s_auth));
reset_session_state_locked();
unlock_state();
@@ -2123,6 +2257,8 @@ esp_err_t control_plane_multimodal_get_runtime_state(control_plane_multimodal_ru
return ESP_ERR_TIMEOUT;
}
out_state->connected = transport_available_locked();
out_state->starting = s_starting;
out_state->stopping = s_stopping;
out_state->has_active_task = s_task_id[0] != '\0';
out_state->server_listening = s_server_listening;
out_state->remote_responding = s_remote_responding;
@@ -2227,6 +2363,9 @@ esp_err_t control_plane_multimodal_start_uplink(void)
void control_plane_multimodal_stop_uplink(void)
{
if (!domain_runtime_multimodal_audio_is_uplink_running()) {
return;
}
esp_err_t err = domain_runtime_multimodal_audio_stop_uplink(CONTROL_PLANE_UPLINK_READY_WAIT_MS);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Mic uplink stop ended with %s", esp_err_to_name(err));
@@ -48,6 +48,10 @@
#define CONFIG_TQ_KEY_INPUT_POLL_MS 20
#endif
#ifndef CONTROL_PLANE_PTT_INITIAL_INACTIVE_STABLE_MS
#define CONTROL_PLANE_PTT_INITIAL_INACTIVE_STABLE_MS 120
#endif
#ifndef CONFIG_TQ_LOCAL_TASK_CORE
#define CONFIG_TQ_LOCAL_TASK_CORE 1
#endif
@@ -98,6 +102,7 @@ typedef struct {
bool long_press_sent;
bool click_pending;
int64_t debounce_deadline_ms;
int64_t inactive_since_ms;
int64_t press_start_ms;
int64_t click_deadline_ms;
} control_plane_key_state_t;
@@ -113,9 +118,9 @@ static const control_plane_key_config_t s_key_configs[] = {
.long_press_ms = CONFIG_TQ_KEY_LONG_PRESS_MS,
.double_click_ms = CONFIG_TQ_KEY_DOUBLE_CLICK_MS,
.emit_press_release = true,
.enable_short_press = true,
.enable_short_press = false,
.enable_long_press = false,
.enable_double_click = true,
.enable_double_click = false,
},
{
.key_id = CONTROL_PLANE_KEY_ID_SW2,
@@ -256,9 +261,14 @@ static void control_plane_handle_debounced_transition(const control_plane_key_co
if (state->wait_inactive_before_press) {
if (pressed) {
state->debounced_pressed = false;
state->inactive_since_ms = 0;
return;
}
state->wait_inactive_before_press = false;
state->inactive_since_ms = 0;
ESP_LOGI(TAG,
"key=%s initial active gate cleared by debounced inactive level",
control_plane_key_id_name(config->key_id));
}
if (pressed == state->debounced_pressed) {
@@ -334,6 +344,33 @@ static void control_plane_process_key_timers(void)
continue;
}
if (state->wait_inactive_before_press) {
const int level = gpio_get_level(config->gpio_num);
const bool pressed = control_plane_key_level_is_pressed(config, level);
if (!pressed) {
if (state->inactive_since_ms == 0) {
state->inactive_since_ms = now_ms;
}
uint32_t stable_ms = config->debounce_ms;
if (stable_ms < CONTROL_PLANE_PTT_INITIAL_INACTIVE_STABLE_MS) {
stable_ms = CONTROL_PLANE_PTT_INITIAL_INACTIVE_STABLE_MS;
}
if ((now_ms - state->inactive_since_ms) >= stable_ms) {
state->wait_inactive_before_press = false;
state->debounced_pressed = false;
state->debounce_pending = false;
state->press_start_ms = 0;
state->long_press_sent = false;
ESP_LOGI(TAG,
"key=%s initial active gate cleared by stable inactive poll (%" PRId64 "ms)",
control_plane_key_id_name(config->key_id),
now_ms - state->inactive_since_ms);
}
} else {
state->inactive_since_ms = 0;
}
}
if (state->debounce_pending && now_ms >= state->debounce_deadline_ms) {
state->debounce_pending = false;
const int level = gpio_get_level(config->gpio_num);
@@ -390,6 +427,7 @@ static void control_plane_key_input_task(void *arg)
"PTT initial level is active; wait for next stable press before emitting press event");
s_key_states[i].debounced_pressed = false;
s_key_states[i].wait_inactive_before_press = true;
s_key_states[i].inactive_since_ms = 0;
}
setup_err = control_plane_configure_key_gpio(config);
@@ -21,7 +21,6 @@
#include "sdkconfig.h"
#define CONTROL_PLANE_MODEL_CMD_TASK_STACK_SIZE (1024 * 4)
#define CONTROL_PLANE_DIRECTION_TASK_STACK_SIZE (1024 * 4)
#define CONTROL_PLANE_LCD_RENDER_LOOP_TASK_STACK_SIZE (1024 * 12)
#define CONTROL_PLANE_WIFI_STATE_TASK_STACK_SIZE (1024 * 12)
#define CONTROL_PLANE_TASK_MONITOR_TASK_STACK_SIZE (1024 * 6)
@@ -29,7 +28,6 @@
#define CONTROL_PLANE_MODEL_CMD_RECV_TIMEOUT_MS 200
#define CONTROL_PLANE_TASK_STOP_WAIT_MS 1500
#define CONTROL_PLANE_TASK_START_GATE_WAIT_MS 5000
#define CONTROL_PLANE_INPUT_BUF_SIZE 64
#define CONTROL_PLANE_WIFI_STATE_POLL_INTERVAL_MS 1000
#define CONTROL_PLANE_PTT_ACTIVE_POLL_INTERVAL_MS 80
#define CONTROL_PLANE_WIFI_DISCONNECTED_REFRESH_MS 5000
@@ -71,10 +69,6 @@
#define CONFIG_TQ_TASK_MONITOR_ENABLE 0
#endif
#ifndef CONFIG_TQ_DIRECTION_CONSOLE_ENABLE
#define CONFIG_TQ_DIRECTION_CONSOLE_ENABLE 0
#endif
#ifndef CONFIG_TQ_TASK_MONITOR_INTERVAL_MS
#define CONFIG_TQ_TASK_MONITOR_INTERVAL_MS 10000
#endif
@@ -157,6 +151,8 @@ typedef struct {
bool foreground_connect;
uint8_t foreground_attempts;
bool pending_press_after_teardown;
bool start_action_sent;
bool speech_action_sent;
} ptt_runtime_t;
typedef struct {
@@ -170,9 +166,6 @@ static TaskHandle_t s_model_cmd_task;
static bool s_ws_start_requested;
static bool s_ptt_active;
static ptt_runtime_t s_ptt_runtime;
#if CONFIG_TQ_DIRECTION_CONSOLE_ENABLE
static TaskHandle_t s_direction_console_task;
#endif
static TaskHandle_t s_lcd_render_loop_task;
static TaskHandle_t s_wifi_state_task;
#if CONFIG_TQ_TASK_MONITOR_ENABLE
@@ -240,10 +233,20 @@ static void control_plane_ptt_transition_locked(ptt_state_t next_state, const ch
(void)reason;
s_ptt_runtime.state = next_state;
s_ptt_runtime.state_enter_tick = xTaskGetTickCount();
if (next_state == PTT_STATE_WAIT_START_SENT) {
s_ptt_runtime.start_action_sent = false;
}
if (next_state == PTT_STATE_WAIT_SPEECH_STARTED) {
s_ptt_runtime.speech_action_sent = false;
}
if (next_state == PTT_STATE_IDLE ||
next_state == PTT_STATE_STREAMING ||
next_state == PTT_STATE_DENIED) {
s_ptt_runtime.foreground_connect = false;
if (next_state == PTT_STATE_IDLE || next_state == PTT_STATE_DENIED) {
s_ptt_runtime.start_action_sent = false;
s_ptt_runtime.speech_action_sent = false;
}
}
}
@@ -261,6 +264,20 @@ static void control_plane_ptt_log_transition(ptt_state_t old_state,
ptt_state_name(next_state),
(unsigned)generation,
reason != NULL ? reason : "-");
control_plane_multimodal_runtime_state_t mm_state = {0};
esp_err_t state_err = control_plane_multimodal_get_runtime_state(&mm_state);
ESP_LOGI(TAG,
"PTT snapshot generation=%u wifi=%d ws(conn=%d starting=%d stopping=%d) task=%d listening=%d speech=%d responding=%d uplink=%d",
(unsigned)generation,
control_plane_is_sta_connected() ? 1 : 0,
(state_err == ESP_OK && mm_state.connected) ? 1 : 0,
(state_err == ESP_OK && mm_state.starting) ? 1 : 0,
(state_err == ESP_OK && mm_state.stopping) ? 1 : 0,
(state_err == ESP_OK && mm_state.has_active_task) ? 1 : 0,
(state_err == ESP_OK && mm_state.server_listening) ? 1 : 0,
(state_err == ESP_OK && mm_state.client_speech_active) ? 1 : 0,
(state_err == ESP_OK && mm_state.remote_responding) ? 1 : 0,
control_plane_multimodal_is_uplink_running() ? 1 : 0);
}
static void control_plane_notify_wifi_state_task(void)
@@ -278,6 +295,7 @@ void control_plane_set_ptt_active(bool active)
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();
portENTER_CRITICAL(&s_runtime_lock);
old_state = s_ptt_runtime.state;
s_ptt_active = active;
@@ -288,6 +306,8 @@ void control_plane_set_ptt_active(bool active)
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;
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;
@@ -302,7 +322,10 @@ void control_plane_set_ptt_active(bool active)
s_ptt_runtime.foreground_attempts = 0;
s_ptt_runtime.pending_press_after_teardown = false;
s_ptt_runtime.generation++;
if (s_ptt_runtime.state == PTT_STATE_WAIT_WIFI ||
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 ||
@@ -310,9 +333,6 @@ void control_plane_set_ptt_active(bool active)
s_ptt_runtime.state == PTT_STATE_DENIED) {
control_plane_ptt_transition_locked(PTT_STATE_IDLE, "release_before_streaming");
transition_reason = "release_before_streaming";
} else if (s_ptt_runtime.state == PTT_STATE_STREAMING) {
control_plane_ptt_transition_locked(PTT_STATE_TEARDOWN_LOCAL, "release_streaming");
transition_reason = "release_streaming";
}
notify_worker = true;
}
@@ -631,90 +651,6 @@ static void control_plane_task_start_gate(void *arg)
control_plane_delete_task(NULL, task_with_caps);
}
#if CONFIG_TQ_DIRECTION_CONSOLE_ENABLE
static void control_plane_print_help(void)
{
printf("\n========================================\n");
printf(" Direction Control Console\n");
printf("========================================\n");
printf("Available directions:\n");
size_t count = domain_get_direction_count();
for (size_t i = 0; i < count; i++) {
const char *name_cn = NULL;
const char *name_en = NULL;
float angle = 0.0f;
if (!domain_get_direction_view(i, &name_cn, &name_en, &angle)) {
continue;
}
if (i == 0) {
printf(" %s/%s - %.1f deg [default]\n", name_cn, name_en, angle);
} else {
printf(" %s/%s - %.1f deg\n", name_cn, name_en, angle);
}
}
printf("----------------------------------------\n");
printf("Commands:\n");
printf(" help - show help\n");
printf(" pos - show current position\n");
printf(" cal - run calibration (if configured)\n");
printf("========================================\n\n");
}
static void control_plane_print_current_position(void)
{
float angle = domain_get_current_angle();
size_t index = 0;
if (domain_get_current_direction_index(&index)) {
const char *name_cn = NULL;
const char *name_en = NULL;
float direction_angle = 0.0f;
if (domain_get_direction_view(index, &name_cn, &name_en, &direction_angle)) {
printf("Current position: %s/%s (%.1f deg)\n", name_cn, name_en, angle);
return;
}
}
printf("Current position: %.1f deg (not a named direction)\n", angle);
}
static void control_plane_process_direction_command(char *input)
{
char *cmd = input;
while (*cmd == ' ') {
cmd++;
}
if (*cmd != '\0') {
char *end = cmd + strlen(cmd) - 1;
while (end > cmd && *end == ' ') {
*end-- = '\0';
}
}
if (*cmd == '\0') {
return;
}
control_plane_direction_command_t parsed = CONTROL_PLANE_DIRECTION_CMD_UNKNOWN;
esp_err_t err = control_plane_execute_direction_command(cmd, &parsed, NULL);
if (parsed == CONTROL_PLANE_DIRECTION_CMD_HELP) {
control_plane_print_help();
} else if (parsed == CONTROL_PLANE_DIRECTION_CMD_POSITION) {
control_plane_print_current_position();
} else if (parsed == CONTROL_PLANE_DIRECTION_CMD_CALIBRATE) {
if (err != ESP_OK) {
printf("Calibration failed, err=0x%x\n", err);
}
} else if (parsed == CONTROL_PLANE_DIRECTION_CMD_DIRECTION) {
if (err != ESP_OK) {
printf("Rotation failed, err=0x%x\n", err);
}
} else {
printf("Unknown command or direction: '%s'\n", cmd);
printf("Type 'help' to list available directions\n");
}
}
#endif
static void control_plane_on_model_command(control_plane_model_command_type_t command_type,
const char *value,
void *user_data)
@@ -962,74 +898,6 @@ static void control_plane_task_monitor_task(void *arg)
}
#endif
#if CONFIG_TQ_DIRECTION_CONSOLE_ENABLE
static void control_plane_direction_console_task(void *arg)
{
(void)arg;
ESP_LOGI(TAG, "Direction console task started");
control_plane_print_help();
char input_buf[CONTROL_PLANE_INPUT_BUF_SIZE] = {0};
size_t input_len = 0;
bool prompt_printed = false;
bool ignore_lf_after_cr = false;
while (!control_plane_runtime_stop_requested()) {
if (!prompt_printed) {
printf("Enter direction > ");
fflush(stdout);
prompt_printed = true;
}
int ch = fgetc(stdin);
if (ch == EOF) {
clearerr(stdin);
vTaskDelay(pdMS_TO_TICKS(20));
continue;
}
if (ch == '\n' && ignore_lf_after_cr) {
ignore_lf_after_cr = false;
continue;
}
if (ch == '\r' || ch == '\n') {
ignore_lf_after_cr = (ch == '\r');
input_buf[input_len] = '\0';
printf("\n");
fflush(stdout);
prompt_printed = false;
control_plane_process_direction_command(input_buf);
input_len = 0;
continue;
}
ignore_lf_after_cr = false;
if (ch == '\b' || ch == 0x7F) {
if (input_len > 0) {
input_len--;
printf("\b \b");
fflush(stdout);
}
continue;
}
if (ch >= 0x20 && ch <= 0x7E) {
if (input_len < CONTROL_PLANE_INPUT_BUF_SIZE - 1) {
input_buf[input_len++] = (char)ch;
putchar(ch);
fflush(stdout);
}
}
}
control_plane_set_task_handle(&s_direction_console_task, NULL);
return;
}
#endif
#if CONFIG_TQ_SCREEN_ENABLE
static void control_plane_lcd_render_loop_task(void *arg)
{
@@ -1130,6 +998,16 @@ static esp_err_t control_plane_ensure_user_config_server(void)
static bool control_plane_try_start_multimodal_from_state_machine(TickType_t *last_attempt_tick,
bool foreground)
{
control_plane_multimodal_runtime_state_t state = {0};
if (control_plane_multimodal_get_runtime_state(&state) == ESP_OK) {
if (state.connected) {
return true;
}
if (state.starting || state.stopping) {
return false;
}
}
TickType_t now = xTaskGetTickCount();
TickType_t retry_ticks = pdMS_TO_TICKS(CONTROL_PLANE_WS_START_RETRY_DELAY_MS);
if (retry_ticks == 0) {
@@ -1150,11 +1028,21 @@ static bool control_plane_try_start_multimodal_from_state_machine(TickType_t *la
ESP_LOGI(TAG, "Multimodal client started");
return true;
}
if (err == ESP_ERR_INVALID_STATE) {
return false;
}
ESP_LOGW(TAG, "Multimodal client start failed: %s", esp_err_to_name(err));
return false;
}
static bool control_plane_multimodal_start_busy(void)
{
control_plane_multimodal_runtime_state_t state = {0};
return control_plane_multimodal_get_runtime_state(&state) == ESP_OK &&
(state.starting || state.stopping);
}
static void control_plane_ptt_set_state(ptt_state_t next_state, const char *reason)
{
ptt_state_t old_state = PTT_STATE_IDLE;
@@ -1214,10 +1102,11 @@ static void control_plane_handle_ptt_teardown(void)
control_plane_ptt_transition_locked(PTT_STATE_IDLE, "teardown_complete");
if (pending_press && active) {
s_ws_start_requested = true;
s_ptt_runtime.generation++;
s_ptt_runtime.foreground_connect = true;
s_ptt_runtime.foreground_attempts = 0;
s_ptt_runtime.press_tick = xTaskGetTickCount();
s_ptt_runtime.start_action_sent = false;
s_ptt_runtime.speech_action_sent = false;
control_plane_ptt_transition_locked(PTT_STATE_WAIT_WIFI, "pending_press");
}
generation = s_ptt_runtime.generation;
@@ -1270,6 +1159,10 @@ static void control_plane_drive_ptt_state_machine(TickType_t *last_ws_start_atte
}
if (!control_plane_multimodal_is_connected()) {
if (ptt.state == PTT_STATE_STREAMING || control_plane_multimodal_is_uplink_running()) {
control_plane_ptt_set_state(PTT_STATE_TEARDOWN_LOCAL, "ws_unavailable_abort_uplink");
return;
}
control_plane_ptt_set_state(PTT_STATE_WAIT_WS, "ws_unavailable");
bool try_foreground = ptt.foreground_connect &&
ptt.foreground_attempts <
@@ -1278,10 +1171,14 @@ static void control_plane_drive_ptt_state_machine(TickType_t *last_ws_start_atte
bool started = control_plane_try_start_multimodal_from_state_machine(
last_ws_start_attempt_tick,
true);
portENTER_CRITICAL(&s_runtime_lock);
s_ptt_runtime.foreground_attempts++;
portEXIT_CRITICAL(&s_runtime_lock);
bool busy = control_plane_multimodal_start_busy();
if (!busy) {
portENTER_CRITICAL(&s_runtime_lock);
s_ptt_runtime.foreground_attempts++;
portEXIT_CRITICAL(&s_runtime_lock);
}
if (!started &&
!busy &&
ptt.foreground_attempts + 1U >=
CONTROL_PLANE_PTT_FOREGROUND_RECONNECT_MAX_ATTEMPTS) {
control_plane_ptt_set_state(PTT_STATE_DENIED, "ws_foreground_attempts_exhausted");
@@ -1333,8 +1230,27 @@ static void control_plane_drive_ptt_state_machine(TickType_t *last_ws_start_atte
return;
}
ptt = control_plane_ptt_snapshot();
bool should_run_press_step = true;
if (ptt.state == PTT_STATE_WAIT_START_SENT && ptt.start_action_sent) {
should_run_press_step = false;
}
if (ptt.state == PTT_STATE_WAIT_SPEECH_STARTED && ptt.speech_action_sent) {
should_run_press_step = false;
}
if (!should_run_press_step) {
return;
}
esp_err_t press_err = control_plane_multimodal_handle_ptt_press_step();
if (press_err == ESP_OK) {
portENTER_CRITICAL(&s_runtime_lock);
if (s_ptt_runtime.state == PTT_STATE_WAIT_START_SENT) {
s_ptt_runtime.start_action_sent = true;
} else if (s_ptt_runtime.state == PTT_STATE_WAIT_SPEECH_STARTED) {
s_ptt_runtime.speech_action_sent = true;
}
portEXIT_CRITICAL(&s_runtime_lock);
control_plane_multimodal_reset_uplink_failure_window();
} else if (press_err != ESP_ERR_INVALID_STATE &&
press_err != ESP_ERR_NOT_SUPPORTED) {
@@ -1502,7 +1418,9 @@ static void control_plane_wifi_state_task(void *arg)
if (connected) {
bool ws_start_due = control_plane_consume_ws_start_requested();
if (ws_start_due && !control_plane_multimodal_is_connected()) {
if (ws_start_due &&
!control_plane_get_ptt_active() &&
!control_plane_multimodal_is_connected()) {
control_plane_try_start_multimodal_from_state_machine(&last_ws_start_attempt_tick, false);
now_tick = xTaskGetTickCount();
}
@@ -1844,23 +1762,6 @@ esp_err_t control_plane_start_runtime_workers(void)
(void)control_plane_schedule_websocket_wait_task();
#if CONFIG_TQ_DIRECTION_CONSOLE_ENABLE
if (control_plane_get_task_handle(&s_direction_console_task) == NULL) {
esp_err_t task_err = control_plane_create_task(control_plane_direction_console_task,
"cp_direction",
CONTROL_PLANE_DIRECTION_TASK_STACK_SIZE,
5,
CONTROL_PLANE_LOCAL_TASK_CORE,
&s_direction_console_task);
if (task_err != ESP_OK) {
control_plane_stop_runtime_workers();
return task_err;
}
}
#else
ESP_LOGI(TAG, "Direction console disabled by configuration");
#endif
#if CONFIG_TQ_SCREEN_ENABLE
esp_err_t lcd_worker_err = control_plane_start_lcd_render_worker();
if (lcd_worker_err != ESP_OK) {
@@ -1988,11 +1889,6 @@ void control_plane_stop_runtime_workers(void)
ESP_LOGW(TAG, "LCD render loop stop ended with %s", esp_err_to_name(lcd_stop_err));
}
#if CONFIG_TQ_DIRECTION_CONSOLE_ENABLE
if (!control_plane_wait_task_exit(&s_direction_console_task, CONTROL_PLANE_TASK_STOP_WAIT_MS)) {
control_plane_force_delete_task(&s_direction_console_task, "cp_direction");
}
#endif
if (!control_plane_wait_task_exit(&s_lcd_render_loop_task, CONTROL_PLANE_TASK_STOP_WAIT_MS)) {
control_plane_force_delete_task(&s_lcd_render_loop_task, "cp_lcd_render");
}