feat(provisioning): add mini-program BLE Wi-Fi setup

This commit is contained in:
chenjintao
2026-08-13 15:44:18 +08:00
parent 5711c26a9a
commit 841fe32c49
21 changed files with 1776 additions and 41 deletions
@@ -6,9 +6,13 @@
#include "control_plane.h"
#include <string.h>
#include "control_plane_internal.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "sdkconfig.h"
@@ -24,13 +28,33 @@
#define CONFIG_TQ_BOOT_SOUND_FILE ""
#endif
#ifndef CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE 0
#endif
#ifndef CONFIG_TQ_MINI_PROGRAM_PROVISIONING_CONNECT_TIMEOUT_MS
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_CONNECT_TIMEOUT_MS 30000
#endif
#define CONTROL_PLANE_WIFI_SUCCESS_PROMPT_FILE "/littlefs/audio_prompts/network_connect_success.opus"
#define CONTROL_PLANE_STA_CONNECT_MAX_ATTEMPTS 3
#define CONTROL_PLANE_STA_RETRY_BASE_DELAY_MS 300
#define CONTROL_PLANE_PROVISIONING_QUEUE_LENGTH 1
#define CONTROL_PLANE_PROVISIONING_TASK_STACK_WORDS 4096
#define CONTROL_PLANE_PROVISIONING_TASK_PRIORITY 5
#define CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT BIT0
#define CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT BIT1
#define CONTROL_PLANE_PROVISIONING_POLL_MS 100
#define CONTROL_PLANE_PROVISIONING_NOTIFY_SETTLE_MS 750
#define CONTROL_PLANE_PROVISIONING_TRANSPORT_STOP_MS 2000
static const char *TAG = "control_plane_rt";
static bool s_wifi_success_prompt_played;
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 int elapsed_ms_since(TickType_t start_tick)
{
@@ -159,11 +183,6 @@ static esp_err_t control_plane_connect_sta_with_feedback(int timeout_ms,
return ESP_OK;
}
static bool control_plane_wifi_connect_error_keeps_credentials(esp_err_t err)
{
return domain_runtime_wifi_connect_error_keeps_credentials(err);
}
static void control_plane_register_wifi_behavior_hooks(void)
{
domain_wifi_behavior_hooks_t hooks = {
@@ -174,10 +193,261 @@ static void control_plane_register_wifi_behavior_hooks(void)
domain_runtime_wifi_portal_set_behavior_hooks(&hooks);
}
static void control_plane_provisioning_notify(domain_wifi_provisioning_status_t status)
{
esp_err_t err = domain_runtime_mini_program_provisioning_notify(status);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Mini-program provisioning status notify failed: %s",
esp_err_to_name(err));
}
}
static void control_plane_finish_provisioning_ui(void)
{
const domain_expression_request_t idle_request = {
.id = DOMAIN_EXPRESSION_IDLE,
.trigger = DOMAIN_EXPRESSION_TRIGGER_WIFI_RECONNECTED,
};
esp_err_t err = domain_runtime_request_expression(&idle_request);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG,
"Restore idle expression after provisioning failed: %s",
esp_err_to_name(err));
}
}
static void control_plane_provisioning_event_callback(
const domain_wifi_provisioning_event_t *event,
void *user_data)
{
(void)user_data;
if (event == NULL) {
return;
}
QueueHandle_t queue;
portENTER_CRITICAL(&s_provisioning_lock);
queue = s_provisioning_queue;
portEXIT_CRITICAL(&s_provisioning_lock);
if (queue == NULL || xQueueOverwrite(queue, event) != pdTRUE) {
ESP_LOGW(TAG, "Drop mini-program provisioning event: queue unavailable");
}
}
static bool control_plane_provisioning_stop_requested(void)
{
return s_provisioning_events != NULL &&
(xEventGroupGetBits(s_provisioning_events) &
CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT) != 0;
}
static void control_plane_provisioning_worker(void *arg)
{
(void)arg;
bool provisioning_succeeded = false;
while (!control_plane_provisioning_stop_requested()) {
domain_wifi_provisioning_event_t event = {0};
if (xQueueReceive(s_provisioning_queue,
&event,
pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_POLL_MS)) != pdTRUE) {
continue;
}
if (event.type == DOMAIN_WIFI_PROVISIONING_EVENT_BAD_JSON) {
control_plane_provisioning_notify(
DOMAIN_WIFI_PROVISIONING_STATUS_FAIL_BAD_JSON);
continue;
}
if (event.type == DOMAIN_WIFI_PROVISIONING_EVENT_EMPTY_SSID) {
control_plane_provisioning_notify(
DOMAIN_WIFI_PROVISIONING_STATUS_FAIL_EMPTY_SSID);
continue;
}
if (event.type != DOMAIN_WIFI_PROVISIONING_EVENT_CREDENTIALS) {
continue;
}
control_plane_provisioning_notify(
DOMAIN_WIFI_PROVISIONING_STATUS_SAVING_WIFI);
control_plane_provisioning_notify(
DOMAIN_WIFI_PROVISIONING_STATUS_WIFI_CONNECTING);
esp_err_t err = domain_runtime_wifi_portal_connect_sta_with_credentials(
event.credentials.ssid,
event.credentials.password,
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_CONNECT_TIMEOUT_MS);
memset(&event, 0, sizeof(event));
if (err != ESP_OK) {
control_plane_wifi_set_keep_awake(true);
control_plane_provisioning_notify(
err == ESP_ERR_TIMEOUT
? DOMAIN_WIFI_PROVISIONING_STATUS_FAIL_TIMEOUT
: DOMAIN_WIFI_PROVISIONING_STATUS_FAIL_CONNECT);
continue;
}
control_plane_play_wifi_success_prompt();
control_plane_provisioning_notify(
DOMAIN_WIFI_PROVISIONING_STATUS_WIFI_GOT_IP);
control_plane_provisioning_notify(
DOMAIN_WIFI_PROVISIONING_STATUS_READY);
vTaskDelay(pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_NOTIFY_SETTLE_MS));
provisioning_succeeded = true;
break;
}
esp_err_t stop_err = domain_runtime_mini_program_provisioning_stop(
CONTROL_PLANE_PROVISIONING_TRANSPORT_STOP_MS);
if (stop_err != ESP_OK) {
ESP_LOGE(TAG, "Mini-program BLE teardown failed: %s",
esp_err_to_name(stop_err));
}
control_plane_wifi_set_keep_awake(false);
portENTER_CRITICAL(&s_provisioning_lock);
s_provisioning_task = NULL;
portEXIT_CRITICAL(&s_provisioning_lock);
(void)xEventGroupSetBits(s_provisioning_events,
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT);
if (provisioning_succeeded && stop_err == ESP_OK) {
control_plane_finish_provisioning_ui();
ESP_LOGI(TAG,
"BLE provisioning completed; starting deferred runtime workers");
esp_err_t runtime_err = control_plane_start_runtime_workers();
if (runtime_err != ESP_OK) {
ESP_LOGE(TAG,
"Deferred runtime worker start failed: %s",
esp_err_to_name(runtime_err));
}
} else if (provisioning_succeeded) {
ESP_LOGE(TAG,
"Runtime workers remain deferred because BLE teardown did not complete");
}
vTaskDelete(NULL);
}
static esp_err_t control_plane_start_mini_program_provisioning(void)
{
#if !CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE
return ESP_ERR_NOT_SUPPORTED;
#else
portENTER_CRITICAL(&s_provisioning_lock);
bool already_running = s_provisioning_task != NULL;
portEXIT_CRITICAL(&s_provisioning_lock);
if (already_running) {
return ESP_OK;
}
if (s_provisioning_queue == NULL) {
s_provisioning_queue = xQueueCreate(
CONTROL_PLANE_PROVISIONING_QUEUE_LENGTH,
sizeof(domain_wifi_provisioning_event_t));
}
if (s_provisioning_events == NULL) {
s_provisioning_events = xEventGroupCreate();
}
if (s_provisioning_queue == NULL || s_provisioning_events == NULL) {
return ESP_ERR_NO_MEM;
}
(void)xQueueReset(s_provisioning_queue);
(void)xEventGroupClearBits(
s_provisioning_events,
CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT |
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT);
BaseType_t created = xTaskCreate(control_plane_provisioning_worker,
"mini_prog_wifi",
CONTROL_PLANE_PROVISIONING_TASK_STACK_WORDS,
NULL,
CONTROL_PLANE_PROVISIONING_TASK_PRIORITY,
&s_provisioning_task);
if (created != pdPASS) {
s_provisioning_task = NULL;
return ESP_ERR_NO_MEM;
}
esp_err_t err = domain_runtime_mini_program_provisioning_start(
control_plane_provisioning_event_callback,
NULL);
if (err != ESP_OK) {
(void)xEventGroupSetBits(s_provisioning_events,
CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT);
(void)xEventGroupWaitBits(
s_provisioning_events,
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT,
pdFALSE,
pdTRUE,
pdMS_TO_TICKS(CONTROL_PLANE_PROVISIONING_TRANSPORT_STOP_MS));
return err;
}
ESP_LOGI(TAG, "Mini-program Wi-Fi provisioning started");
return ESP_OK;
#endif
}
bool control_plane_is_mini_program_provisioning_running(void)
{
bool running;
portENTER_CRITICAL(&s_provisioning_lock);
running = s_provisioning_task != NULL;
portEXIT_CRITICAL(&s_provisioning_lock);
return running;
}
esp_err_t control_plane_stop_mini_program_provisioning(int timeout_ms)
{
if (timeout_ms < 0) {
return ESP_ERR_INVALID_ARG;
}
if (s_provisioning_events == NULL ||
!control_plane_is_mini_program_provisioning_running()) {
return domain_runtime_mini_program_provisioning_stop(timeout_ms);
}
(void)xEventGroupSetBits(s_provisioning_events,
CONTROL_PLANE_PROVISIONING_STOP_REQUESTED_BIT);
esp_err_t transport_err = domain_runtime_mini_program_provisioning_stop(0);
if (timeout_ms == 0) {
return transport_err;
}
EventBits_t bits = xEventGroupWaitBits(
s_provisioning_events,
CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT,
pdFALSE,
pdTRUE,
pdMS_TO_TICKS(timeout_ms));
return (bits & CONTROL_PLANE_PROVISIONING_WORKER_DONE_BIT) != 0
? transport_err
: ESP_ERR_TIMEOUT;
}
esp_err_t control_plane_enter_mini_program_reprovisioning(const char *reason)
{
#if !CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE
(void)reason;
return ESP_ERR_NOT_SUPPORTED;
#else
if (control_plane_is_mini_program_provisioning_running()) {
return ESP_OK;
}
ESP_LOGW(TAG, "Entering mini-program BLE reprovisioning: reason=%s",
reason != NULL ? reason : "unspecified");
control_plane_wifi_set_keep_awake(true);
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);
}
return err;
#endif
}
esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
const char *seed_password,
int connect_timeout_ms,
const char *config_ap_ssid_prefix)
int connect_timeout_ms)
{
ESP_LOGI(TAG,
"Boot Wi-Fi config SSID=%s timeout_ms=%d",
@@ -196,24 +466,27 @@ esp_err_t control_plane_bootstrap_connectivity(const char *seed_ssid,
if (err == ESP_OK) {
return ESP_OK;
}
if (control_plane_wifi_connect_error_keeps_credentials(err)) {
ESP_LOGW(TAG,
"STA connect failed, keep credentials for runtime retry: %s",
esp_err_to_name(err));
} else {
ESP_LOGW(TAG, "STA connect failed, clear credentials and fallback: %s",
esp_err_to_name(err));
(void)domain_runtime_wifi_portal_clear_saved_credentials();
}
ESP_LOGW(TAG,
"Boot STA connect failed; preserve credentials and enter BLE provisioning immediately: %s",
esp_err_to_name(err));
}
const domain_expression_request_t connecting_request = {
.id = DOMAIN_EXPRESSION_WIFI_CONNECTING,
.trigger = DOMAIN_EXPRESSION_TRIGGER_WIFI_BOOTSTRAP,
};
esp_err_t display_err = domain_runtime_request_expression(&connecting_request);
if (display_err != ESP_OK && display_err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "Wi-Fi connecting QR display unavailable: %s",
esp_err_to_name(display_err));
}
control_plane_wifi_set_keep_awake(true);
err = domain_runtime_wifi_portal_start_config_ap(
(config_ap_ssid_prefix != NULL && config_ap_ssid_prefix[0] != '\0')
? config_ap_ssid_prefix
: "TQ-CAT");
err = control_plane_start_mini_program_provisioning();
if (err != ESP_OK) {
control_plane_wifi_set_keep_awake(false);
ESP_LOGE(TAG, "BLE provisioning unavailable; SoftAP fallback disabled: %s",
esp_err_to_name(err));
}
return err;
}
@@ -110,6 +110,14 @@
#define CONFIG_TQ_NETWORK_AUTO_REASSOCIATION_ENABLE 0
#endif
#ifndef CONFIG_TQ_MINI_PROGRAM_PROVISIONING_RECONNECT_FAILURES
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_RECONNECT_FAILURES 2
#endif
#ifndef CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE
#define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE 0
#endif
#if CONFIG_TQ_NETWORK_AUTO_REASSOCIATION_ENABLE
#define CONTROL_PLANE_NETWORK_FAILURE_REASSOC_THRESHOLD 1U
#endif
@@ -1708,6 +1716,7 @@ static void control_plane_wifi_state_task(void *arg)
TickType_t last_ws_start_attempt_tick = 0;
uint32_t reconnect_delay_ms = CONTROL_PLANE_WIFI_RECONNECT_BASE_DELAY_MS;
bool reconnect_attempted = false;
uint32_t reconnect_failure_count = 0;
uint8_t ptt_wifi_foreground_attempts = 0;
#if CONFIG_TQ_MEMORY_MONITOR_ENABLE
@@ -1715,10 +1724,15 @@ static void control_plane_wifi_state_task(void *arg)
#endif
if (!was_connected) {
ESP_LOGI(TAG, "Wi-Fi STA unavailable at runtime start; requesting disconnected expression");
control_plane_request_wifi_state_expression(DOMAIN_EXPRESSION_WIFI_DISCONNECTED,
DOMAIN_EXPRESSION_TRIGGER_WIFI_DISCONNECTED,
"Wi-Fi disconnected");
bool provisioning_running =
control_plane_is_mini_program_provisioning_running();
ESP_LOGI(TAG, "Wi-Fi STA unavailable at runtime start; requesting offline expression");
control_plane_request_wifi_state_expression(
provisioning_running ? DOMAIN_EXPRESSION_WIFI_CONNECTING
: DOMAIN_EXPRESSION_WIFI_DISCONNECTED,
provisioning_running ? DOMAIN_EXPRESSION_TRIGGER_WIFI_BOOTSTRAP
: DOMAIN_EXPRESSION_TRIGGER_WIFI_DISCONNECTED,
provisioning_running ? "Wi-Fi provisioning" : "Wi-Fi disconnected");
disconnected_expression_active = true;
last_disconnected_request_tick = xTaskGetTickCount();
} else {
@@ -1774,9 +1788,14 @@ static void control_plane_wifi_state_task(void *arg)
if (was_connected || !disconnected_expression_active) {
ESP_LOGI(TAG, "Wi-Fi STA disconnected; requesting disconnected expression");
}
control_plane_request_wifi_state_expression(DOMAIN_EXPRESSION_WIFI_DISCONNECTED,
DOMAIN_EXPRESSION_TRIGGER_WIFI_DISCONNECTED,
"Wi-Fi disconnected");
bool provisioning_running =
control_plane_is_mini_program_provisioning_running();
control_plane_request_wifi_state_expression(
provisioning_running ? DOMAIN_EXPRESSION_WIFI_CONNECTING
: DOMAIN_EXPRESSION_WIFI_DISCONNECTED,
provisioning_running ? DOMAIN_EXPRESSION_TRIGGER_WIFI_BOOTSTRAP
: DOMAIN_EXPRESSION_TRIGGER_WIFI_DISCONNECTED,
provisioning_running ? "Wi-Fi provisioning" : "Wi-Fi disconnected");
disconnected_expression_active = true;
last_disconnected_request_tick = now_tick;
}
@@ -1791,7 +1810,11 @@ static void control_plane_wifi_state_task(void *arg)
bool reconnect_due = ptt_foreground ||
!reconnect_attempted ||
(now_tick - last_reconnect_attempt_tick) >= reconnect_delay_ticks;
if (reconnect_due && domain_runtime_wifi_portal_has_saved_credentials()) {
bool provisioning_running =
control_plane_is_mini_program_provisioning_running();
bool has_saved_credentials =
domain_runtime_wifi_portal_has_saved_credentials();
if (reconnect_due && !provisioning_running && has_saved_credentials) {
esp_err_t reconnect_err = control_plane_attempt_wifi_reconnect();
now_tick = xTaskGetTickCount();
reconnect_attempted = true;
@@ -1802,11 +1825,37 @@ static void control_plane_wifi_state_task(void *arg)
if (reconnect_err == ESP_OK) {
connected = true;
reconnect_delay_ms = CONTROL_PLANE_WIFI_RECONNECT_BASE_DELAY_MS;
reconnect_failure_count = 0;
ptt_wifi_foreground_attempts = 0;
} else {
if (reconnect_failure_count < UINT32_MAX) {
reconnect_failure_count++;
}
reconnect_delay_ms = control_plane_next_wifi_reconnect_delay_ms(reconnect_delay_ms);
}
}
if (!connected &&
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE &&
!control_plane_get_ptt_active() &&
!control_plane_is_mini_program_provisioning_running() &&
(!has_saved_credentials ||
reconnect_failure_count >=
CONFIG_TQ_MINI_PROGRAM_PROVISIONING_RECONNECT_FAILURES)) {
esp_err_t provisioning_err =
control_plane_enter_mini_program_reprovisioning(
has_saved_credentials
? "wifi_reconnect_failed"
: "wifi_credentials_missing");
if (provisioning_err == ESP_OK) {
reconnect_failure_count = 0;
reconnect_delay_ms = CONTROL_PLANE_WIFI_RECONNECT_BASE_DELAY_MS;
reconnect_attempted = false;
disconnected_expression_active = false;
} else {
ESP_LOGW(TAG, "Mini-program reprovisioning failed: %s",
esp_err_to_name(provisioning_err));
}
}
if (!control_plane_get_ptt_active()) {
ptt_wifi_foreground_attempts = 0;
}
@@ -1840,6 +1889,7 @@ static void control_plane_wifi_state_task(void *arg)
disconnect_started_tick = 0;
reconnect_delay_ms = CONTROL_PLANE_WIFI_RECONNECT_BASE_DELAY_MS;
reconnect_attempted = false;
reconnect_failure_count = 0;
}
if (connected && (!was_connected || disconnected_expression_active)) {
@@ -1859,6 +1909,7 @@ static void control_plane_wifi_state_task(void *arg)
} else if (connected) {
disconnect_pending = false;
disconnect_started_tick = 0;
reconnect_failure_count = 0;
}
if (connected) {
@@ -2341,6 +2392,8 @@ void control_plane_stop_event_observer_runtime(void)
void control_plane_stop_runtime_workers(void)
{
control_plane_set_runtime_stop_requested(true);
(void)control_plane_stop_mini_program_provisioning(
CONTROL_PLANE_TASK_STOP_WAIT_MS);
control_plane_stop_user_config_server();
(void)control_plane_local_wake_stop(CONFIG_TQ_LOCAL_WAKE_STOP_WAIT_MS);