fix(connectivity): harden BLE reprovisioning under memory pressure

This commit is contained in:
chenjintao
2026-08-25 14:09:44 +08:00
parent 140e90447c
commit 6495f0e107
6 changed files with 323 additions and 58 deletions
@@ -105,22 +105,37 @@ static esp_err_t app_composition_run_plan_a_boot_sequence(void)
esp_err_to_name(battery_guard_err));
}
app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_AUDIO_INIT,
"audio_init",
"boot_sound_policy_begin");
app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_BOOT_SOUND,
"boot_sound",
"play_boot_sound_with_timeout");
esp_err_t boot_sound_err =
control_plane_play_boot_sound(CONFIG_TQ_BOOT_SOUND_TIMEOUT_MS);
if (boot_sound_err != ESP_OK) {
ESP_LOGW(TAG,
"Boot sound unavailable, continue boot: %s",
esp_err_to_name(boot_sound_err));
bool provisioning_recovery_boot =
control_plane_take_mini_program_recovery_boot_request();
if (provisioning_recovery_boot) {
app_composition_log_stage_skipped(APP_COMPOSITION_BOOT_STAGE_AUDIO_INIT,
"audio_init",
"ble_recovery_boot");
app_composition_log_stage_skipped(APP_COMPOSITION_BOOT_STAGE_BOOT_SOUND,
"boot_sound",
"ble_recovery_boot");
app_composition_log_stage_skipped(APP_COMPOSITION_BOOT_STAGE_AUDIO_READY,
"audio_ready",
"ble_recovery_boot");
} else {
app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_AUDIO_INIT,
"audio_init",
"boot_sound_policy_begin");
app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_BOOT_SOUND,
"boot_sound",
"play_boot_sound_with_timeout");
esp_err_t boot_sound_err =
control_plane_play_boot_sound(CONFIG_TQ_BOOT_SOUND_TIMEOUT_MS);
if (boot_sound_err != ESP_OK) {
ESP_LOGW(TAG,
"Boot sound unavailable, continue boot: %s",
esp_err_to_name(boot_sound_err));
}
app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_AUDIO_READY,
"audio_ready",
"boot_sound_policy_end_keep_runtime");
}
app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_AUDIO_READY,
"audio_ready",
"boot_sound_policy_end_keep_runtime");
#if CONFIG_TQ_SCREEN_ENABLE
app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_LCD_DEVICE,
@@ -131,7 +146,7 @@ static esp_err_t app_composition_run_plan_a_boot_sequence(void)
"lcd_device",
lcd_err);
bool lcd_ready = (lcd_err == ESP_OK);
if (lcd_ready) {
if (lcd_ready && !provisioning_recovery_boot) {
app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_LCD_RENDER_LOOP,
"lcd_render_loop",
"control_plane_start_lcd_render_worker");
@@ -142,10 +157,14 @@ static esp_err_t app_composition_run_plan_a_boot_sequence(void)
if (lcd_worker_err != ESP_OK) {
lcd_ready = false;
}
} else {
} else if (!lcd_ready) {
app_composition_log_stage_skipped(APP_COMPOSITION_BOOT_STAGE_LCD_RENDER_LOOP,
"lcd_render_loop",
"lcd_device_unavailable");
} else {
app_composition_log_stage_skipped(APP_COMPOSITION_BOOT_STAGE_LCD_RENDER_LOOP,
"lcd_render_loop",
"ble_recovery_boot");
}
#else
app_composition_log_stage_skipped(APP_COMPOSITION_BOOT_STAGE_LCD_DEVICE,
@@ -173,15 +192,17 @@ static esp_err_t app_composition_run_plan_a_boot_sequence(void)
}
#if CONFIG_TQ_FACTORY_CLI_ENABLE
err = control_plane_factory_cli_start();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Factory CLI start failed: %s", esp_err_to_name(err));
return err;
if (!provisioning_recovery_boot) {
err = control_plane_factory_cli_start();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Factory CLI start failed: %s", esp_err_to_name(err));
return err;
}
}
#endif
#if CONFIG_TQ_SCREEN_ENABLE
if (lcd_ready) {
if (lcd_ready && !provisioning_recovery_boot) {
esp_err_t expression_err = control_plane_request_boot_expression();
if (expression_err != ESP_OK) {
ESP_LOGW(TAG,
@@ -192,14 +213,21 @@ static esp_err_t app_composition_run_plan_a_boot_sequence(void)
#endif
#if CONFIG_TQ_BOOT_TURNTABLE_CALIBRATION_ENABLE
app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_TURNTABLE_CALIBRATION,
"turntable_calibration",
"control_plane_calibrate");
esp_err_t calibration_err = control_plane_calibrate();
if (calibration_err != ESP_OK) {
ESP_LOGW(TAG,
"Boot turntable calibration failed, continue degraded: %s",
esp_err_to_name(calibration_err));
if (provisioning_recovery_boot) {
app_composition_log_stage_skipped(
APP_COMPOSITION_BOOT_STAGE_TURNTABLE_CALIBRATION,
"turntable_calibration",
"ble_recovery_boot");
} else {
app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_TURNTABLE_CALIBRATION,
"turntable_calibration",
"control_plane_calibrate");
esp_err_t calibration_err = control_plane_calibrate();
if (calibration_err != ESP_OK) {
ESP_LOGW(TAG,
"Boot turntable calibration failed, continue degraded: %s",
esp_err_to_name(calibration_err));
}
}
#else
app_composition_log_stage_skipped(APP_COMPOSITION_BOOT_STAGE_TURNTABLE_CALIBRATION,
@@ -236,6 +236,15 @@ esp_err_t control_plane_calibrate(void);
esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
const char *seed_password,
int connect_timeout_ms);
/**
* @brief Consume a one-shot request to use the low-memory BLE recovery boot.
*
* The request survives a software restart in RTC no-init memory. A true result
* is latched for the current boot so connectivity skips STA reconnect and
* starts BLE provisioning before full runtime workers.
*/
bool control_plane_take_mini_program_recovery_boot_request(void);
bool control_plane_is_sta_connected(void);
esp_err_t control_plane_stop_mini_program_provisioning(int timeout_ms);
bool control_plane_is_mini_program_provisioning_running(void);
@@ -9,6 +9,7 @@
#include <string.h>
#include "control_plane_internal.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
@@ -40,26 +41,103 @@
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_CONNECT_TIMEOUT_MS 30000
#endif
#ifndef CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_FREE_INTERNAL_BYTES
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_FREE_INTERNAL_BYTES 49152
#endif
#ifndef CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_LARGEST_DMA_BYTES
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_LARGEST_DMA_BYTES 12288
#endif
#define CONTROL_PLANE_WIFI_SUCCESS_PROMPT_FILE "/littlefs/audio_prompts/network_connect_success.opus"
#define CONTROL_PLANE_STA_CONNECT_MAX_ATTEMPTS 3
#define CONTROL_PLANE_STA_RETRY_BASE_DELAY_MS 300
#define CONTROL_PLANE_PROVISIONING_QUEUE_LENGTH 1
#define CONTROL_PLANE_PROVISIONING_TASK_STACK_WORDS 4096
#define CONTROL_PLANE_PROVISIONING_TASK_STACK_BYTES 4096
#define CONTROL_PLANE_PROVISIONING_TASK_PRIORITY 5
#define CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT BIT0
#define CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT BIT1
#define CONTROL_PLANE_PROVISIONING_POLL_MS 100
#define CONTROL_PLANE_PROVISIONING_WAIT_LOG_MS 10000
#define CONTROL_PLANE_PROVISIONING_NOTIFY_SETTLE_MS 750
#define CONTROL_PLANE_PROVISIONING_TRANSPORT_STOP_MS 2000
#define CONTROL_PLANE_PROVISIONING_RUNTIME_QUIESCE_MS 2000
#define CONTROL_PLANE_PROVISIONING_RESTART_DELAY_MS 100
#define CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC 0x5451424cU
static const char *TAG = "control_plane_rt";
static bool s_wifi_success_prompt_played;
static bool s_provisioning_recovery_boot;
RTC_NOINIT_ATTR static uint32_t s_provisioning_recovery_marker;
RTC_NOINIT_ATTR static uint32_t s_provisioning_recovery_marker_inverse;
static portMUX_TYPE s_provisioning_lock = portMUX_INITIALIZER_UNLOCKED;
static QueueHandle_t s_provisioning_queue;
static EventGroupHandle_t s_provisioning_events;
static TaskHandle_t s_provisioning_task;
bool control_plane_take_mini_program_recovery_boot_request(void)
{
bool requested =
s_provisioning_recovery_marker == CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC &&
s_provisioning_recovery_marker_inverse ==
~CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC;
s_provisioning_recovery_marker = 0U;
s_provisioning_recovery_marker_inverse = 0U;
s_provisioning_recovery_boot = requested;
if (requested) {
ESP_LOGW(TAG, "Consumed one-shot low-memory BLE recovery boot request");
}
return requested;
}
static void control_plane_request_provisioning_recovery_restart(
const domain_ota_memory_headroom_t *headroom)
{
s_provisioning_recovery_marker = CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC;
s_provisioning_recovery_marker_inverse =
~CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC;
ESP_LOGW(TAG,
"Restarting into low-memory BLE recovery boot: free_internal=%u largest_internal=%u largest_dma=%u",
headroom != NULL ? (unsigned)headroom->free_internal : 0U,
headroom != NULL ? (unsigned)headroom->largest_internal : 0U,
headroom != NULL ? (unsigned)headroom->largest_dma : 0U);
vTaskDelay(pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_RESTART_DELAY_MS));
domain_runtime_restart();
}
static esp_err_t control_plane_check_provisioning_memory_headroom(void)
{
domain_ota_memory_headroom_t headroom = {0};
esp_err_t err = domain_runtime_ota_get_memory_headroom(&headroom);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Read BLE provisioning memory headroom failed: %s",
esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG,
"BLE provisioning memory headroom: free_internal=%u largest_internal=%u largest_dma=%u",
(unsigned)headroom.free_internal,
(unsigned)headroom.largest_internal,
(unsigned)headroom.largest_dma);
if (headroom.free_internal >=
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_FREE_INTERNAL_BYTES &&
headroom.largest_dma >=
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_LARGEST_DMA_BYTES) {
return ESP_OK;
}
ESP_LOGE(TAG,
"BLE provisioning memory headroom insufficient: need free_internal>=%u largest_dma>=%u",
(unsigned)CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_FREE_INTERNAL_BYTES,
(unsigned)CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_LARGEST_DMA_BYTES);
if (!s_provisioning_recovery_boot) {
control_plane_request_provisioning_recovery_restart(&headroom);
}
return ESP_ERR_NO_MEM;
}
static int elapsed_ms_since(TickType_t start_tick)
{
TickType_t elapsed_ticks = xTaskGetTickCount() - start_tick;
@@ -225,6 +303,19 @@ static void control_plane_finish_provisioning_ui(void)
}
}
static void control_plane_show_provisioning_ui(void)
{
const domain_expression_request_t request = {
.id = DOMAIN_EXPRESSION_WIFI_CONNECTING,
.trigger = DOMAIN_EXPRESSION_TRIGGER_WIFI_BOOTSTRAP,
};
esp_err_t err = domain_runtime_request_expression(&request);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "Wi-Fi provisioning display unavailable: %s",
esp_err_to_name(err));
}
}
static void control_plane_provisioning_event_callback(
const domain_wifi_provisioning_event_t *event,
void *user_data)
@@ -254,12 +345,24 @@ static void control_plane_provisioning_worker(void *arg)
{
(void)arg;
bool provisioning_succeeded = false;
TickType_t last_wait_log_tick = xTaskGetTickCount();
TickType_t wait_log_ticks =
pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_WAIT_LOG_MS);
if (wait_log_ticks == 0U) {
wait_log_ticks = 1U;
}
while (!control_plane_provisioning_stop_requested()) {
domain_wifi_provisioning_event_t event = {0};
if (xQueueReceive(s_provisioning_queue,
&event,
pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_POLL_MS)) != pdTRUE) {
TickType_t now_tick = xTaskGetTickCount();
if ((now_tick - last_wait_log_tick) >= wait_log_ticks) {
ESP_LOGI(TAG,
"BLE provisioning active; waiting for mini-program credentials");
last_wait_log_tick = now_tick;
}
continue;
}
@@ -319,7 +422,14 @@ static void control_plane_provisioning_worker(void *arg)
(void)xEventGroupSetBits(s_provisioning_events,
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT);
if (provisioning_succeeded && stop_err == ESP_OK) {
if (provisioning_succeeded && stop_err == ESP_OK &&
s_provisioning_recovery_boot) {
ESP_LOGI(TAG,
"BLE recovery provisioning completed; restarting into normal runtime");
s_provisioning_recovery_boot = false;
vTaskDelay(pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_RESTART_DELAY_MS));
domain_runtime_restart();
} else if (provisioning_succeeded && stop_err == ESP_OK) {
control_plane_finish_provisioning_ui();
ESP_LOGI(TAG,
"BLE provisioning completed; starting deferred runtime workers");
@@ -365,31 +475,31 @@ static esp_err_t control_plane_start_mini_program_provisioning(void)
CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT |
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT);
esp_err_t err = control_plane_check_provisioning_memory_headroom();
if (err != ESP_OK) {
return err;
}
err = domain_runtime_mini_program_provisioning_start(
control_plane_provisioning_event_callback,
NULL);
if (err != ESP_OK) {
return err;
}
BaseType_t created = xTaskCreate(control_plane_provisioning_worker,
"mini_prog_wifi",
CONTROL_PLANE_PROVISIONING_TASK_STACK_WORDS,
CONTROL_PLANE_PROVISIONING_TASK_STACK_BYTES,
NULL,
CONTROL_PLANE_PROVISIONING_TASK_PRIORITY,
&s_provisioning_task);
if (created != pdPASS) {
s_provisioning_task = NULL;
ESP_LOGE(TAG, "Create mini-program provisioning worker failed after BLE start");
(void)domain_runtime_mini_program_provisioning_stop(
CONTROL_PLANE_PROVISIONING_TRANSPORT_STOP_MS);
return ESP_ERR_NO_MEM;
}
esp_err_t err = domain_runtime_mini_program_provisioning_start(
control_plane_provisioning_event_callback,
NULL);
if (err != ESP_OK) {
(void)xEventGroupSetBits(s_provisioning_events,
CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT);
(void)xEventGroupWaitBits(
s_provisioning_events,
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT,
pdFALSE,
pdTRUE,
pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_TRANSPORT_STOP_MS));
return err;
}
ESP_LOGI(TAG, "Mini-program Wi-Fi provisioning started");
return ESP_OK;
#endif
@@ -443,12 +553,24 @@ esp_err_t control_plane_enter_mini_program_reprovisioning(const char *reason)
ESP_LOGW(TAG, "Entering mini-program BLE reprovisioning: reason=%s",
reason != NULL ? reason : "unspecified");
control_plane_wifi_set_keep_awake(true);
control_plane_set_runtime_stop_requested(true);
control_plane_set_ptt_active(false);
(void)control_plane_local_wake_stop(
CONTROL_PLANE_PROVISIONING_RUNTIME_QUIESCE_MS);
(void)control_plane_stop_multimodal_client();
(void)domain_runtime_multimodal_audio_stop_downstream_playback();
domain_runtime_multimodal_audio_deinit();
control_plane_show_provisioning_ui();
esp_err_t err = domain_runtime_wifi_portal_stop_sta();
if (err == ESP_OK) {
err = control_plane_start_mini_program_provisioning();
}
if (err != ESP_OK) {
control_plane_wifi_set_keep_awake(false);
if (!s_provisioning_recovery_boot) {
control_plane_request_provisioning_recovery_restart(NULL);
}
}
return err;
#endif
@@ -469,7 +591,10 @@ esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
return err;
}
if (domain_runtime_wifi_portal_has_saved_credentials()) {
if (s_provisioning_recovery_boot) {
ESP_LOGW(TAG,
"Low-memory recovery boot skips STA reconnect and enters BLE provisioning");
} else if (domain_runtime_wifi_portal_has_saved_credentials()) {
int timeout_ms = control_plane_normalize_connect_timeout_ms(connect_timeout_ms, 0);
err = control_plane_connect_sta_with_feedback(timeout_ms, true);
if (err == ESP_OK) {
@@ -480,20 +605,26 @@ esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
esp_err_to_name(err));
}
const domain_expression_request_t connecting_request = {
.id = DOMAIN_EXPRESSION_WIFI_CONNECTING,
.trigger = DOMAIN_EXPRESSION_TRIGGER_WIFI_BOOTSTRAP,
};
esp_err_t display_err = domain_runtime_request_expression(&connecting_request);
if (display_err != ESP_OK && display_err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "Wi-Fi connecting QR display unavailable: %s",
esp_err_to_name(display_err));
}
control_plane_show_provisioning_ui();
control_plane_wifi_set_keep_awake(true);
err = control_plane_start_mini_program_provisioning();
if (err == ESP_OK && s_provisioning_recovery_boot) {
esp_err_t lcd_err = control_plane_start_lcd_render_worker();
if (lcd_err != ESP_OK && lcd_err != ESP_ERR_NOT_SUPPORTED) {
ESP_LOGW(TAG,
"Start LCD after BLE recovery provisioning failed: %s",
esp_err_to_name(lcd_err));
}
}
if (err != ESP_OK) {
control_plane_wifi_set_keep_awake(false);
if (s_provisioning_recovery_boot) {
ESP_LOGE(TAG,
"Low-memory BLE recovery boot failed; resume normal boot: %s",
esp_err_to_name(err));
s_provisioning_recovery_boot = false;
}
ESP_LOGE(TAG, "BLE provisioning unavailable; SoftAP fallback disabled: %s",
esp_err_to_name(err));
}