fix(connectivity): harden BLE reprovisioning under memory pressure
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# BLE Provisioning Memory Recovery
|
||||
|
||||
## Failure Mode
|
||||
|
||||
Runtime Wi-Fi recovery can fall back to mini-program BLE provisioning after
|
||||
saved network candidates fail. The BLE controller requires contiguous internal
|
||||
DMA-capable memory even when the NimBLE host is configured to use external RAM.
|
||||
Starting BLE late in a fragmented runtime previously allowed a controller
|
||||
allocation to fail inside ROM code, followed by an assertion and an interrupt
|
||||
watchdog reset.
|
||||
|
||||
## Runtime Transition
|
||||
|
||||
Before runtime reprovisioning starts BLE, the control plane now:
|
||||
|
||||
1. Blocks new runtime work through the runtime stop gate.
|
||||
2. Clears active PTT state.
|
||||
3. Stops local wake detection and its owned capture path.
|
||||
4. Stops the multimodal client and downstream playback.
|
||||
5. Fully deinitializes the multimodal audio pipeline.
|
||||
6. Switches the running LCD renderer to the provisioning QR expression.
|
||||
7. Stops the Wi-Fi station.
|
||||
|
||||
The provisioning queue and event group are allocated first. Memory headroom is
|
||||
then checked before entering the BLE controller. NimBLE is initialized before
|
||||
the 4096-byte provisioning worker is created, preserving one additional
|
||||
contiguous internal block during controller startup.
|
||||
|
||||
## Memory Gate
|
||||
|
||||
BLE initialization proceeds only when both configured thresholds are met:
|
||||
|
||||
```text
|
||||
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_FREE_INTERNAL_BYTES=49152
|
||||
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_LARGEST_DMA_BYTES=12288
|
||||
```
|
||||
|
||||
Both current values and thresholds are logged before BLE startup. These values
|
||||
are conservative guards against the observed 4096-byte controller allocation
|
||||
failure and leave room for the controller's preceding allocations.
|
||||
|
||||
## Recovery Boot
|
||||
|
||||
If the runtime cannot provide enough headroom, the control plane writes a
|
||||
magic value and its inverse to RTC no-init memory, waits 100 ms for logs, and
|
||||
performs a controlled software restart. The marker is one-shot and does not
|
||||
write flash.
|
||||
|
||||
On the next boot, application composition keeps the battery safety service but
|
||||
defers the boot sound, LCD render worker, factory CLI worker, and turntable
|
||||
calibration. Connectivity skips the saved-credential STA attempt and starts
|
||||
BLE provisioning before full runtime workers. The LCD renderer starts only
|
||||
after BLE has secured its controller memory. Successful provisioning tears
|
||||
down BLE and restarts into the normal boot sequence.
|
||||
|
||||
While provisioning is active, the worker logs every 10 seconds that it is
|
||||
waiting for mini-program credentials. Runtime transitions keep the LCD active
|
||||
on the provisioning QR expression, so a healthy wait state is distinguishable
|
||||
from a stalled device.
|
||||
|
||||
If recovery-boot BLE startup itself fails, the one-shot mode is cleared and
|
||||
normal runtime startup resumes instead of entering a restart loop.
|
||||
|
||||
## Validation
|
||||
|
||||
Required device validation covers:
|
||||
|
||||
- normal boot provisioning with no saved credentials;
|
||||
- runtime Wi-Fi loss followed by failed candidate traversal;
|
||||
- low-memory fallback through the one-shot recovery boot;
|
||||
- successful provisioning followed by a normal reboot;
|
||||
- repeated Wi-Fi/BLE transitions under audio and local-wake load;
|
||||
- confirmation that no `BLE_INIT: Malloc failed`, ROM assertion, or interrupt
|
||||
watchdog reset occurs.
|
||||
@@ -180,6 +180,27 @@ menu "TQ Cat Configuration"
|
||||
default 2
|
||||
range 1 10
|
||||
|
||||
config TQ_MINI_PROGRAM_PROVISIONING_MIN_FREE_INTERNAL_BYTES
|
||||
int "Minimum free internal memory before BLE provisioning"
|
||||
depends on TQ_MINI_PROGRAM_PROVISIONING_ENABLE
|
||||
default 49152
|
||||
range 16384 131072
|
||||
help
|
||||
BLE controller initialization requires internal DMA-capable
|
||||
memory even when the NimBLE host uses external RAM. Runtime
|
||||
reprovisioning performs a controlled recovery boot when free
|
||||
internal memory remains below this threshold after teardown.
|
||||
|
||||
config TQ_MINI_PROGRAM_PROVISIONING_MIN_LARGEST_DMA_BYTES
|
||||
int "Minimum largest DMA block before BLE provisioning"
|
||||
depends on TQ_MINI_PROGRAM_PROVISIONING_ENABLE
|
||||
default 12288
|
||||
range 4096 65536
|
||||
help
|
||||
Minimum contiguous DMA-capable block required before entering
|
||||
the BLE controller. This headroom prevents the controller ROM
|
||||
from asserting after a late allocation failure.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Runtime Diagnostics"
|
||||
|
||||
@@ -19,6 +19,8 @@ CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y
|
||||
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE=y
|
||||
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_CONNECT_TIMEOUT_MS=30000
|
||||
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_RECONNECT_FAILURES=2
|
||||
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_FREE_INTERNAL_BYTES=49152
|
||||
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_LARGEST_DMA_BYTES=12288
|
||||
CONFIG_TQ_WIFI_CONNECT_CYCLE_TIMEOUT_MS=20000
|
||||
|
||||
# External PSRAM (ESP32-S3 octal mode)
|
||||
|
||||
Reference in New Issue
Block a user