fix(control_plane): bound provisioning recovery and prevent reboot loops
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user