fix(control_plane): bound provisioning recovery and prevent reboot loops

This commit is contained in:
chenjintao
2026-10-23 15:37:59 +08:00
parent aede687dc9
commit 415c75b0e6
12 changed files with 947 additions and 57 deletions
@@ -284,7 +284,8 @@ esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
* @brief 消费一次低内存 BLE 恢复启动请求。 / Consume a one-shot BLE recovery request.
*
* 请求借助 RTC no-init 内存跨软重启;本次启动读到 true 后会锁存,使联网流程跳过 STA 重连,
* 在完整运行时任务启动前优先进入 BLE 配网。
* 在完整运行时任务启动前优先进入 BLE 配网。失败后保留“已尝试恢复”的记录,
* 身份/配置更新和再次消费请求均不会重新开放恢复重启资格。
*
* 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
@@ -187,3 +187,8 @@ esp_err_t control_plane_schedule_websocket_wait_task(void);
void control_plane_set_ptt_active(bool active);
void control_plane_set_ptt_active_from_local_wake(void);
esp_err_t control_plane_enter_mini_program_reprovisioning(const char *reason);
/* 非阻塞通知/策略查询;配置写入线程只发通知,启动仍由 Wi-Fi worker 串行执行。 */
void control_plane_provisioning_configuration_changed(void);
bool control_plane_provisioning_retry_due(void);
bool control_plane_provisioning_allows_voice(void);
void control_plane_provisioning_sta_connected(void);
@@ -0,0 +1,76 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "domain.h"
/* 私有纯策略,由 connectivity 模块在 s_provisioning_lock 内调用。
* 不持有 BLE 的运行状态副本:ACTIVE 仅表示协调器正在执行这一轮配网。
* 无定时器/后台任务;Wi-Fi worker 轮询 due 后才执行有副作用的启动。 */
typedef enum {
CP_PROVISIONING_IDLE = 0,
CP_PROVISIONING_STARTING,
CP_PROVISIONING_ACTIVE,
CP_PROVISIONING_STOPPING,
CP_PROVISIONING_WAIT_IDENTITY,
CP_PROVISIONING_BACKOFF,
CP_PROVISIONING_BLOCKED,
} cp_provisioning_phase_t;
typedef struct {
cp_provisioning_phase_t phase;
unsigned retry_count;
uint32_t failed_at_ms;
uint32_t retry_delay_ms;
bool retry_requested;
bool recovery_attempted;
bool runtime_quiesced;
/* 协调器尚欠一次成功的有界 teardown;不是 BLE ready 状态的镜像。 */
bool cleanup_required;
} cp_provisioning_policy_t;
static inline bool cp_provisioning_policy_due(cp_provisioning_policy_t *policy, uint32_t now_ms)
{
if (policy->phase == CP_PROVISIONING_STARTING || policy->phase == CP_PROVISIONING_ACTIVE ||
policy->phase == CP_PROVISIONING_STOPPING) {
return false;
}
if (policy->retry_requested) {
/* 配置通知只重新开放普通尝试,不重置“已恢复重启”锁存。 */
policy->retry_requested = false;
policy->phase = CP_PROVISIONING_IDLE;
policy->retry_count = 0;
}
return policy->phase == CP_PROVISIONING_IDLE ||
(policy->phase == CP_PROVISIONING_BACKOFF &&
(uint32_t)(now_ms - policy->failed_at_ms) >= policy->retry_delay_ms);
}
static inline void cp_provisioning_policy_failed(cp_provisioning_policy_t *policy,
domain_wifi_provisioning_start_status_t status,
uint32_t now_ms)
{
static const uint32_t retry_delays_ms[] = {1000, 3000, 10000};
policy->retry_delay_ms = 0;
if (status == DOMAIN_WIFI_PROVISIONING_START_IDENTITY_REQUIRED) {
policy->phase = CP_PROVISIONING_WAIT_IDENTITY;
} else if (status == DOMAIN_WIFI_PROVISIONING_START_TRANSIENT &&
policy->retry_count < sizeof(retry_delays_ms) / sizeof(retry_delays_ms[0])) {
policy->retry_delay_ms = retry_delays_ms[policy->retry_count++];
policy->failed_at_ms = now_ms;
policy->phase = CP_PROVISIONING_BACKOFF;
} else {
policy->phase = CP_PROVISIONING_BLOCKED;
}
}
static inline bool cp_provisioning_policy_claim_recovery(cp_provisioning_policy_t *policy,
domain_wifi_provisioning_start_status_t status)
{
if (status != DOMAIN_WIFI_PROVISIONING_START_NO_MEMORY || policy->recovery_attempted) {
return false;
}
/* 当前启动先锁存,再写 RTC 请求。恢复启动消费请求时再次锁存;失败不可清零。 */
policy->recovery_attempted = true;
return true;
}
@@ -9,6 +9,8 @@
#include <string.h>
#include "control_plane_internal.h"
#include "control_plane_provisioning_policy.h"
#include "esp_timer.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
@@ -64,6 +66,7 @@
#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_WORKER_START_BIT BIT2
#define CONTROL_PLANE_PROVISIONING_POLL_MS 100
#define CONTROL_PLANE_PROVISIONING_WAIT_LOG_MS 10000
#define CONTROL_PLANE_PROVISIONING_NOTIFY_SETTLE_MS 750
@@ -86,7 +89,66 @@ 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;
static cp_provisioning_policy_t s_provisioning_policy;
static uint32_t control_plane_provisioning_now_ms(void)
{
return (uint32_t)(esp_timer_get_time() / 1000);
}
static bool control_plane_provisioning_is_recovery_boot(void)
{
portENTER_CRITICAL(&s_provisioning_lock);
bool recovery_boot = s_provisioning_recovery_boot;
portEXIT_CRITICAL(&s_provisioning_lock);
return recovery_boot;
}
bool control_plane_provisioning_retry_due(void)
{
uint32_t now_ms = control_plane_provisioning_now_ms();
portENTER_CRITICAL(&s_provisioning_lock);
bool due = cp_provisioning_policy_due(&s_provisioning_policy, now_ms);
portEXIT_CRITICAL(&s_provisioning_lock);
return due;
}
void control_plane_provisioning_configuration_changed(void)
{
/* 产测线程只发布请求;不在串口回调中停音频、等待 BLE 或重启设备。 */
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_policy.retry_requested = true;
portEXIT_CRITICAL(&s_provisioning_lock);
control_plane_request_wifi_reconnect_now();
}
bool control_plane_provisioning_allows_voice(void)
{
portENTER_CRITICAL(&s_provisioning_lock);
bool allowed = !s_provisioning_policy.runtime_quiesced;
portEXIT_CRITICAL(&s_provisioning_lock);
return allowed;
}
void control_plane_provisioning_sta_connected(void)
{
/* 非 BLE 路径恢复 STA 时也解除语音暂停;活动配网仍由自己的 worker 负责释放。 */
bool resume_voice = false;
portENTER_CRITICAL(&s_provisioning_lock);
if (s_provisioning_policy.phase != CP_PROVISIONING_STARTING &&
s_provisioning_policy.phase != CP_PROVISIONING_ACTIVE &&
!s_provisioning_policy.cleanup_required) {
resume_voice = s_provisioning_policy.runtime_quiesced;
s_provisioning_policy.runtime_quiesced = false;
}
portEXIT_CRITICAL(&s_provisioning_lock);
if (resume_voice && control_plane_ota_voice_services_allowed()) {
esp_err_t err = control_plane_local_wake_start();
if (err != ESP_OK) {
ESP_LOGW(TAG, "Restore local wake after STA recovery failed: %s", esp_err_to_name(err));
}
}
}
bool control_plane_take_mini_program_recovery_boot_request(void)
{
bool requested =
@@ -95,7 +157,11 @@ bool control_plane_take_mini_program_recovery_boot_request(void)
~CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC;
s_provisioning_recovery_marker = 0U;
s_provisioning_recovery_marker_inverse = 0U;
s_provisioning_recovery_boot = requested;
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_recovery_boot = s_provisioning_recovery_boot || requested;
s_provisioning_policy.recovery_attempted =
s_provisioning_policy.recovery_attempted || requested;
portEXIT_CRITICAL(&s_provisioning_lock);
if (requested) {
ESP_LOGW(TAG, "Consumed one-shot low-memory BLE recovery boot request");
}
@@ -103,20 +169,72 @@ bool control_plane_take_mini_program_recovery_boot_request(void)
}
static void control_plane_request_provisioning_recovery_restart(
const char *stage, esp_err_t error,
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);
if (headroom != NULL) {
ESP_LOGW(TAG,
"Restarting once for BLE memory recovery: stage=%s error=%s free_internal=%u largest_internal=%u largest_dma=%u",
stage, esp_err_to_name(error), (unsigned)headroom->free_internal,
(unsigned)headroom->largest_internal, (unsigned)headroom->largest_dma);
} else {
/* 采样失败不伪造 0;本分支只接受已确认的 NO_MEMORY 原因。 */
ESP_LOGW(TAG, "Restarting once for BLE memory recovery: stage=%s error=%s memory=unknown",
stage, esp_err_to_name(error));
}
vTaskDelay(pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_RESTART_DELAY_MS));
domain_runtime_restart();
}
static const char *control_plane_provisioning_failure_name(domain_wifi_provisioning_start_status_t status)
{
switch (status) {
case DOMAIN_WIFI_PROVISIONING_START_IDENTITY_REQUIRED: return "identity_required";
case DOMAIN_WIFI_PROVISIONING_START_INVALID_CONFIG: return "invalid_config";
case DOMAIN_WIFI_PROVISIONING_START_NO_MEMORY: return "no_memory";
case DOMAIN_WIFI_PROVISIONING_START_TRANSIENT: return "transient";
default: return "failed";
}
}
static void control_plane_provisioning_start_failed(
const char *stage, esp_err_t error, domain_wifi_provisioning_start_status_t status)
{
domain_ota_memory_headroom_t headroom = {0};
esp_err_t memory_err = domain_runtime_ota_get_memory_headroom(&headroom);
uint32_t now_ms = control_plane_provisioning_now_ms();
portENTER_CRITICAL(&s_provisioning_lock);
if (s_provisioning_policy.phase != CP_PROVISIONING_STOPPING) {
cp_provisioning_policy_failed(&s_provisioning_policy, status, now_ms);
}
bool restart = cp_provisioning_policy_claim_recovery(&s_provisioning_policy, status);
uint32_t delay_ms = s_provisioning_policy.retry_delay_ms;
bool attempted = s_provisioning_policy.recovery_attempted;
bool voice_paused = s_provisioning_policy.runtime_quiesced;
portEXIT_CRITICAL(&s_provisioning_lock);
ESP_LOGE(TAG,
"BLE provisioning start failed: stage=%s error=%s reason=%s retry_ms=%u recovery_attempted=%d voice_paused=%d",
stage, esp_err_to_name(error), control_plane_provisioning_failure_name(status),
(unsigned)delay_ms, attempted, voice_paused);
if (!restart && delay_ms == 0U) {
ESP_LOGW(TAG, "BLE auto-retry paused; waiting for save_config or retry_provisioning");
}
if (memory_err == ESP_OK) {
ESP_LOGI(TAG, "BLE failure memory snapshot: free_internal=%u largest_internal=%u largest_dma=%u",
(unsigned)headroom.free_internal, (unsigned)headroom.largest_internal,
(unsigned)headroom.largest_dma);
} else {
ESP_LOGW(TAG, "BLE failure memory snapshot unavailable: %s", esp_err_to_name(memory_err));
}
if (restart) {
control_plane_request_provisioning_recovery_restart(
stage, error, memory_err == ESP_OK ? &headroom : NULL);
}
}
static esp_err_t control_plane_check_provisioning_memory_headroom(void)
{
/* BLE 初始化需要连续内部/DMA 块;总空闲堆达标但最大块不足时仍必须拒绝并走恢复启动。 */
@@ -144,9 +262,6 @@ static esp_err_t control_plane_check_provisioning_memory_headroom(void)
"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;
}
@@ -359,6 +474,27 @@ static void control_plane_provisioning_worker(void *arg)
{
/* 状态通知顺序也是小程序协议的一部分:connecting→got_ip→saving→ready,失败则给明确原因。 */
(void)arg;
/* 创建者先发布 handle/ACTIVE,再放行,避免快速退出留下失效任务句柄。 */
EventBits_t start_bits = xEventGroupWaitBits(
s_provisioning_events, CONTROL_PLANE_PROVISIONING_WORKER_START_BIT,
pdFALSE, pdTRUE, pdMS_TO_TICKS(5000));
if ((start_bits & CONTROL_PLANE_PROVISIONING_WORKER_START_BIT) == 0) {
ESP_LOGE(TAG, "Provisioning worker start gate timed out");
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_task = NULL;
s_provisioning_policy.phase = CP_PROVISIONING_STOPPING;
portEXIT_CRITICAL(&s_provisioning_lock);
esp_err_t stop_err = domain_runtime_mini_program_provisioning_stop(
CONTROL_PLANE_PROVISIONING_TRANSPORT_STOP_MS);
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_policy.cleanup_required = stop_err != ESP_OK;
s_provisioning_policy.phase = CP_PROVISIONING_BLOCKED;
portEXIT_CRITICAL(&s_provisioning_lock);
control_plane_wifi_set_keep_awake(false);
(void)xEventGroupSetBits(s_provisioning_events, CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT);
vTaskDelete(NULL);
return;
}
bool provisioning_succeeded = false;
TickType_t last_wait_log_tick = xTaskGetTickCount();
TickType_t wait_log_ticks =
@@ -433,15 +569,25 @@ static void control_plane_provisioning_worker(void *arg)
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_task = NULL;
s_provisioning_policy.cleanup_required = stop_err != ESP_OK;
/* 取消/停止不自动重进配网;只有成功完成才重新开放普通运行。 */
s_provisioning_policy.phase = provisioning_succeeded && stop_err == ESP_OK
? CP_PROVISIONING_IDLE : CP_PROVISIONING_BLOCKED;
if (provisioning_succeeded && stop_err == ESP_OK) {
s_provisioning_policy.runtime_quiesced = false;
s_provisioning_policy.retry_count = 0;
}
portEXIT_CRITICAL(&s_provisioning_lock);
(void)xEventGroupSetBits(s_provisioning_events,
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT);
if (provisioning_succeeded && stop_err == ESP_OK &&
s_provisioning_recovery_boot) {
control_plane_provisioning_is_recovery_boot()) {
ESP_LOGI(TAG,
"BLE recovery provisioning completed; restarting into normal runtime");
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_recovery_boot = false;
portEXIT_CRITICAL(&s_provisioning_lock);
vTaskDelay(pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_RESTART_DELAY_MS));
domain_runtime_restart();
} else if (provisioning_succeeded && stop_err == ESP_OK) {
@@ -461,11 +607,15 @@ static void control_plane_provisioning_worker(void *arg)
vTaskDelete(NULL);
}
static esp_err_t control_plane_start_mini_program_provisioning(void)
static esp_err_t control_plane_start_provisioning_transport(
domain_wifi_provisioning_start_status_t *status, const char **stage)
{
#if !CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE
*status = DOMAIN_WIFI_PROVISIONING_START_INVALID_CONFIG;
return ESP_ERR_NOT_SUPPORTED;
#else
*stage = "resources";
*status = DOMAIN_WIFI_PROVISIONING_START_NO_MEMORY;
portENTER_CRITICAL(&s_provisioning_lock);
bool already_running = s_provisioning_task != NULL;
portEXIT_CRITICAL(&s_provisioning_lock);
@@ -488,33 +638,74 @@ static esp_err_t control_plane_start_mini_program_provisioning(void)
(void)xEventGroupClearBits(
s_provisioning_events,
CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT |
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT);
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT |
CONTROL_PLANE_PROVISIONING_WORKER_START_BIT);
*stage = "memory_check";
esp_err_t err = control_plane_check_provisioning_memory_headroom();
if (err != ESP_OK) {
*status = err == ESP_ERR_NO_MEM ? DOMAIN_WIFI_PROVISIONING_START_NO_MEMORY
: DOMAIN_WIFI_PROVISIONING_START_FAILED;
return err;
}
*stage = "ble_start";
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_policy.cleanup_required = true;
s_provisioning_policy.runtime_quiesced = true;
portEXIT_CRITICAL(&s_provisioning_lock);
err = domain_runtime_mini_program_provisioning_start(
control_plane_provisioning_event_callback,
NULL);
NULL, status);
if (err != ESP_OK) {
/* 平台启动超时也可能带有尚在停止的 host,必须确认 teardown,不能直接重试。 */
esp_err_t stop_err = domain_runtime_mini_program_provisioning_stop(
CONTROL_PLANE_PROVISIONING_TRANSPORT_STOP_MS);
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_policy.cleanup_required = stop_err != ESP_OK;
portEXIT_CRITICAL(&s_provisioning_lock);
if (stop_err != ESP_OK) {
*stage = "ble_start_cleanup";
*status = DOMAIN_WIFI_PROVISIONING_START_FAILED;
return stop_err;
}
return err;
}
*stage = "worker_create";
TaskHandle_t worker_task = NULL;
BaseType_t created = xTaskCreate(control_plane_provisioning_worker,
"mini_prog_wifi",
CONTROL_PLANE_PROVISIONING_TASK_STACK_BYTES,
NULL,
CONTROL_PLANE_PROVISIONING_TASK_PRIORITY,
&s_provisioning_task);
&worker_task);
if (created != pdPASS) {
s_provisioning_task = NULL;
*status = DOMAIN_WIFI_PROVISIONING_START_NO_MEMORY;
ESP_LOGE(TAG, "Create mini-program provisioning worker failed after BLE start");
(void)domain_runtime_mini_program_provisioning_stop(
esp_err_t stop_err = domain_runtime_mini_program_provisioning_stop(
CONTROL_PLANE_PROVISIONING_TRANSPORT_STOP_MS);
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_policy.cleanup_required = stop_err != ESP_OK;
portEXIT_CRITICAL(&s_provisioning_lock);
if (stop_err != ESP_OK) {
*status = DOMAIN_WIFI_PROVISIONING_START_FAILED;
return stop_err;
}
return ESP_ERR_NO_MEM;
}
portENTER_CRITICAL(&s_provisioning_lock);
if (s_provisioning_policy.phase != CP_PROVISIONING_STARTING) {
/* 启动门若已超时,worker 自行收尾;不能再发布其可能失效的任务句柄。 */
portEXIT_CRITICAL(&s_provisioning_lock);
*status = DOMAIN_WIFI_PROVISIONING_START_FAILED;
return ESP_ERR_TIMEOUT;
}
s_provisioning_task = worker_task;
s_provisioning_policy.phase = CP_PROVISIONING_ACTIVE;
portEXIT_CRITICAL(&s_provisioning_lock);
/* 用常驻 event group 放行,避免异常调度时对已退出的任务句柄发送通知。 */
(void)xEventGroupSetBits(s_provisioning_events, CONTROL_PLANE_PROVISIONING_WORKER_START_BIT);
ESP_LOGI(TAG, "Mini-program Wi-Fi provisioning started");
return ESP_OK;
#endif
@@ -556,42 +747,105 @@ esp_err_t control_plane_stop_mini_program_provisioning(int timeout_ms)
: ESP_ERR_TIMEOUT;
}
esp_err_t control_plane_enter_mini_program_reprovisioning(const char *reason)
static esp_err_t control_plane_try_provisioning(bool quiesce_runtime)
{
/* 先静默语音/网络运行时释放内存;仍不足时写 RTC 标记并软重启进入最小资源配网路径。 */
#if !CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE
(void)reason;
(void)quiesce_runtime;
return ESP_ERR_NOT_SUPPORTED;
#else
if (control_plane_is_mini_program_provisioning_running()) {
uint32_t now_ms = control_plane_provisioning_now_ms();
portENTER_CRITICAL(&s_provisioning_lock);
bool allowed = cp_provisioning_policy_due(&s_provisioning_policy, now_ms);
if (allowed) {
s_provisioning_policy.phase = CP_PROVISIONING_STARTING;
}
portEXIT_CRITICAL(&s_provisioning_lock);
if (!allowed) {
return control_plane_is_mini_program_provisioning_running() ? ESP_OK : ESP_ERR_INVALID_STATE;
}
/* 身份缺失时 BLE 无法启动。先检查,保持已有音频/串口等资源不受影响。 */
domain_wifi_provisioning_start_status_t status =
domain_runtime_mini_program_provisioning_preflight();
esp_err_t err = ESP_OK;
const char *stage = "identity";
if (status != DOMAIN_WIFI_PROVISIONING_START_READY) {
err = ESP_ERR_NOT_FOUND;
goto failed;
}
control_plane_wifi_set_keep_awake(true);
if (quiesce_runtime) {
/* 只暂停语音,不设置全局 stop_requested:Wi-Fi 协调、LCD、电池和 CLI 必须继续运行。
* 后续启动失败时进入明确的离线等待/退避模式,语音保持暂停以免抢占 BLE 重试内存;
* 配网成功或 STA 恢复后解除暂停。 */
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_policy.runtime_quiesced = true;
portEXIT_CRITICAL(&s_provisioning_lock);
control_plane_set_ptt_active(false);
stage = "stop_local_wake";
err = control_plane_local_wake_stop(CONTROL_PLANE_PROVISIONING_RUNTIME_QUIESCE_MS);
if (err != ESP_OK) {
goto quiesce_failed;
}
stage = "stop_multimodal";
err = control_plane_stop_multimodal_client();
if (err != ESP_OK) {
goto quiesce_failed;
}
stage = "stop_playback";
err = domain_runtime_multimodal_audio_stop_downstream_playback();
if (err != ESP_OK) {
goto quiesce_failed;
}
domain_runtime_multimodal_audio_deinit();
}
portENTER_CRITICAL(&s_provisioning_lock);
bool cleanup_required = s_provisioning_policy.cleanup_required;
portEXIT_CRITICAL(&s_provisioning_lock);
if (cleanup_required) {
/* 上轮 teardown 超时后只允许显式重试;先完成释放,不能直接复用旧 BLE 实例。 */
stage = "previous_ble_teardown";
err = domain_runtime_mini_program_provisioning_stop(
CONTROL_PLANE_PROVISIONING_TRANSPORT_STOP_MS);
if (err != ESP_OK) {
status = DOMAIN_WIFI_PROVISIONING_START_FAILED;
goto failed;
}
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_policy.cleanup_required = false;
portEXIT_CRITICAL(&s_provisioning_lock);
}
control_plane_show_provisioning_ui();
stage = "stop_sta";
err = domain_runtime_wifi_portal_stop_sta();
if (err != ESP_OK) {
goto quiesce_failed;
}
err = control_plane_start_provisioning_transport(&status, &stage);
if (err == ESP_OK) {
return ESP_OK;
}
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();
goto failed;
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);
}
}
quiesce_failed:
/* 停止操作失败时不能继续释放其仍在使用的资源;超时可有界重试,其余故障等待诊断。 */
status = err == ESP_ERR_TIMEOUT ? DOMAIN_WIFI_PROVISIONING_START_TRANSIENT
: DOMAIN_WIFI_PROVISIONING_START_FAILED;
failed:
control_plane_wifi_set_keep_awake(false);
control_plane_provisioning_start_failed(stage, err, status);
return err;
#endif
}
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");
return control_plane_try_provisioning(true);
}
esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
const char *seed_password,
int connect_timeout_ms)
@@ -608,7 +862,7 @@ esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
return err;
}
if (s_provisioning_recovery_boot) {
if (control_plane_provisioning_is_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()) {
@@ -622,11 +876,8 @@ esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
esp_err_to_name(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) {
err = control_plane_try_provisioning(false);
if (err == ESP_OK && control_plane_provisioning_is_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,
@@ -636,11 +887,14 @@ esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
}
if (err != ESP_OK) {
control_plane_wifi_set_keep_awake(false);
if (s_provisioning_recovery_boot) {
if (control_plane_provisioning_is_recovery_boot()) {
ESP_LOGE(TAG,
"Low-memory BLE recovery boot failed; resume normal boot: %s",
esp_err_to_name(err));
/* 退出精简启动模式,但已尝试恢复的锁存保留,禁止同一故障再次重启。 */
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_recovery_boot = false;
portEXIT_CRITICAL(&s_provisioning_lock);
}
ESP_LOGE(TAG, "BLE provisioning unavailable; SoftAP fallback disabled: %s",
esp_err_to_name(err));
@@ -485,6 +485,8 @@ static void handle_save_config(void)
s_pending_serial_number[0] = '\0';
/* Force a fresh challenge/response against the new identity on next use. */
control_plane_multimodal_device_auth_invalidate(0);
/* 仅发布配置更新;Wi-Fi worker 在自己的上下文恢复配网,不阻塞串口应答。 */
control_plane_provisioning_configuration_changed();
ESP_LOGI(TAG, "Factory identity committed");
cli_write_ok();
}
@@ -625,6 +627,10 @@ static void handle_command_line(char *line)
cli_write_ok();
} else if (strcmp(cmd, "save_config") == 0) {
handle_save_config();
} else if (strcmp(cmd, "retry_provisioning") == 0) {
/* 诊断/修正配置后的显式重试不重置内存恢复次数。 */
control_plane_provisioning_configuration_changed();
cli_write_ok();
} else if (strcmp(cmd, "get_wifi") == 0) {
handle_get_wifi();
} else if (strcmp(cmd, "set_wifi") == 0) {
@@ -148,7 +148,7 @@ bool control_plane_ota_voice_services_allowed(void)
portENTER_CRITICAL(&s_ota_lock);
allowed = !s_ota_voice_services_reserved;
portEXIT_CRITICAL(&s_ota_lock);
return allowed;
return allowed && control_plane_provisioning_allows_voice();
}
bool control_plane_ota_business_interactions_allowed(void)
@@ -159,7 +159,7 @@ bool control_plane_ota_business_interactions_allowed(void)
s_ota_snapshot.state != CONTROL_PLANE_OTA_STATE_DOWNLOADING &&
s_ota_snapshot.state != CONTROL_PLANE_OTA_STATE_READY_TO_REBOOT;
portEXIT_CRITICAL(&s_ota_lock);
return allowed;
return allowed && control_plane_provisioning_allows_voice();
}
static void control_plane_ota_set_state(control_plane_ota_state_t state,
@@ -440,6 +440,10 @@ static esp_err_t control_plane_ota_acquire_voice_window(
}
memset(window, 0, sizeof(*window));
/* 配网只暂停资源,不再终止长期 worker;BLE 未释放前不另开 OTA HTTPS 窗口。 */
if (!control_plane_provisioning_allows_voice()) {
return ESP_ERR_INVALID_STATE;
}
if (!initial_window && !allow_active_teardown && !control_plane_ota_runtime_is_idle()) {
return ESP_ERR_INVALID_STATE;
}
@@ -2231,6 +2231,17 @@ static void control_plane_wifi_state_task(void *arg)
control_plane_memory_monitor_sample(now_tick, false);
#endif
if (connected) {
control_plane_provisioning_sta_connected();
/* BLE 收到凭据后 STA 可先连上,但 teardown 尚未完成;不要抢跑 TLS/语音。 */
if (!control_plane_provisioning_allows_voice()) {
if (!control_plane_is_mini_program_provisioning_running() &&
control_plane_provisioning_retry_due()) {
(void)control_plane_enter_mini_program_reprovisioning("pending_ble_cleanup");
}
continue;
}
}
(void)control_plane_service_ptt_lifecycle();
if (!connected) {
@@ -2318,6 +2329,7 @@ static void control_plane_wifi_state_task(void *arg)
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE &&
!control_plane_get_ptt_active() &&
!control_plane_is_mini_program_provisioning_running() &&
control_plane_provisioning_retry_due() &&
(!has_saved_credentials ||
reconnect_failure_count >=
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_RECONNECT_FAILURES)) {
@@ -2612,7 +2624,7 @@ static void control_plane_daily_fortune_task(void *arg)
continue;
}
if (!control_plane_is_sta_connected()) {
if (!control_plane_is_sta_connected() || !control_plane_provisioning_allows_voice()) {
control_plane_fortune_delay_ms(CONTROL_PLANE_FORTUNE_CONNECT_POLL_MS);
continue;
}
@@ -2664,7 +2676,8 @@ static void control_plane_daily_fortune_task(void *arg)
}
control_plane_fortune_wait_for_multimodal_connected();
if (control_plane_runtime_stop_requested() || !control_plane_is_sta_connected()) {
if (control_plane_runtime_stop_requested() || !control_plane_is_sta_connected() ||
!control_plane_provisioning_allows_voice()) {
continue; /* loop head re-gates; refresh-due flags persist, so no fetch is lost */
}
now = time(NULL); /* refresh after the wait so the resolved date is current */
@@ -2836,7 +2849,7 @@ esp_err_t control_plane_start_runtime_workers(void)
return local_wake_err;
}
} else {
ESP_LOGI(TAG, "Local wake startup deferred for OTA HTTPS window");
ESP_LOGI(TAG, "Local wake startup deferred while OTA/provisioning reserves voice resources");
}
#if CONFIG_FORTUNE_ENABLE
+19 -2
View File
@@ -698,10 +698,27 @@ typedef enum {
DOMAIN_WIFI_PROVISIONING_STATUS_FAIL_CONNECT,
} domain_wifi_provisioning_status_t;
/** 启动固定 TalkingQ 小程序 BLE 配网协议。 / Start the TalkingQ BLE provisioning protocol. */
/* 配网启动结果由 domain 定义;控制层据此选择等待、退避或内存恢复,不能猜测错误码来源。 */
typedef enum {
DOMAIN_WIFI_PROVISIONING_START_READY = 0,
DOMAIN_WIFI_PROVISIONING_START_IDENTITY_REQUIRED,
DOMAIN_WIFI_PROVISIONING_START_INVALID_CONFIG,
DOMAIN_WIFI_PROVISIONING_START_NO_MEMORY,
DOMAIN_WIFI_PROVISIONING_START_TRANSIENT,
DOMAIN_WIFI_PROVISIONING_START_FAILED,
} domain_wifi_provisioning_start_status_t;
/** 只检查身份前置条件,无 BLE 初始化或资源停止副作用,可重复调用。 */
domain_wifi_provisioning_start_status_t domain_runtime_mini_program_provisioning_preflight(void);
/**
* 启动固定小程序 BLE 配网协议;out_status 必填,失败时返回明确原因。
* 同步完成启动,不在返回失败后后台重试;停止和释放仍使用下方有界 stop 接口。
*/
esp_err_t domain_runtime_mini_program_provisioning_start(
domain_wifi_provisioning_event_callback_t callback,
void *user_data);
void *user_data,
domain_wifi_provisioning_start_status_t *out_status);
esp_err_t domain_runtime_mini_program_provisioning_notify(
domain_wifi_provisioning_status_t status);
esp_err_t domain_runtime_mini_program_provisioning_stop(int timeout_ms);
@@ -340,10 +340,44 @@ static const char *domain_provisioning_status_text(
}
}
domain_wifi_provisioning_start_status_t domain_runtime_mini_program_provisioning_preflight(void)
{
char device_id[DOMAIN_PROVISIONING_ID_MAX_LEN] = {0};
char serial_number[DOMAIN_PROVISIONING_SERIAL_MAX_LEN] = {0};
domain_runtime_read_device_identity(device_id, sizeof(device_id),
serial_number, sizeof(serial_number));
return device_id[0] != '\0' && serial_number[0] != '\0'
? DOMAIN_WIFI_PROVISIONING_START_READY
: DOMAIN_WIFI_PROVISIONING_START_IDENTITY_REQUIRED;
}
static domain_wifi_provisioning_start_status_t domain_provisioning_classify_start_error(esp_err_t err)
{
switch (err) {
case ESP_OK:
return DOMAIN_WIFI_PROVISIONING_START_READY;
case ESP_ERR_NO_MEM:
return DOMAIN_WIFI_PROVISIONING_START_NO_MEMORY;
case ESP_ERR_TIMEOUT:
return DOMAIN_WIFI_PROVISIONING_START_TRANSIENT;
case ESP_ERR_INVALID_ARG:
case ESP_ERR_NOT_SUPPORTED:
return DOMAIN_WIFI_PROVISIONING_START_INVALID_CONFIG;
default:
/* 未知故障不假定可重试,更不能误判为身份缺失或内存耗尽。 */
return DOMAIN_WIFI_PROVISIONING_START_FAILED;
}
}
esp_err_t domain_runtime_mini_program_provisioning_start(
domain_wifi_provisioning_event_callback_t callback,
void *user_data)
void *user_data,
domain_wifi_provisioning_start_status_t *out_status)
{
if (out_status == NULL) {
return ESP_ERR_INVALID_ARG;
}
*out_status = DOMAIN_WIFI_PROVISIONING_START_INVALID_CONFIG;
if (callback == NULL) {
return ESP_ERR_INVALID_ARG;
}
@@ -356,6 +390,9 @@ esp_err_t domain_runtime_mini_program_provisioning_start(
device_info,
sizeof(device_info));
if (err != ESP_OK) {
*out_status = err == ESP_ERR_NOT_FOUND
? DOMAIN_WIFI_PROVISIONING_START_IDENTITY_REQUIRED
: domain_provisioning_classify_start_error(err);
return err;
}
@@ -378,6 +415,7 @@ esp_err_t domain_runtime_mini_program_provisioning_start(
};
err = platform_ble_gatt_server_start(&config);
if (err != ESP_OK) {
*out_status = domain_provisioning_classify_start_error(err);
portENTER_CRITICAL(&s_callback_lock);
s_event_callback = NULL;
s_event_user_data = NULL;
@@ -385,6 +423,7 @@ esp_err_t domain_runtime_mini_program_provisioning_start(
return err;
}
*out_status = DOMAIN_WIFI_PROVISIONING_START_READY;
ESP_LOGI(TAG, "Mini-program provisioning protocol ready");
return ESP_OK;
}