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.
|
* @brief 消费一次低内存 BLE 恢复启动请求。 / Consume a one-shot BLE recovery request.
|
||||||
*
|
*
|
||||||
* 请求借助 RTC no-init 内存跨软重启;本次启动读到 true 后会锁存,使联网流程跳过 STA 重连,
|
* 请求借助 RTC no-init 内存跨软重启;本次启动读到 true 后会锁存,使联网流程跳过 STA 重连,
|
||||||
* 在完整运行时任务启动前优先进入 BLE 配网。
|
* 在完整运行时任务启动前优先进入 BLE 配网。失败后保留“已尝试恢复”的记录,
|
||||||
|
* 身份/配置更新和再次消费请求均不会重新开放恢复重启资格。
|
||||||
*
|
*
|
||||||
* The request survives a software restart in RTC no-init memory. A true result
|
* 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
|
* 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(bool active);
|
||||||
void control_plane_set_ptt_active_from_local_wake(void);
|
void control_plane_set_ptt_active_from_local_wake(void);
|
||||||
esp_err_t control_plane_enter_mini_program_reprovisioning(const char *reason);
|
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 <string.h>
|
||||||
|
|
||||||
#include "control_plane_internal.h"
|
#include "control_plane_internal.h"
|
||||||
|
#include "control_plane_provisioning_policy.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
#include "esp_attr.h"
|
#include "esp_attr.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
@@ -64,6 +66,7 @@
|
|||||||
#define CONTROL_PLANE_PROVISIONING_TASK_PRIORITY 5
|
#define CONTROL_PLANE_PROVISIONING_TASK_PRIORITY 5
|
||||||
#define CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT BIT0
|
#define CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT BIT0
|
||||||
#define CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT BIT1
|
#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_POLL_MS 100
|
||||||
#define CONTROL_PLANE_PROVISIONING_WAIT_LOG_MS 10000
|
#define CONTROL_PLANE_PROVISIONING_WAIT_LOG_MS 10000
|
||||||
#define CONTROL_PLANE_PROVISIONING_NOTIFY_SETTLE_MS 750
|
#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 QueueHandle_t s_provisioning_queue;
|
||||||
static EventGroupHandle_t s_provisioning_events;
|
static EventGroupHandle_t s_provisioning_events;
|
||||||
static TaskHandle_t s_provisioning_task;
|
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 control_plane_take_mini_program_recovery_boot_request(void)
|
||||||
{
|
{
|
||||||
bool requested =
|
bool requested =
|
||||||
@@ -95,7 +157,11 @@ bool control_plane_take_mini_program_recovery_boot_request(void)
|
|||||||
~CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC;
|
~CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC;
|
||||||
s_provisioning_recovery_marker = 0U;
|
s_provisioning_recovery_marker = 0U;
|
||||||
s_provisioning_recovery_marker_inverse = 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) {
|
if (requested) {
|
||||||
ESP_LOGW(TAG, "Consumed one-shot low-memory BLE recovery boot request");
|
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(
|
static void control_plane_request_provisioning_recovery_restart(
|
||||||
|
const char *stage, esp_err_t error,
|
||||||
const domain_ota_memory_headroom_t *headroom)
|
const domain_ota_memory_headroom_t *headroom)
|
||||||
{
|
{
|
||||||
s_provisioning_recovery_marker = CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC;
|
s_provisioning_recovery_marker = CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC;
|
||||||
s_provisioning_recovery_marker_inverse =
|
s_provisioning_recovery_marker_inverse =
|
||||||
~CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC;
|
~CONTROL_PLANE_PROVISIONING_RECOVERY_MAGIC;
|
||||||
ESP_LOGW(TAG,
|
if (headroom != NULL) {
|
||||||
"Restarting into low-memory BLE recovery boot: free_internal=%u largest_internal=%u largest_dma=%u",
|
ESP_LOGW(TAG,
|
||||||
headroom != NULL ? (unsigned)headroom->free_internal : 0U,
|
"Restarting once for BLE memory recovery: stage=%s error=%s free_internal=%u largest_internal=%u largest_dma=%u",
|
||||||
headroom != NULL ? (unsigned)headroom->largest_internal : 0U,
|
stage, esp_err_to_name(error), (unsigned)headroom->free_internal,
|
||||||
headroom != NULL ? (unsigned)headroom->largest_dma : 0U);
|
(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));
|
vTaskDelay(pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_RESTART_DELAY_MS));
|
||||||
domain_runtime_restart();
|
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)
|
static esp_err_t control_plane_check_provisioning_memory_headroom(void)
|
||||||
{
|
{
|
||||||
/* BLE 初始化需要连续内部/DMA 块;总空闲堆达标但最大块不足时仍必须拒绝并走恢复启动。 */
|
/* 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",
|
"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_FREE_INTERNAL_BYTES,
|
||||||
(unsigned)CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_LARGEST_DMA_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;
|
return ESP_ERR_NO_MEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,6 +474,27 @@ static void control_plane_provisioning_worker(void *arg)
|
|||||||
{
|
{
|
||||||
/* 状态通知顺序也是小程序协议的一部分:connecting→got_ip→saving→ready,失败则给明确原因。 */
|
/* 状态通知顺序也是小程序协议的一部分:connecting→got_ip→saving→ready,失败则给明确原因。 */
|
||||||
(void)arg;
|
(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;
|
bool provisioning_succeeded = false;
|
||||||
TickType_t last_wait_log_tick = xTaskGetTickCount();
|
TickType_t last_wait_log_tick = xTaskGetTickCount();
|
||||||
TickType_t wait_log_ticks =
|
TickType_t wait_log_ticks =
|
||||||
@@ -433,15 +569,25 @@ static void control_plane_provisioning_worker(void *arg)
|
|||||||
|
|
||||||
portENTER_CRITICAL(&s_provisioning_lock);
|
portENTER_CRITICAL(&s_provisioning_lock);
|
||||||
s_provisioning_task = NULL;
|
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);
|
portEXIT_CRITICAL(&s_provisioning_lock);
|
||||||
(void)xEventGroupSetBits(s_provisioning_events,
|
(void)xEventGroupSetBits(s_provisioning_events,
|
||||||
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT);
|
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT);
|
||||||
|
|
||||||
if (provisioning_succeeded && stop_err == ESP_OK &&
|
if (provisioning_succeeded && stop_err == ESP_OK &&
|
||||||
s_provisioning_recovery_boot) {
|
control_plane_provisioning_is_recovery_boot()) {
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGI(TAG,
|
||||||
"BLE recovery provisioning completed; restarting into normal runtime");
|
"BLE recovery provisioning completed; restarting into normal runtime");
|
||||||
|
portENTER_CRITICAL(&s_provisioning_lock);
|
||||||
s_provisioning_recovery_boot = false;
|
s_provisioning_recovery_boot = false;
|
||||||
|
portEXIT_CRITICAL(&s_provisioning_lock);
|
||||||
vTaskDelay(pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_RESTART_DELAY_MS));
|
vTaskDelay(pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_RESTART_DELAY_MS));
|
||||||
domain_runtime_restart();
|
domain_runtime_restart();
|
||||||
} else if (provisioning_succeeded && stop_err == ESP_OK) {
|
} else if (provisioning_succeeded && stop_err == ESP_OK) {
|
||||||
@@ -461,11 +607,15 @@ static void control_plane_provisioning_worker(void *arg)
|
|||||||
vTaskDelete(NULL);
|
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
|
#if !CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE
|
||||||
|
*status = DOMAIN_WIFI_PROVISIONING_START_INVALID_CONFIG;
|
||||||
return ESP_ERR_NOT_SUPPORTED;
|
return ESP_ERR_NOT_SUPPORTED;
|
||||||
#else
|
#else
|
||||||
|
*stage = "resources";
|
||||||
|
*status = DOMAIN_WIFI_PROVISIONING_START_NO_MEMORY;
|
||||||
portENTER_CRITICAL(&s_provisioning_lock);
|
portENTER_CRITICAL(&s_provisioning_lock);
|
||||||
bool already_running = s_provisioning_task != NULL;
|
bool already_running = s_provisioning_task != NULL;
|
||||||
portEXIT_CRITICAL(&s_provisioning_lock);
|
portEXIT_CRITICAL(&s_provisioning_lock);
|
||||||
@@ -488,33 +638,74 @@ static esp_err_t control_plane_start_mini_program_provisioning(void)
|
|||||||
(void)xEventGroupClearBits(
|
(void)xEventGroupClearBits(
|
||||||
s_provisioning_events,
|
s_provisioning_events,
|
||||||
CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT |
|
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();
|
esp_err_t err = control_plane_check_provisioning_memory_headroom();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
|
*status = err == ESP_ERR_NO_MEM ? DOMAIN_WIFI_PROVISIONING_START_NO_MEMORY
|
||||||
|
: DOMAIN_WIFI_PROVISIONING_START_FAILED;
|
||||||
return err;
|
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(
|
err = domain_runtime_mini_program_provisioning_start(
|
||||||
control_plane_provisioning_event_callback,
|
control_plane_provisioning_event_callback,
|
||||||
NULL);
|
NULL, status);
|
||||||
if (err != ESP_OK) {
|
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;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*stage = "worker_create";
|
||||||
|
TaskHandle_t worker_task = NULL;
|
||||||
BaseType_t created = xTaskCreate(control_plane_provisioning_worker,
|
BaseType_t created = xTaskCreate(control_plane_provisioning_worker,
|
||||||
"mini_prog_wifi",
|
"mini_prog_wifi",
|
||||||
CONTROL_PLANE_PROVISIONING_TASK_STACK_BYTES,
|
CONTROL_PLANE_PROVISIONING_TASK_STACK_BYTES,
|
||||||
NULL,
|
NULL,
|
||||||
CONTROL_PLANE_PROVISIONING_TASK_PRIORITY,
|
CONTROL_PLANE_PROVISIONING_TASK_PRIORITY,
|
||||||
&s_provisioning_task);
|
&worker_task);
|
||||||
if (created != pdPASS) {
|
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");
|
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);
|
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;
|
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");
|
ESP_LOGI(TAG, "Mini-program Wi-Fi provisioning started");
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
#endif
|
#endif
|
||||||
@@ -556,42 +747,105 @@ esp_err_t control_plane_stop_mini_program_provisioning(int timeout_ms)
|
|||||||
: ESP_ERR_TIMEOUT;
|
: 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
|
#if !CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE
|
||||||
(void)reason;
|
(void)quiesce_runtime;
|
||||||
return ESP_ERR_NOT_SUPPORTED;
|
return ESP_ERR_NOT_SUPPORTED;
|
||||||
#else
|
#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;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "Entering mini-program BLE reprovisioning: reason=%s",
|
goto failed;
|
||||||
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();
|
quiesce_failed:
|
||||||
if (err == ESP_OK) {
|
/* 停止操作失败时不能继续释放其仍在使用的资源;超时可有界重试,其余故障等待诊断。 */
|
||||||
err = control_plane_start_mini_program_provisioning();
|
status = err == ESP_ERR_TIMEOUT ? DOMAIN_WIFI_PROVISIONING_START_TRANSIENT
|
||||||
}
|
: DOMAIN_WIFI_PROVISIONING_START_FAILED;
|
||||||
if (err != ESP_OK) {
|
failed:
|
||||||
control_plane_wifi_set_keep_awake(false);
|
control_plane_wifi_set_keep_awake(false);
|
||||||
if (!s_provisioning_recovery_boot) {
|
control_plane_provisioning_start_failed(stage, err, status);
|
||||||
control_plane_request_provisioning_recovery_restart(NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err;
|
return err;
|
||||||
#endif
|
#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,
|
esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
|
||||||
const char *seed_password,
|
const char *seed_password,
|
||||||
int connect_timeout_ms)
|
int connect_timeout_ms)
|
||||||
@@ -608,7 +862,7 @@ esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_provisioning_recovery_boot) {
|
if (control_plane_provisioning_is_recovery_boot()) {
|
||||||
ESP_LOGW(TAG,
|
ESP_LOGW(TAG,
|
||||||
"Low-memory recovery boot skips STA reconnect and enters BLE provisioning");
|
"Low-memory recovery boot skips STA reconnect and enters BLE provisioning");
|
||||||
} else if (domain_runtime_wifi_portal_has_saved_credentials()) {
|
} 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));
|
esp_err_to_name(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
control_plane_show_provisioning_ui();
|
err = control_plane_try_provisioning(false);
|
||||||
|
if (err == ESP_OK && control_plane_provisioning_is_recovery_boot()) {
|
||||||
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();
|
esp_err_t lcd_err = control_plane_start_lcd_render_worker();
|
||||||
if (lcd_err != ESP_OK && lcd_err != ESP_ERR_NOT_SUPPORTED) {
|
if (lcd_err != ESP_OK && lcd_err != ESP_ERR_NOT_SUPPORTED) {
|
||||||
ESP_LOGW(TAG,
|
ESP_LOGW(TAG,
|
||||||
@@ -636,11 +887,14 @@ esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
|
|||||||
}
|
}
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
control_plane_wifi_set_keep_awake(false);
|
control_plane_wifi_set_keep_awake(false);
|
||||||
if (s_provisioning_recovery_boot) {
|
if (control_plane_provisioning_is_recovery_boot()) {
|
||||||
ESP_LOGE(TAG,
|
ESP_LOGE(TAG,
|
||||||
"Low-memory BLE recovery boot failed; resume normal boot: %s",
|
"Low-memory BLE recovery boot failed; resume normal boot: %s",
|
||||||
esp_err_to_name(err));
|
esp_err_to_name(err));
|
||||||
|
/* 退出精简启动模式,但已尝试恢复的锁存保留,禁止同一故障再次重启。 */
|
||||||
|
portENTER_CRITICAL(&s_provisioning_lock);
|
||||||
s_provisioning_recovery_boot = false;
|
s_provisioning_recovery_boot = false;
|
||||||
|
portEXIT_CRITICAL(&s_provisioning_lock);
|
||||||
}
|
}
|
||||||
ESP_LOGE(TAG, "BLE provisioning unavailable; SoftAP fallback disabled: %s",
|
ESP_LOGE(TAG, "BLE provisioning unavailable; SoftAP fallback disabled: %s",
|
||||||
esp_err_to_name(err));
|
esp_err_to_name(err));
|
||||||
|
|||||||
@@ -485,6 +485,8 @@ static void handle_save_config(void)
|
|||||||
s_pending_serial_number[0] = '\0';
|
s_pending_serial_number[0] = '\0';
|
||||||
/* Force a fresh challenge/response against the new identity on next use. */
|
/* Force a fresh challenge/response against the new identity on next use. */
|
||||||
control_plane_multimodal_device_auth_invalidate(0);
|
control_plane_multimodal_device_auth_invalidate(0);
|
||||||
|
/* 仅发布配置更新;Wi-Fi worker 在自己的上下文恢复配网,不阻塞串口应答。 */
|
||||||
|
control_plane_provisioning_configuration_changed();
|
||||||
ESP_LOGI(TAG, "Factory identity committed");
|
ESP_LOGI(TAG, "Factory identity committed");
|
||||||
cli_write_ok();
|
cli_write_ok();
|
||||||
}
|
}
|
||||||
@@ -625,6 +627,10 @@ static void handle_command_line(char *line)
|
|||||||
cli_write_ok();
|
cli_write_ok();
|
||||||
} else if (strcmp(cmd, "save_config") == 0) {
|
} else if (strcmp(cmd, "save_config") == 0) {
|
||||||
handle_save_config();
|
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) {
|
} else if (strcmp(cmd, "get_wifi") == 0) {
|
||||||
handle_get_wifi();
|
handle_get_wifi();
|
||||||
} else if (strcmp(cmd, "set_wifi") == 0) {
|
} else if (strcmp(cmd, "set_wifi") == 0) {
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ bool control_plane_ota_voice_services_allowed(void)
|
|||||||
portENTER_CRITICAL(&s_ota_lock);
|
portENTER_CRITICAL(&s_ota_lock);
|
||||||
allowed = !s_ota_voice_services_reserved;
|
allowed = !s_ota_voice_services_reserved;
|
||||||
portEXIT_CRITICAL(&s_ota_lock);
|
portEXIT_CRITICAL(&s_ota_lock);
|
||||||
return allowed;
|
return allowed && control_plane_provisioning_allows_voice();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool control_plane_ota_business_interactions_allowed(void)
|
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_DOWNLOADING &&
|
||||||
s_ota_snapshot.state != CONTROL_PLANE_OTA_STATE_READY_TO_REBOOT;
|
s_ota_snapshot.state != CONTROL_PLANE_OTA_STATE_READY_TO_REBOOT;
|
||||||
portEXIT_CRITICAL(&s_ota_lock);
|
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,
|
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));
|
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()) {
|
if (!initial_window && !allow_active_teardown && !control_plane_ota_runtime_is_idle()) {
|
||||||
return ESP_ERR_INVALID_STATE;
|
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);
|
control_plane_memory_monitor_sample(now_tick, false);
|
||||||
#endif
|
#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();
|
(void)control_plane_service_ptt_lifecycle();
|
||||||
|
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
@@ -2318,6 +2329,7 @@ static void control_plane_wifi_state_task(void *arg)
|
|||||||
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE &&
|
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE &&
|
||||||
!control_plane_get_ptt_active() &&
|
!control_plane_get_ptt_active() &&
|
||||||
!control_plane_is_mini_program_provisioning_running() &&
|
!control_plane_is_mini_program_provisioning_running() &&
|
||||||
|
control_plane_provisioning_retry_due() &&
|
||||||
(!has_saved_credentials ||
|
(!has_saved_credentials ||
|
||||||
reconnect_failure_count >=
|
reconnect_failure_count >=
|
||||||
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_RECONNECT_FAILURES)) {
|
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_RECONNECT_FAILURES)) {
|
||||||
@@ -2612,7 +2624,7 @@ static void control_plane_daily_fortune_task(void *arg)
|
|||||||
continue;
|
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);
|
control_plane_fortune_delay_ms(CONTROL_PLANE_FORTUNE_CONNECT_POLL_MS);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2664,7 +2676,8 @@ static void control_plane_daily_fortune_task(void *arg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
control_plane_fortune_wait_for_multimodal_connected();
|
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 */
|
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 */
|
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;
|
return local_wake_err;
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
#if CONFIG_FORTUNE_ENABLE
|
||||||
|
|||||||
@@ -698,10 +698,27 @@ typedef enum {
|
|||||||
DOMAIN_WIFI_PROVISIONING_STATUS_FAIL_CONNECT,
|
DOMAIN_WIFI_PROVISIONING_STATUS_FAIL_CONNECT,
|
||||||
} domain_wifi_provisioning_status_t;
|
} 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(
|
esp_err_t domain_runtime_mini_program_provisioning_start(
|
||||||
domain_wifi_provisioning_event_callback_t callback,
|
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(
|
esp_err_t domain_runtime_mini_program_provisioning_notify(
|
||||||
domain_wifi_provisioning_status_t status);
|
domain_wifi_provisioning_status_t status);
|
||||||
esp_err_t domain_runtime_mini_program_provisioning_stop(int timeout_ms);
|
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(
|
esp_err_t domain_runtime_mini_program_provisioning_start(
|
||||||
domain_wifi_provisioning_event_callback_t callback,
|
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) {
|
if (callback == NULL) {
|
||||||
return ESP_ERR_INVALID_ARG;
|
return ESP_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
@@ -356,6 +390,9 @@ esp_err_t domain_runtime_mini_program_provisioning_start(
|
|||||||
device_info,
|
device_info,
|
||||||
sizeof(device_info));
|
sizeof(device_info));
|
||||||
if (err != ESP_OK) {
|
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;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,6 +415,7 @@ esp_err_t domain_runtime_mini_program_provisioning_start(
|
|||||||
};
|
};
|
||||||
err = platform_ble_gatt_server_start(&config);
|
err = platform_ble_gatt_server_start(&config);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
|
*out_status = domain_provisioning_classify_start_error(err);
|
||||||
portENTER_CRITICAL(&s_callback_lock);
|
portENTER_CRITICAL(&s_callback_lock);
|
||||||
s_event_callback = NULL;
|
s_event_callback = NULL;
|
||||||
s_event_user_data = NULL;
|
s_event_user_data = NULL;
|
||||||
@@ -385,6 +423,7 @@ esp_err_t domain_runtime_mini_program_provisioning_start(
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*out_status = DOMAIN_WIFI_PROVISIONING_START_READY;
|
||||||
ESP_LOGI(TAG, "Mini-program provisioning protocol ready");
|
ESP_LOGI(TAG, "Mini-program provisioning protocol ready");
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ ERROR <reason>
|
|||||||
| `set_device_id <value>` | 临时设置设备 ID |
|
| `set_device_id <value>` | 临时设置设备 ID |
|
||||||
| `set_serial_number <value>` | 临时设置序列号 |
|
| `set_serial_number <value>` | 临时设置序列号 |
|
||||||
| `save_config` | 提交并保存两个身份字段 |
|
| `save_config` | 提交并保存两个身份字段 |
|
||||||
|
| `retry_provisioning` | 配网故障修正后请求重新尝试;不重置内存恢复次数 |
|
||||||
| `get_auth_material` | 生成或加载设备密钥,返回公开材料 |
|
| `get_auth_material` | 生成或加载设备密钥,返回公开材料 |
|
||||||
| `auth_sign_challenge <id> <challenge>` | 使用设备私钥签署工厂提供的 Challenge |
|
| `auth_sign_challenge <id> <challenge>` | 使用设备私钥签署工厂提供的 Challenge |
|
||||||
| `auth_prepare_rotate <version>` | 生成新密钥并准备轮换授权签名 |
|
| `auth_prepare_rotate <version>` | 生成新密钥并准备轮换授权签名 |
|
||||||
@@ -178,6 +179,10 @@ get_device_info
|
|||||||
|
|
||||||
### 3.2 `set` 和 `save_config` 的区别
|
### 3.2 `set` 和 `save_config` 的区别
|
||||||
|
|
||||||
|
身份缺失时设备保持运行并等待写入,不再作为低内存故障重启。`save_config` 成功后
|
||||||
|
通知 Wi-Fi 协调器重新检查身份并恢复配网;串口处理函数不直接执行 BLE 初始化。
|
||||||
|
错误分类、有限退避及恢复次数见 [配网失败处理与内存恢复](../配网失败处理与内存恢复.zh-CN.md)。
|
||||||
|
|
||||||
`set_device_id` 和 `set_serial_number` 只将值暂存在 RAM:
|
`set_device_id` 和 `set_serial_number` 只将值暂存在 RAM:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# 配网失败处理与内存恢复
|
||||||
|
|
||||||
|
更新日期:2026-10-23。本文描述源码策略与主机验证,不代表新板实机验收。
|
||||||
|
|
||||||
|
## 故障背景
|
||||||
|
|
||||||
|
新板启动日志显示内部 RAM 空闲 135,167 B、最大 DMA 连续块 73,728 B,均高于
|
||||||
|
当前配网门槛 49,152 B / 12,288 B。设备身份缺失返回 `ESP_ERR_NOT_FOUND`,
|
||||||
|
发生在 BLE 初始化之前。旧控制层将所有重新配网错误送入低内存恢复重启,并将
|
||||||
|
空快照打印为三个零。恢复启动再次失败后清除恢复模式,又被运行时触发重启。
|
||||||
|
因此该样本不能证明 BLE 内存不足,也不能证明身份补齐后的 BLE 内存一定充足。
|
||||||
|
|
||||||
|
## 当前职责与流程
|
||||||
|
|
||||||
|
- `domain.h` 是配网语义及 `domain_wifi_provisioning_start_status_t` 的唯一声明点,
|
||||||
|
对应 AGENTS 所有权矩阵的“小程序 Wi-Fi 配网语义”一行。未增加新契约家族或改变所有权。
|
||||||
|
- `domain_runtime_mini_program_provisioning_preflight()` 只检查身份,无 BLE 初始化副作用。
|
||||||
|
`domain_runtime_mini_program_provisioning_start()` 同步启动,必须传入结果输出指针;
|
||||||
|
失败返回错误码及类型化原因,domain/platform 不后台重试。
|
||||||
|
- `control_plane_connectivity_runtime.c` 持有唯一配网协调策略,所有共享字段在
|
||||||
|
`s_provisioning_lock` 下访问;资源操作、通知和日志在锁外执行。
|
||||||
|
- 私有 `control_plane_provisioning_policy.h` 只实现等待、退避和恢复次数决策。
|
||||||
|
Wi-Fi worker 轮询协调器,不新增任务、定时器或上层状态镜像。
|
||||||
|
|
||||||
|
```text
|
||||||
|
尝试被允许 → 身份检查 → 有界暂停语音 → 停止 STA → 内存检查 → BLE 启动 → 配网 worker
|
||||||
|
↓ ↓ ↓
|
||||||
|
等待身份 按实际原因处理 仅内存错误允许一次恢复启动
|
||||||
|
```
|
||||||
|
|
||||||
|
| 原因 | 策略 | 如何继续 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 身份缺失 | 等待身份,不停现有音频,不自动重试、不重启 | 成功 `save_config` 后由 Wi-Fi worker 重新检查 |
|
||||||
|
| 无效参数、不支持的配置 | 停止自动尝试 | 修正后 `retry_provisioning` |
|
||||||
|
| 明确临时超时 | 首次失败后依次等待至少 1、3、10 秒,最多三次额外尝试 | 超限后等待显式重试 |
|
||||||
|
| 未分类启动错误 | 停止自动尝试,不猜测为内存故障 | 根据日志排查后显式重试 |
|
||||||
|
| 内存门槛不满足或明确分配失败 | 本轮最多一次恢复重启 | 恢复启动仍失败则保持运行,等待排查 |
|
||||||
|
| Wi-Fi 密码错误或连接超时 | 保持 BLE,通知小程序连接失败 | 小程序重新提交凭据 |
|
||||||
|
| BLE teardown 失败 | 暂停云语音/HTTPS,保留待释放义务 | 显式重试先有界完成旧实例 teardown,成功后才能重启 BLE |
|
||||||
|
| 配网被停止或取消 | 有界释放,不自动重新进入配网 | 显式重试 |
|
||||||
|
|
||||||
|
退避由 worker 轮询驱动,1/3/10 秒是最短间隔,不是精确唤醒时间。
|
||||||
|
只有明确 `ESP_ERR_TIMEOUT` 被归为临时失败;未知错误不自动循环。
|
||||||
|
内存采样 API 本身失败属于诊断失败,不触发内存恢复重启。
|
||||||
|
|
||||||
|
## 恢复次数与日志
|
||||||
|
|
||||||
|
`recovery_boot` 表示当前采用精简启动;`recovery_attempted` 表示该恢复链已用过重启。
|
||||||
|
RTC 一次性请求在恢复启动消费后锁存 attempted。恢复失败退出精简启动时,不清除
|
||||||
|
attempted;`save_config`、显式重试及再次消费请求也不重置它。
|
||||||
|
恢复配网成功并完成 BLE teardown 后,仍按原流程重启进入完整正常运行时;
|
||||||
|
这是成功后的模式切换,不是失败重试。普通启动配网成功直接恢复运行时,无需重启。
|
||||||
|
|
||||||
|
启动失败日志包含 `stage`、`error`、`reason`、`retry_ms`、`recovery_attempted` 和
|
||||||
|
`voice_paused`。内存日志使用实际采样;采样失败明确显示 unavailable / unknown,
|
||||||
|
不能把缺少快照打印为内存零值。
|
||||||
|
|
||||||
|
## 等待期间的资源与产测命令
|
||||||
|
|
||||||
|
重新配网不再设置全局运行时停止标志,因此 Wi-Fi 协调、屏幕、电池、按键与
|
||||||
|
产测串口继续工作。身份检查在暂停资源之前完成。若已暂停语音后才失败,设备
|
||||||
|
明确进入离线等待/退避,云语音保持暂停,避免与 BLE 重试争抢内存;OTA、财运
|
||||||
|
HTTPS 和语音启动入口遵守此门禁。BLE 成功 teardown 或其他 STA 恢复路径解除门禁。
|
||||||
|
若旧 BLE 尚未成功释放,STA 即使已连接也不能直接恢复云服务。
|
||||||
|
|
||||||
|
```text
|
||||||
|
get_device_info
|
||||||
|
set_device_id <后台分配的实际设备ID>
|
||||||
|
set_serial_number <实际序列号>
|
||||||
|
save_config
|
||||||
|
get_device_info
|
||||||
|
```
|
||||||
|
|
||||||
|
`set_*` 只暂存 RAM,`save_config` 成功持久化后才发布配置通知;失败不发布。
|
||||||
|
CLI 不直接停资源或初始化 BLE。新命令 `retry_provisioning` 只请求协调器重新尝试,
|
||||||
|
不写入设备身份、不删除 Wi-Fi 凭据,也不重置内存恢复次数。Wi-Fi 已正常连接且
|
||||||
|
没有待清理 BLE 时继续正常联网,不强制中断连接进入配网。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/test_provisioning_recovery.py
|
||||||
|
```
|
||||||
|
|
||||||
|
该主机测试提取并编译实际领域启动、控制层协调、恢复请求消费、配网 worker 和
|
||||||
|
产测保存函数,使用 RTOS/BLE/存储故障桩及 ASan/UBSan。覆盖身份等待、失败与成功保存、
|
||||||
|
错误分类、退避与计时回绕、内存门槛边界和分配失败、恢复失败不再重启、未知快照日志、
|
||||||
|
停止超时、Wi-Fi 失败后继续接收凭据、成功恢复运行时、teardown 失败、取消和启动门超时。
|
||||||
|
|
||||||
|
固件使用 ESP-IDF 5.5.4 构建。实机仍需验证空身份持续运行、身份写入后的 BLE 广播、
|
||||||
|
错误/正确 Wi-Fi 凭据、连接后的 BLE 释放与语音恢复,并记录 BLE 真正初始化前后的
|
||||||
|
内部 RAM、最大 DMA 连续块及并行负载。主机故障注入不替代这些设备证据。
|
||||||
@@ -0,0 +1,378 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Fault-inject the production provisioning coordinator and domain start functions.
|
||||||
|
|
||||||
|
Uses host stubs for RTOS/BLE/storage only; policy, startup, error handling,
|
||||||
|
recovery-boot consumption and factory save are compiled from production code.
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
CP = (ROOT / 'components/control_plane/src/runtime/control_plane_connectivity_runtime.c').read_text()
|
||||||
|
DOMAIN = (ROOT / 'components/domain/src/runtime/domain_mini_program_provisioning.c').read_text()
|
||||||
|
CLI = (ROOT / 'components/control_plane/src/runtime/control_plane_factory_cli.c').read_text()
|
||||||
|
FACADE = (ROOT / 'components/domain/include/domain.h').read_text()
|
||||||
|
|
||||||
|
|
||||||
|
def extract(source, name):
|
||||||
|
match = re.search(r'^(?:static )?(?:esp_err_t|bool|void|uint32_t|const char \*|domain_wifi_provisioning_start_status_t)\s*' + name + r'\([^;]*?\)\s*\{', source, re.M)
|
||||||
|
if not match:
|
||||||
|
raise ValueError(name)
|
||||||
|
pos, depth = match.end(), 1
|
||||||
|
while depth:
|
||||||
|
depth += (source[pos] == '{') - (source[pos] == '}')
|
||||||
|
pos += 1
|
||||||
|
return source[match.start():pos]
|
||||||
|
|
||||||
|
|
||||||
|
EVENTS = re.search(r'typedef enum \{\s*DOMAIN_WIFI_PROVISIONING_EVENT_CREDENTIALS.*?\} domain_wifi_provisioning_event_type_t;', FACADE, re.S).group()
|
||||||
|
NOTIFY = re.search(r'typedef enum \{\s*DOMAIN_WIFI_PROVISIONING_STATUS_WIFI_CONNECTING.*?\} domain_wifi_provisioning_status_t;', FACADE, re.S).group()
|
||||||
|
STATUS = re.search(r'typedef enum \{\s*DOMAIN_WIFI_PROVISIONING_START_READY.*?\} domain_wifi_provisioning_start_status_t;', FACADE, re.S).group()
|
||||||
|
CONSTANTS = '\n'.join(re.findall(r'^#define CONTROL_PLANE_PROVISIONING_.*$', CP, re.M))
|
||||||
|
STUBS = r'''
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "control_plane_provisioning_policy.h"
|
||||||
|
typedef int esp_err_t;
|
||||||
|
typedef void *TaskHandle_t;
|
||||||
|
typedef void *QueueHandle_t;
|
||||||
|
typedef void *EventGroupHandle_t;
|
||||||
|
typedef int BaseType_t;
|
||||||
|
typedef struct {int type; struct {char ssid[33], password[65];} credentials;} domain_wifi_provisioning_event_t;
|
||||||
|
typedef uint32_t TickType_t;
|
||||||
|
typedef uint32_t EventBits_t;
|
||||||
|
typedef void (*domain_wifi_provisioning_event_callback_t)(const void *, void *);
|
||||||
|
typedef struct {uint32_t free_internal, largest_internal, largest_dma;} domain_ota_memory_headroom_t;
|
||||||
|
#define ESP_OK 0
|
||||||
|
#define ESP_ERR_INVALID_ARG 1
|
||||||
|
#define ESP_ERR_NO_MEM 2
|
||||||
|
#define ESP_ERR_TIMEOUT 3
|
||||||
|
#define ESP_ERR_NOT_FOUND 4
|
||||||
|
#define ESP_ERR_NOT_SUPPORTED 5
|
||||||
|
#define ESP_ERR_INVALID_STATE 6
|
||||||
|
#define ESP_FAIL 7
|
||||||
|
#define BIT0 1
|
||||||
|
#define BIT1 2
|
||||||
|
#define BIT2 4
|
||||||
|
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE 1
|
||||||
|
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_FREE_INTERNAL_BYTES 49152
|
||||||
|
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_MIN_LARGEST_DMA_BYTES 12288
|
||||||
|
#define DOMAIN_PROVISIONING_ID_MAX_LEN 64
|
||||||
|
#define DOMAIN_PROVISIONING_SERIAL_MAX_LEN 64
|
||||||
|
#define DOMAIN_PROVISIONING_INFO_MAX_LEN 512
|
||||||
|
#define PLATFORM_BLE_GATT_NAME_MAX_LEN 32
|
||||||
|
#define FACTORY_CLI_DEVICE_ID_MAX_LEN 64
|
||||||
|
#define FACTORY_CLI_SERIAL_MAX_LEN 64
|
||||||
|
#define pdMS_TO_TICKS(ms) (ms)
|
||||||
|
#define pdPASS 1
|
||||||
|
#define pdTRUE 1
|
||||||
|
#define pdFALSE 0
|
||||||
|
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_CONNECT_TIMEOUT_MS 30000
|
||||||
|
static int lock_depth;
|
||||||
|
#define portENTER_CRITICAL(lock) do {assert(lock_depth++ == 0);} while(0)
|
||||||
|
#define portEXIT_CRITICAL(lock) do {assert(--lock_depth == 0);} while(0)
|
||||||
|
static char logs[32768];
|
||||||
|
static void record(const char *fmt, ...) {
|
||||||
|
va_list ap; va_start(ap, fmt);
|
||||||
|
vsnprintf(logs+strlen(logs), sizeof(logs)-strlen(logs), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
#define ESP_LOGI(tag, ...) record(__VA_ARGS__)
|
||||||
|
#define ESP_LOGW(tag, ...) record(__VA_ARGS__)
|
||||||
|
#define ESP_LOGE(tag, ...) record(__VA_ARGS__)
|
||||||
|
static const char *esp_err_to_name(int err) {return err==ESP_ERR_NOT_FOUND ? "NOT_FOUND" : "error";}
|
||||||
|
static uint64_t now_ms;
|
||||||
|
static int restarts, memory_calls, quiesce_calls, audio_deinits, ble_calls, ble_stops, notifications;
|
||||||
|
static int ble_error, stop_error, memory_error_after, create_error, queue_error, local_wake_error, sta_error, save_error;
|
||||||
|
static int cli_ok, cli_errors, config_notifications;
|
||||||
|
static bool identity_ready, keep_awake;
|
||||||
|
static unsigned event_bits;
|
||||||
|
static int runtime_starts, task_deletes, connect_calls, connect_failures, wifi_failed_notifications;
|
||||||
|
static domain_ota_memory_headroom_t memory;
|
||||||
|
static cp_provisioning_policy_t s_provisioning_policy;
|
||||||
|
static bool s_provisioning_recovery_boot;
|
||||||
|
static uint32_t s_provisioning_recovery_marker, s_provisioning_recovery_marker_inverse;
|
||||||
|
static TaskHandle_t s_provisioning_task;
|
||||||
|
static QueueHandle_t s_provisioning_queue;
|
||||||
|
static EventGroupHandle_t s_provisioning_events;
|
||||||
|
static domain_wifi_provisioning_event_callback_t s_event_callback;
|
||||||
|
static void *s_event_user_data;
|
||||||
|
static char s_pending_device_id[64], s_pending_serial_number[64];
|
||||||
|
static uint64_t esp_timer_get_time(void) {return now_ms*1000;}
|
||||||
|
static void vTaskDelay(unsigned ms) {now_ms+=ms;}
|
||||||
|
static void domain_runtime_restart(void) {++restarts;}
|
||||||
|
static esp_err_t domain_runtime_ota_get_memory_headroom(domain_ota_memory_headroom_t *out) {
|
||||||
|
assert(!lock_depth); ++memory_calls;
|
||||||
|
if (memory_error_after && memory_calls >= memory_error_after) return ESP_FAIL;
|
||||||
|
*out=memory; return ESP_OK;
|
||||||
|
}
|
||||||
|
static void domain_runtime_read_device_identity(char *id, size_t id_len, char *sn, size_t sn_len) {
|
||||||
|
snprintf(id,id_len,"%s",identity_ready?"device":"");
|
||||||
|
snprintf(sn,sn_len,"%s",identity_ready?"serial":"");
|
||||||
|
}
|
||||||
|
static void control_plane_request_wifi_reconnect_now(void) {++config_notifications;}
|
||||||
|
static void control_plane_wifi_set_keep_awake(bool value) {keep_awake=value;}
|
||||||
|
static void control_plane_set_ptt_active(bool active) {assert(!active);}
|
||||||
|
static int wake_restarts;
|
||||||
|
static bool control_plane_ota_voice_services_allowed(void) {return !s_provisioning_policy.runtime_quiesced;}
|
||||||
|
static esp_err_t control_plane_local_wake_start(void) {++wake_restarts; return ESP_OK;}
|
||||||
|
static esp_err_t control_plane_local_wake_stop(int ms) {assert(ms==2000); ++quiesce_calls; return local_wake_error;}
|
||||||
|
static esp_err_t control_plane_stop_multimodal_client(void) {return ESP_OK;}
|
||||||
|
static esp_err_t domain_runtime_multimodal_audio_stop_downstream_playback(void) {return ESP_OK;}
|
||||||
|
static void domain_runtime_multimodal_audio_deinit(void) {++audio_deinits;}
|
||||||
|
static void control_plane_show_provisioning_ui(void) {}
|
||||||
|
static esp_err_t domain_runtime_wifi_portal_stop_sta(void) {return sta_error;}
|
||||||
|
static void *xQueueCreate(unsigned len, unsigned size) {return queue_error ? NULL : (void*)1;}
|
||||||
|
static void *xEventGroupCreate(void) {return (void*)2;}
|
||||||
|
static void xQueueReset(void *queue) {assert(queue);}
|
||||||
|
static void xEventGroupClearBits(void *events, unsigned bits) {assert(events);}
|
||||||
|
static void control_plane_provisioning_event_callback(const void *event, void *user) {}
|
||||||
|
static uint32_t xTaskGetTickCount(void) {return (uint32_t)now_ms;}
|
||||||
|
static unsigned xEventGroupGetBits(void *events) {return event_bits;}
|
||||||
|
static unsigned xEventGroupWaitBits(void *events, unsigned bits, int clear, int all, unsigned timeout) {return event_bits;}
|
||||||
|
static int xQueueReceive(void *queue, domain_wifi_provisioning_event_t *event, unsigned timeout) {
|
||||||
|
event->type=DOMAIN_WIFI_PROVISIONING_EVENT_CREDENTIALS; return pdTRUE;
|
||||||
|
}
|
||||||
|
static void vTaskDelete(void *task) {++task_deletes;}
|
||||||
|
static void control_plane_provisioning_notify(domain_wifi_provisioning_status_t status) {
|
||||||
|
if (status==DOMAIN_WIFI_PROVISIONING_STATUS_FAIL_CONNECT || status==DOMAIN_WIFI_PROVISIONING_STATUS_FAIL_TIMEOUT)
|
||||||
|
++wifi_failed_notifications;
|
||||||
|
}
|
||||||
|
static esp_err_t domain_runtime_wifi_portal_connect_sta_with_credentials(const char *ssid, const char *password, int timeout) {
|
||||||
|
++connect_calls; return connect_calls<=connect_failures ? ESP_FAIL : ESP_OK;
|
||||||
|
}
|
||||||
|
static void control_plane_play_wifi_success_prompt(void) {}
|
||||||
|
static void control_plane_finish_provisioning_ui(void) {}
|
||||||
|
static esp_err_t control_plane_start_runtime_workers(void) {
|
||||||
|
assert(!s_provisioning_policy.runtime_quiesced); ++runtime_starts; return ESP_OK;
|
||||||
|
}
|
||||||
|
static int xTaskCreate(void (*fn)(void*), const char *name, unsigned stack, void *arg, unsigned priority, void **task) {
|
||||||
|
if (create_error) return 0;
|
||||||
|
*task=(void*)3; return pdPASS;
|
||||||
|
}
|
||||||
|
static void xEventGroupSetBits(void *events, unsigned bits) {
|
||||||
|
assert(events==s_provisioning_events);
|
||||||
|
event_bits |= bits;
|
||||||
|
if (bits==BIT2) {
|
||||||
|
assert(s_provisioning_task && s_provisioning_policy.phase==CP_PROVISIONING_ACTIVE);
|
||||||
|
++notifications;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void domain_provisioning_reset_write_buffer(void) {}
|
||||||
|
static void domain_provisioning_handle_write(void) {}
|
||||||
|
static const char *s_service_uuid="service", *s_wifi_write_uuid="write", *s_status_uuid="status", *s_device_info_uuid="info";
|
||||||
|
typedef struct {
|
||||||
|
const char *advertised_name, *service_uuid, *write_uuid, *notify_uuid, *read_notify_uuid;
|
||||||
|
const uint8_t *read_value; size_t read_value_len;
|
||||||
|
void (*on_write)(void); void *user_data;
|
||||||
|
} platform_ble_gatt_server_config_t;
|
||||||
|
static esp_err_t platform_ble_gatt_server_start(const platform_ble_gatt_server_config_t *cfg) {++ble_calls; return ble_error;}
|
||||||
|
static esp_err_t domain_runtime_mini_program_provisioning_stop(int timeout) {assert(timeout==2000); ++ble_stops; return stop_error;}
|
||||||
|
static esp_err_t domain_provisioning_build_device_info(char *name, size_t n, char *info, size_t i) {
|
||||||
|
name[0]=0; snprintf(info,i,"{}"); return identity_ready?ESP_OK:ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
static void control_plane_register_wifi_behavior_hooks(void) {}
|
||||||
|
static esp_err_t domain_runtime_wifi_portal_init_defaults(const char *ssid, const char *password) {return ESP_OK;}
|
||||||
|
static bool domain_runtime_wifi_portal_has_saved_credentials(void) {return false;}
|
||||||
|
static int control_plane_normalize_connect_timeout_ms(int ms, int unused) {return ms;}
|
||||||
|
static esp_err_t control_plane_connect_sta_with_feedback(int ms, bool feedback) {return ESP_FAIL;}
|
||||||
|
static esp_err_t control_plane_start_lcd_render_worker(void) {return ESP_OK;}
|
||||||
|
static bool is_text_empty(const char *s) {return !s || !s[0];}
|
||||||
|
#define strlcpy test_strlcpy
|
||||||
|
static size_t test_strlcpy(char *dst, const char *src, size_t len) {size_t n=strlen(src); snprintf(dst,len,"%s",src); return n;}
|
||||||
|
static void trim_text(char *s) {}
|
||||||
|
static esp_err_t domain_runtime_write_device_identity(const char *id, const char *sn) {
|
||||||
|
if (save_error) return save_error;
|
||||||
|
identity_ready=id[0] && sn[0]; return ESP_OK;
|
||||||
|
}
|
||||||
|
static void control_plane_multimodal_device_auth_invalidate(unsigned unused) {}
|
||||||
|
static void cli_write_error(const char *error) {++cli_errors;}
|
||||||
|
static void cli_write_ok(void) {++cli_ok;}
|
||||||
|
'''
|
||||||
|
|
||||||
|
TESTS = r'''
|
||||||
|
static void reset(void) {
|
||||||
|
memset(&s_provisioning_policy,0,sizeof(s_provisioning_policy));
|
||||||
|
s_provisioning_recovery_boot=false;
|
||||||
|
s_provisioning_recovery_marker=s_provisioning_recovery_marker_inverse=0;
|
||||||
|
s_provisioning_task=s_provisioning_queue=s_provisioning_events=NULL;
|
||||||
|
now_ms=0; restarts=memory_calls=quiesce_calls=audio_deinits=ble_calls=ble_stops=notifications=0;
|
||||||
|
ble_error=stop_error=memory_error_after=create_error=queue_error=local_wake_error=sta_error=save_error=0;
|
||||||
|
cli_ok=cli_errors=config_notifications=0;
|
||||||
|
wake_restarts=0; event_bits=0; runtime_starts=task_deletes=connect_calls=connect_failures=wifi_failed_notifications=0;
|
||||||
|
identity_ready=false; keep_awake=false;
|
||||||
|
s_pending_device_id[0]=s_pending_serial_number[0]=logs[0]=0;
|
||||||
|
memory=(domain_ota_memory_headroom_t){135167,73728,73728};
|
||||||
|
}
|
||||||
|
int main(void) {
|
||||||
|
reset();
|
||||||
|
assert(control_plane_bootstrap_connectivity("","",20000)==ESP_ERR_NOT_FOUND);
|
||||||
|
assert(s_provisioning_policy.phase==CP_PROVISIONING_WAIT_IDENTITY);
|
||||||
|
for (unsigned i=0;i<10000;i++) {now_ms+=1000; assert(!control_plane_provisioning_retry_due());}
|
||||||
|
assert(!restarts && !ble_calls && !quiesce_calls && !audio_deinits);
|
||||||
|
assert(control_plane_provisioning_allows_voice());
|
||||||
|
assert(strstr(logs,"identity_required"));
|
||||||
|
/* Failed save cannot wake policy; successful save only notifies, BLE runs later. */
|
||||||
|
snprintf(s_pending_device_id,64,"new-id"); snprintf(s_pending_serial_number,64,"new-sn");
|
||||||
|
save_error=ESP_FAIL; handle_save_config();
|
||||||
|
assert(cli_errors==1 && !config_notifications && !control_plane_provisioning_retry_due());
|
||||||
|
save_error=0; handle_save_config();
|
||||||
|
assert(cli_ok==1 && config_notifications==1 && !ble_calls);
|
||||||
|
assert(control_plane_provisioning_retry_due());
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("identity_saved")==ESP_OK);
|
||||||
|
assert(ble_calls==1 && quiesce_calls==1 && notifications==1 && !restarts);
|
||||||
|
assert(!control_plane_provisioning_allows_voice() && !control_plane_provisioning_retry_due());
|
||||||
|
|
||||||
|
/* Actual timeout classification and exact bounded backoff, including uint32 wrap. */
|
||||||
|
reset(); identity_ready=true; ble_error=ESP_ERR_TIMEOUT; now_ms=UINT32_MAX-500;
|
||||||
|
const unsigned delays[]={1000,3000,10000};
|
||||||
|
for (unsigned i=0;i<3;i++) {
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("timeout")==ESP_ERR_TIMEOUT);
|
||||||
|
assert(s_provisioning_policy.phase==CP_PROVISIONING_BACKOFF);
|
||||||
|
assert(s_provisioning_policy.retry_delay_ms==delays[i]);
|
||||||
|
assert(!control_plane_provisioning_retry_due()); now_ms+=delays[i]-1;
|
||||||
|
assert(!control_plane_provisioning_retry_due()); ++now_ms;
|
||||||
|
assert(control_plane_provisioning_retry_due());
|
||||||
|
}
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("timeout")==ESP_ERR_TIMEOUT);
|
||||||
|
assert(s_provisioning_policy.phase==CP_PROVISIONING_BLOCKED && !restarts && ble_calls==4);
|
||||||
|
now_ms+=100000; assert(!control_plane_provisioning_retry_due());
|
||||||
|
control_plane_provisioning_configuration_changed(); ble_error=ESP_OK;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("manual")==ESP_OK);
|
||||||
|
|
||||||
|
const int permanent[]={ESP_ERR_INVALID_ARG,ESP_ERR_NOT_SUPPORTED,ESP_ERR_NOT_FOUND,ESP_FAIL};
|
||||||
|
for (unsigned i=0;i<4;i++) {
|
||||||
|
reset(); identity_ready=true; ble_error=permanent[i];
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("permanent")==permanent[i]);
|
||||||
|
assert(s_provisioning_policy.phase==CP_PROVISIONING_BLOCKED);
|
||||||
|
assert(!restarts && !control_plane_provisioning_retry_due());
|
||||||
|
assert(!s_event_callback && !s_event_user_data);
|
||||||
|
}
|
||||||
|
/* Memory threshold and actual allocator failure both permit only ONE recovery. */
|
||||||
|
for (unsigned scenario=0;scenario<5;scenario++) {
|
||||||
|
reset(); identity_ready=true;
|
||||||
|
if (scenario==0) memory.free_internal=49151;
|
||||||
|
if (scenario==1) memory.largest_dma=12287;
|
||||||
|
if (scenario==2) ble_error=ESP_ERR_NO_MEM;
|
||||||
|
if (scenario==3) create_error=1;
|
||||||
|
if (scenario==4) queue_error=1;
|
||||||
|
assert(control_plane_bootstrap_connectivity("","",20000)==ESP_ERR_NO_MEM);
|
||||||
|
assert(restarts==1 && s_provisioning_policy.recovery_attempted);
|
||||||
|
assert(control_plane_take_mini_program_recovery_boot_request());
|
||||||
|
assert(!control_plane_take_mini_program_recovery_boot_request());
|
||||||
|
assert(s_provisioning_policy.recovery_attempted);
|
||||||
|
/* Simulate fresh globals after RTC consumption, retaining the attempt latch. */
|
||||||
|
s_provisioning_policy.phase=CP_PROVISIONING_IDLE;
|
||||||
|
assert(control_plane_bootstrap_connectivity("","",20000)==ESP_ERR_NO_MEM);
|
||||||
|
assert(!s_provisioning_recovery_boot && s_provisioning_policy.recovery_attempted);
|
||||||
|
assert(restarts==1 && !control_plane_provisioning_retry_due());
|
||||||
|
for (unsigned retry=0;retry<20;retry++) {
|
||||||
|
control_plane_provisioning_configuration_changed();
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("explicit")==ESP_ERR_NO_MEM);
|
||||||
|
assert(restarts==1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reset(); identity_ready=true; memory.free_internal=49152; memory.largest_dma=12288;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("boundary")==ESP_OK && !restarts);
|
||||||
|
reset(); identity_ready=true; memory_error_after=1;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("measurement_error")==ESP_FAIL);
|
||||||
|
assert(!restarts && !ble_calls && !control_plane_provisioning_retry_due());
|
||||||
|
/* Snapshot failure after a confirmed BLE allocation failure prints unknown, never fake zeros. */
|
||||||
|
reset(); identity_ready=true; ble_error=ESP_ERR_NO_MEM; memory_error_after=2;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("allocation")==ESP_ERR_NO_MEM);
|
||||||
|
assert(restarts==1 && strstr(logs,"memory=unknown") && !strstr(logs,"free_internal=0"));
|
||||||
|
reset(); identity_ready=true; local_wake_error=ESP_ERR_TIMEOUT;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("stop_timeout")==ESP_ERR_TIMEOUT);
|
||||||
|
assert(!audio_deinits && !ble_calls && !restarts);
|
||||||
|
reset(); identity_ready=true; create_error=1; stop_error=ESP_ERR_TIMEOUT;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("cleanup_timeout")==ESP_ERR_TIMEOUT);
|
||||||
|
assert(ble_stops==1 && !restarts && s_provisioning_policy.phase==CP_PROVISIONING_BLOCKED);
|
||||||
|
/* Run the real worker: bad Wi-Fi credentials retain BLE; success restores runtime. */
|
||||||
|
reset(); identity_ready=true; connect_failures=1;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("normal")==ESP_OK);
|
||||||
|
control_plane_provisioning_worker(NULL);
|
||||||
|
assert(connect_calls==2 && wifi_failed_notifications==1 && ble_calls==1 && ble_stops==1);
|
||||||
|
assert(runtime_starts==1 && !restarts && !s_provisioning_task && task_deletes==1);
|
||||||
|
assert(control_plane_provisioning_allows_voice() && !keep_awake);
|
||||||
|
reset(); identity_ready=true; stop_error=ESP_ERR_TIMEOUT;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("teardown")==ESP_OK);
|
||||||
|
control_plane_provisioning_worker(NULL);
|
||||||
|
assert(!runtime_starts && !restarts && s_provisioning_policy.phase==CP_PROVISIONING_BLOCKED);
|
||||||
|
assert(!control_plane_provisioning_allows_voice() && s_provisioning_policy.cleanup_required);
|
||||||
|
control_plane_provisioning_sta_connected();
|
||||||
|
assert(!control_plane_provisioning_allows_voice() && !wake_restarts);
|
||||||
|
control_plane_provisioning_configuration_changed();
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("cleanup_still_fails")==ESP_ERR_TIMEOUT);
|
||||||
|
assert(ble_calls==1 && !restarts && !control_plane_provisioning_retry_due());
|
||||||
|
stop_error=ESP_OK;
|
||||||
|
control_plane_provisioning_configuration_changed();
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("cleanup_then_retry")==ESP_OK);
|
||||||
|
assert(ble_stops==3 && ble_calls==2);
|
||||||
|
reset(); identity_ready=true;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("cancel")==ESP_OK);
|
||||||
|
event_bits |= BIT0;
|
||||||
|
control_plane_provisioning_worker(NULL);
|
||||||
|
assert(!runtime_starts && !restarts && !control_plane_provisioning_retry_due());
|
||||||
|
reset(); identity_ready=true;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("gate_timeout")==ESP_OK);
|
||||||
|
event_bits=0;
|
||||||
|
control_plane_provisioning_worker(NULL);
|
||||||
|
assert(!s_provisioning_task && !restarts && ble_stops==1 && !control_plane_provisioning_retry_due());
|
||||||
|
reset(); identity_ready=true; ble_error=ESP_ERR_TIMEOUT;
|
||||||
|
assert(control_plane_enter_mini_program_reprovisioning("direct_sta_recovery")==ESP_ERR_TIMEOUT);
|
||||||
|
control_plane_provisioning_sta_connected();
|
||||||
|
assert(control_plane_provisioning_allows_voice() && wake_restarts==1);
|
||||||
|
/* One failure-recovery reboot, then the existing success-mode-switch reboot. */
|
||||||
|
reset(); identity_ready=true; memory.free_internal=1;
|
||||||
|
assert(control_plane_bootstrap_connectivity("","",20000)==ESP_ERR_NO_MEM && restarts==1);
|
||||||
|
assert(control_plane_take_mini_program_recovery_boot_request());
|
||||||
|
s_provisioning_policy.phase=CP_PROVISIONING_IDLE; memory.free_internal=135167;
|
||||||
|
assert(control_plane_bootstrap_connectivity("","",20000)==ESP_OK);
|
||||||
|
control_plane_provisioning_worker(NULL);
|
||||||
|
assert(restarts==2 && !runtime_starts && !s_provisioning_task);
|
||||||
|
assert(control_plane_provisioning_allows_voice() && !s_provisioning_recovery_marker);
|
||||||
|
puts("PASS: worker Wi-Fi failure/success, teardown failure, cancellation and bounded start gate");
|
||||||
|
puts("PASS: identity wait/save resume, production BLE error classification, bounded retry/wrap, memory thresholds/allocation failures, recovery-boot loop prevention, cleanup and truthful diagnostics");
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
domain_functions = ['domain_runtime_mini_program_provisioning_preflight',
|
||||||
|
'domain_provisioning_classify_start_error',
|
||||||
|
'domain_runtime_mini_program_provisioning_start']
|
||||||
|
cp_functions = ['control_plane_provisioning_now_ms', 'control_plane_provisioning_is_recovery_boot',
|
||||||
|
'control_plane_provisioning_retry_due', 'control_plane_provisioning_configuration_changed',
|
||||||
|
'control_plane_provisioning_allows_voice', 'control_plane_provisioning_sta_connected',
|
||||||
|
'control_plane_take_mini_program_recovery_boot_request',
|
||||||
|
'control_plane_request_provisioning_recovery_restart', 'control_plane_provisioning_failure_name',
|
||||||
|
'control_plane_provisioning_start_failed', 'control_plane_check_provisioning_memory_headroom',
|
||||||
|
'control_plane_provisioning_stop_requested', 'control_plane_provisioning_worker',
|
||||||
|
'control_plane_start_provisioning_transport', 'control_plane_is_mini_program_provisioning_running',
|
||||||
|
'control_plane_try_provisioning', 'control_plane_enter_mini_program_reprovisioning',
|
||||||
|
'control_plane_bootstrap_connectivity']
|
||||||
|
with tempfile.TemporaryDirectory(prefix='provisioning-recovery-') as tmp:
|
||||||
|
tmp = Path(tmp)
|
||||||
|
(tmp / 'domain.h').write_text('#pragma once\n'+STATUS+'\n'+EVENTS+'\n'+NOTIFY+'\n')
|
||||||
|
source = STUBS+'\n'+CONSTANTS+'\n'
|
||||||
|
source += '\n'.join(extract(DOMAIN, name) for name in domain_functions)+'\n'
|
||||||
|
source += 'bool control_plane_is_mini_program_provisioning_running(void);\n'
|
||||||
|
source += '\n'.join(extract(CP, name) for name in cp_functions)+'\n'
|
||||||
|
source += extract(CLI, 'handle_save_config')+'\n'+TESTS
|
||||||
|
(tmp / 'test.c').write_text(source)
|
||||||
|
subprocess.run(['clang', '-std=c11', '-Wall', '-Wextra', '-Werror',
|
||||||
|
'-Wno-unused-parameter', '-Wno-unused-function',
|
||||||
|
'-fsanitize=address,undefined', '-I',str(tmp),
|
||||||
|
'-I',str(ROOT/'components/control_plane/internal'),
|
||||||
|
str(tmp/'test.c'), '-o',str(tmp/'test')],check=True)
|
||||||
|
subprocess.run([str(tmp/'test')],check=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user