修复了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
@@ -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");
}