Baji_Rtc_Toy/main/boards/common/wifi_board.cc
Rdzleo bffd31645e feat(provisioning): BLE 配网完整修复 (跳过 EAF 资源 + EAF 最小化 + 音效播放)
修复 4 个配网模式核心问题, 让 Rtc_AIavatar 分支 (含火山 RTC SDK + 软件 AEC + 完整业务)
能像 adaptation_dzbjImg_shar 一样正常配网, 同时显示居中提示文字.

============ 问题与修复 ============

### 问题 1: 配网模式 BLE 广播 ADV_DATA malloc 失败 (手机搜不到设备)

  日志:
    E (3731) BLE_INIT: Malloc failed
    E (3731) BT_HCI: CC evt: op=0x2008 (HCI_BLE_WRITE_ADV_DATA), status=0x7
    I (3731) BluetoothProvisioning:  广播启动成功  (假成功, 广播数据空)

  根因:
    Rtc_AIavatar 比 adaptation_dzbjImg_shar 多 ~50-80 KB DRAM 业务 .bss
      (软件 AEC + HTTPS 完整状态机 + dialog watchdog + 完整 RTC 状态),
    + 火山 RTC SDK 静态库 .bss ~30-50 KB (g_cnxMgr 14.6KB, ack$14 12.6KB 等),
    配网模式时 BLE Bluedroid stack 抢不到广播数据 malloc 所需的 ~10KB DRAM.

  修复 (前次 commit 已做): sdkconfig 关闭 BLE 5.0 6 个特性 (项目实际只用 4.2 legacy),
    省 ~15 KB controller DRAM, 广播数据 malloc 成功.

### 问题 2: 配网模式下 LCD 绘制跟 WiFi/BLE 初始化抢 DRAM 导致 reboot

  日志:
    E (1200) wifi:Expected to init 10 rx buffer, actual is 1
    E (1220) BluetoothProvisioning: WiFi初始化失败: ESP_ERR_NO_MEM
    assert failed: vQueueDelete queue.c:2355 (pxQueue) ← BLE GATT fixed_queue_new 失败 → 反向清理 NULL 队列

  排查路径 (失败方案记录):
    - esp_lcd_panel_draw_bitmap 一次画 360x360 (253KB): SPI queue 满, 下半屏未画 + DRAM 抢 WiFi
    - 分块画 (60 行/块) + vTaskDelay 块间: SPI driver 内部 queue 持续保留 DRAM, 仍然抢
    - 强制 codec output_only=false 完整 duplex: 多 15KB DRAM, BLE BTU_StartUp malloc 失败 reboot
    - CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=y: 引入 BTU bt_workqueue 分配失败 → vQueueDelete NULL → assert

  修复 (本次 commit): EAF 最小化初始化
    movecall_moji_esp32s3.cc 配网模式调用新增的 ai_chat_screen_init_provisioning(),
    跳过 8 张 EAF 资源加载 (省 4.32 MB PSRAM + ~10KB DRAM), 跳过数字人 anim,
    只启用 gfx_emote renderer + 单个 gfx_label, flush buffer 启动时预分配 (~30KB DRAM 一次到位),
    跟 BLE 初始化不再有动态分配冲突. 跟 adaptation_dzbjImg_shar 用 LVGL 显示 GIF 同思路,
    用 EAF 替代 LVGL 避免引入 50-80KB LVGL .bss.

### 问题 3: 配网模式音效不播放

  根因:
    ResetWifiConfiguration 由 BOOT 按键 OnClick 调用, 跑在 esp_timer task 上下文,
    vTaskDelay(4000ms) 实测只等了 1.1 秒就被唤醒, 音效没播完就 esp_restart.

  修复 (前次 commit 已做): 派发到独立 task 跑 PlaySound + vTaskDelay + esp_restart,
    独立 task 中 vTaskDelay 正常工作, 等 4 秒确保 解码 + DMA + 功放尾音完整.

### 问题 4: 配网时屏幕黑屏 (UX 不友好)

  实施: ai_chat_screen_init_provisioning("请使用APP\n蓝牙配网~")
    LCD 黑底白字居中显示提示文字, 用户感知"配网中".
    label height=64 (恰好包 2 行 + 余白), GFX_ALIGN_CENTER 上下左右居中.

============ 文件改动 ============

  main/application.cc:
    Application 构造时显式注释: 不能在配网模式置 background_task_=nullptr
    (OnAudioOutput 无判空, 会 std::mutex::lock 异常 abort).

  main/boards/common/wifi_board.cc:
    ResetWifiConfiguration 派发独立 task 跑 PlaySound + 4s delay + esp_restart,
    EnterWifiConfigMode BLE 启动后早 return (StartBleProvisioning 内部已 Alert 音效).

  main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc:
    AI 对话模式分支: 配网时调 ai_chat_screen_init_provisioning() 显示文字,
    正常模式调 ai_chat_screen_init() 显示数字人.

  main/dzbj/ai_chat_ui.h:
    新增 ai_chat_screen_init_provisioning(const char* hint_text) 声明.

  main/dzbj/ai_chat_ui_eaf.c:
    新增 ai_chat_screen_init_provisioning() 实现, EAF 最小化路径:
      gfx_emote_init + gfx_disp_add + 单 label 显示文字, 跳过 EAF 资源/anim/背景图.

============ 测试结果 (设备实测) ============
  - 按 BOOT 触发配网: 听到完整配网音效 (P3_LALA_WIFICONFIG 约 1 秒)
  - 设备重启 → 配网模式启动 → LCD 显示"请使用APP\n蓝牙配网~" 居中
  - 手机能搜到 Airhub_d0:cf:13:03:bb:f2 → 能连接 → 配网完成
  - 配网完成重启 → 正常模式数字人 + RTC 对话功能正常

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:42:13 +08:00

638 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file wifi_board.cc
* @brief WiFi板级管理模块实现文件
*
* 本文件实现了WiFi板级管理的相关功能包括WiFi连接管理、
* BLE蓝牙配网流程控制、网络状态监控等核心功能。
* 提供完整的网络连接解决方案实现。
*/
#include "wifi_board.h"
#include "display.h"
#include "application.h"
#include "system_info.h"
#include "font_awesome_symbols.h"
#include "settings.h"
#include "assets/lang_config.h"
#include "bluetooth_provisioning.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_netif_sntp.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_http.h>
#include <esp_mqtt.h>
#include <esp_udp.h>
#include <tcp_transport.h>
#include <tls_transport.h>
#include <web_socket.h>
#include <esp_log.h>
#include <wifi_station.h>
#include <wifi_configuration_ap.h>
#include <ssid_manager.h>
static const char *TAG = "WifiBoard"; ///< 日志标签用于标识WiFi板级模块的日志输出
/**
* @brief WiFi板级管理构造函数
*
* 初始化WiFi板级管理对象读取NVS存储中的配置参数。
* 检查是否设置了强制AP模式标志如果设置则重置为0。
*/
WifiBoard::WifiBoard() {
// 读取NVS存储中的强制AP模式标志
// 注意:这里只读取不清零,清零在 StartNetwork() 中进行
// 确保后续 NeedsProvisioning() 静态方法能正确读到 force_ap=1
Settings settings("wifi", true);
wifi_config_mode_ = settings.GetInt("force_ap") == 1;
if (wifi_config_mode_) {
ESP_LOGI(TAG, "force_ap is set to 1, will clear in StartNetwork()");
}
}
/**
* @brief 获取板级类型标识
* @return std::string 返回"wifi"字符串标识当前为WiFi板级
*/
std::string WifiBoard::GetBoardType() {
return "wifi";
}
bool WifiBoard::NeedsProvisioning() {
// 检查 force_ap 标志
Settings settings("wifi", true);
if (settings.GetInt("force_ap") == 1) {
return true;
}
// 检查是否有保存的 WiFi 凭据
auto& ssid_manager = SsidManager::GetInstance();
auto ssid_list = ssid_manager.GetSsidList();
return ssid_list.empty();
}
/**
* @brief 进入WiFi配置模式
*
* 启动BLE蓝牙配网流程等待用户通过手机APP配置WiFi信息。
* 如果BLE配网启动失败会持续重试直到成功。
* 不再使用传统的WiFi AP配网模式。
*/
void WifiBoard::EnterWifiConfigMode() {
ESP_LOGI(TAG, "🔵 进入配网模式 - 使用BLE蓝牙配网");
// 使用 BLE 蓝牙配网
bool success = StartBleProvisioning();
ESP_LOGI(TAG, "🔍 BLE配网启动结果: %s", success ? "成功" : "失败");
if (success) {
ESP_LOGI(TAG, "✅ BLE配网启动成功等待手机连接");
return;
}
ESP_LOGW(TAG, "⚠️ BLE配网启动失败将持续重试");
// 持续重试
while (true) {
vTaskDelay(pdMS_TO_TICKS(5000));
ESP_LOGI(TAG, "🔄 重试启动BLE配网...");
if (StartBleProvisioning()) {
ESP_LOGI(TAG, "✅ BLE配网重试成功等待手机连接");
return;
}
ESP_LOGW(TAG, "❌ BLE配网重试失败继续重试...");
}
// 以下代码保留但不会执行用于将来可能重新启用WiFi AP配网
//ESP_LOGI(TAG, "📶 启动WiFi AP配网模式播放配网提示音此代码已被禁用");
auto& application = Application::GetInstance();
application.SetDeviceState(kDeviceStateWifiConfiguring);
auto& wifi_ap = WifiConfigurationAp::GetInstance();
wifi_ap.SetLanguage(Lang::CODE);
wifi_ap.SetSsidPrefix("Airhub");
wifi_ap.Start(); // 初始化AP模式射频
// 显示 WiFi 配置 AP 的 SSID 和 Web 服务器 URL
std::string hint = Lang::Strings::CONNECT_TO_HOTSPOT;
hint += wifi_ap.GetSsid();
hint += Lang::Strings::ACCESS_VIA_BROWSER;
hint += wifi_ap.GetWebServerUrl();
hint += "\n\n";
// 播报配置 WiFi 的提示
// application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "", Lang::Sounds::P3_WIFICONFIG); 原有蜡笔小新音色播报
if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "happy", Lang::Sounds::P3_KAKA_WIFICONFIG);
}
else if(strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0){
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "happy", Lang::Sounds::P3_LALA_WIFICONFIG);
}
// Wait forever until reset after configuration
while (true) {
int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
int min_free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
ESP_LOGI(TAG, "Free internal: %u minimal internal: %u", free_sram, min_free_sram);
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
/**
* @brief 启动网络连接
*
* 根据配置启动WiFi连接或BLE配网流程。
* 如果设置了配网模式或没有WiFi凭据则启动BLE配网
* 否则尝试连接已保存的WiFi网络。
*/
void WifiBoard::StartNetwork() {
// 在所有 NeedsProvisioning() 调用完成后,清除 force_ap 标志
// 防止设备在配网过程中崩溃后无限循环进入配网模式
if (wifi_config_mode_) {
Settings settings("wifi", true);
settings.SetInt("force_ap", 0);
ESP_LOGI(TAG, "force_ap cleared to 0");
}
// 用户可以在启动时按BOOT按钮进入WiFi配置模式
// 开机按BOOT进入配网模式
if (wifi_config_mode_) {
ESP_LOGI(TAG, "🔵 进入配网模式 - BLE蓝牙配网");
EnterWifiConfigMode();
return;
}
// 如果没有配置WiFi SSID优先尝试BLE配网
auto& ssid_manager = SsidManager::GetInstance(); // 获取SSID管理器实例
auto ssid_list = ssid_manager.GetSsidList(); // 获取SSID列表
if (ssid_list.empty()) {
ESP_LOGI(TAG, "🔍 未找到WiFi凭据启动BLE配网...");
if (StartBleProvisioning()) {
ESP_LOGI(TAG, "✅ BLE配网启动成功等待手机连接...");
return;
} else {
ESP_LOGW(TAG, "❌ BLE配网启动失败将重试");
vTaskDelay(pdMS_TO_TICKS(5000));
ESP_LOGI(TAG, "🔄 重试启动BLE配网...");
StartBleProvisioning();
return;
}
}
// WiFi凭据存在尝试直接连接
auto& wifi_station = WifiStation::GetInstance();
// 设置WiFi扫描开始回调
wifi_station.OnScanBegin([this]() {
auto display = Board::GetInstance().GetDisplay();
if (display) {
display->ShowNotification(Lang::Strings::SCANNING_WIFI, 30000);
}
});
// 设置WiFi连接开始回调
wifi_station.OnConnect([this](const std::string& ssid) {
auto display = Board::GetInstance().GetDisplay();
if (display) {
std::string notification = Lang::Strings::CONNECT_TO;
notification += ssid;
notification += "...";
display->ShowNotification(notification.c_str(), 30000);
}
// 根据标志决定是否播放网络连接语音提示
auto& application = Application::GetInstance();
if (!application.ShouldSkipDialogIdleSession()) {
// application.PlaySound(Lang::Sounds::P3_LIANJIEWANGLUO); 原有蜡笔小新 音色播报
if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){
application.PlaySound(Lang::Sounds::P3_KAKA_LIANJIEWANGLUO);
}
else if(strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0){
application.PlaySound(Lang::Sounds::P3_LALA_LIANJIEWANGLUO);
}
ESP_LOGI(TAG, "Starting WiFi connection, playing network connection sound");
} else {
ESP_LOGI(TAG, "Skipping network connection sound due to dialog idle restart flag");
// 清除跳过标志,确保后续正常使用时能播放播报
application.ClearDialogIdleSkipSession();
}
});
// 设置WiFi连接成功回调
wifi_station.OnConnected([this](const std::string& ssid) {
auto display = Board::GetInstance().GetDisplay();
if (display) {
std::string notification = Lang::Strings::CONNECTED_TO;
notification += ssid;
display->ShowNotification(notification.c_str(), 30000);
}
});
wifi_station.OnReconnectTimeout([this]() {
auto& ws = WifiStation::GetInstance();
ws.Stop();
esp_wifi_restore();
ResetWifiConfiguration();
});
// 启动WiFi站点模式
wifi_station.Start();
// 尝试连接WiFi如果失败则尝试BLE配网
// 尝试连接WiFi如果失败则尝试BLE配网
// 增加WiFi连接超时时间避免过快进入配网模式
// if (!wifi_station.WaitForConnected(90 * 1000)) {
if (!wifi_station.WaitForConnected(10 * 1000)) {
wifi_station.Stop();// 停止WiFi连接尝试
esp_wifi_restore();// 恢复WiFi默认配置
ResetWifiConfiguration();// 重置WiFi配置
return;
} else {
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
esp_netif_sntp_init(&config);
int retry = 0;
while (esp_netif_sntp_sync_wait(1000 / portTICK_PERIOD_MS) == ESP_ERR_TIMEOUT && ++retry < 5) {}
setenv("TZ", "CST-8", 1);
tzset();
}
}
/**
* @brief 创建HTTP客户端对象
* @return Http* 返回ESP HTTP客户端对象指针
*/
Http* WifiBoard::CreateHttp() {
return new EspHttp();
}
/**
* @brief 创建WebSocket客户端对象
* @return WebSocket* 返回WebSocket客户端对象指针如果未配置则返回nullptr
*
* 根据配置的WebSocket URL选择使用TLS或TCP传输协议
*/
WebSocket* WifiBoard::CreateWebSocket() {
#ifdef CONFIG_CONNECTION_TYPE_WEBSOCKET
std::string url = CONFIG_WEBSOCKET_URL;
if (url.find("wss://") == 0) {
return new WebSocket(new TlsTransport()); // 使用TLS安全传输
} else {
return new WebSocket(new TcpTransport()); // 使用TCP传输
}
#endif
return nullptr;
}
/**
* @brief 创建MQTT客户端对象
* @return Mqtt* 返回ESP MQTT客户端对象指针
*/
Mqtt* WifiBoard::CreateMqtt() {
return new EspMqtt();
}
Udp* WifiBoard::CreateUdp() {
return new EspUdp();
}
// 获取网络状态图标
const char* WifiBoard::GetNetworkStateIcon() {
if (wifi_config_mode_) {// 如果是配网模式
return FONT_AWESOME_WIFI;// 返回WiFi图标
}
auto& wifi_station = WifiStation::GetInstance();// 获取WiFi配置实例
if (!wifi_station.IsConnected()) {// 如果未连接到WiFi
return FONT_AWESOME_WIFI_OFF;// 返回WiFi断开图标
}
int8_t rssi = wifi_station.GetRssi();// 获取WiFi信号强度
if (rssi >= -60) { // 信号强度大于等于-60dBm
return FONT_AWESOME_WIFI;// 返回WiFi图标
} else if (rssi >= -70) {
return FONT_AWESOME_WIFI_FAIR;// 返回WiFi信号中等图标
} else {
return FONT_AWESOME_WIFI_WEAK;// 返回WiFi信号弱图标
}
}
// 获取板级JSON配置
std::string WifiBoard::GetBoardJson() {
// Set the board type for OTA
auto& wifi_station = WifiStation::GetInstance();
std::string board_json = std::string("{\"type\":\"" BOARD_TYPE "\",");// 板级JSON配置字符串包含设备类型、名称、角色、SSID、信号强度、通道、IP地址和MAC地址
board_json += "\"name\":\"" BOARD_NAME "\",";
board_json += "\"role\":\"" CONFIG_DEVICE_ROLE "\","; // 添加设备角色字段用于OTA升级时的角色匹配
if (!wifi_config_mode_) {
board_json += "\"ssid\":\"" + wifi_station.GetSsid() + "\",";
board_json += "\"rssi\":" + std::to_string(wifi_station.GetRssi()) + ",";
board_json += "\"channel\":" + std::to_string(wifi_station.GetChannel()) + ",";
board_json += "\"ip\":\"" + wifi_station.GetIpAddress() + "\",";
}
board_json += "\"mac\":\"" + SystemInfo::GetMacAddress() + "\"}";
return board_json;
}
// 设置低功耗模式 新增配网模式下禁用省电模式
void WifiBoard::SetPowerSaveMode(bool enabled) {
// 如果正在进行 BLE 配网,强制禁用省电模式以确保 MAC 地址能正常发送到手机端
if (enabled && IsBleProvisioningActive()) {
ESP_LOGI(TAG, "🔵 配网模式下,已强制禁用省电模式!");
enabled = false;
}
ESP_LOGI(TAG, "🔋 电源管理模式切换: %s", enabled ? "启用低功耗模式" : "禁用低功耗模式(恢复正常模式)");
auto& wifi_station = WifiStation::GetInstance();
wifi_station.SetPowerSaveMode(enabled);
}
// 重置WiFi配置设备将重启进入配网模式
void WifiBoard::ResetWifiConfiguration() {
ESP_LOGI(TAG, "🔄 重置WiFi配置设备将重启进入配网模式");
// ⚠️ ResetWifiConfiguration 由 BOOT 按键 OnClick 调用, 跑在 esp_timer task 上下文.
// CLAUDE.md 警告: iot_button 回调在 esp_timer task 中执行, 不能 vTaskDelay
// (4000ms vTaskDelay 实测只等了 1100ms 就被唤醒, 音效没播完就 esp_restart).
// 修复: 派发整个"播音效 + 等待 + 重启"逻辑到独立 task 跑.
xTaskCreate([](void* /*arg*/) {
ESP_LOGI("WifiBoard", "🔄 [reset_task] 开始播放配网音效...");
auto& application = Application::GetInstance();
if (strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0) {
application.PlaySound(Lang::Sounds::P3_KAKA_WIFICONFIG);
} else if (strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0) {
application.PlaySound(Lang::Sounds::P3_LALA_WIFICONFIG);
}
// 等 4 秒让音效完整播完 (在独立 task 中 vTaskDelay 正常工作)
// 覆盖: 入队 + audio_loop 出队 + background_task 解码 + I2S DMA + 功放尾音
vTaskDelay(pdMS_TO_TICKS(4000));
ESP_LOGI("WifiBoard", "🔄 [reset_task] 配网音效播放完成");
// 设置 force_ap 标志, 让设备重启后进入配网模式
{
Settings settings("wifi", true);
settings.SetInt("force_ap", 1);
}
ESP_LOGI("WifiBoard", "🔄 [reset_task] 正在重启设备...");
esp_restart();
vTaskDelete(NULL); // 不应执行到这里
}, "wifi_reset", 4096, NULL, 5, NULL);
// ResetWifiConfiguration 立刻 return, 不阻塞 BOOT 按键回调
}
// 启动BLE配网服务
bool WifiBoard::StartBleProvisioning() {
ESP_LOGI(TAG, "🔵 正在启动BLE蓝牙配网服务...");
Application::GetInstance().StopAudioProcessor();// 停止音频处理器,确保在配网过程中不处理音频数据
Application::GetInstance().ClearAudioQueue();// 清空音频队列,移除所有待处理的音频数据
// 初始化BLE配网服务
if (!bluetooth_provisioning_.Initialize()) {
ESP_LOGE(TAG, "❌ BLE蓝牙配网初始化失败");
ESP_LOGI(TAG, "🔍 BLE Initialize返回结果: false");
return false;
}
ESP_LOGI(TAG, "🔍 BLE Initialize返回结果: true");
// 为BLE事件设置回调函数
bluetooth_provisioning_.SetCallback([this](BluetoothProvisioningEvent event, void* data) {
OnBleProvisioningEvent(event, data);
});
// 启动BLE配网服务设备名称由 StartProvisioning 内部构建: Airhub_ + BLE MAC
if (!bluetooth_provisioning_.StartProvisioning()) {
ESP_LOGE(TAG, "❌ BLE蓝牙配网启动失败");
return false;
}
ESP_LOGI(TAG, "✅ BLE蓝牙配网启动成功");
ESP_LOGI(TAG, "📱 请使用支持BLE的手机APP连接设备进行配网");
ble_provisioning_active_ = true; // 标记BLE配网服务已激活
ble_provisioning_success_ = false;// 标记BLE配网服务未成功
ble_start_time_ = xTaskGetTickCount();// 记录启动时间,用于超时检测
// 显示BLE配网通知
auto display = GetDisplay();
if (display) {
display->ShowNotification("BLE配网模式", 30000);
}
// 播放配网提示音
auto& application = Application::GetInstance();
if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){
application.Alert("BLE配网模式", "请使用手机APP搜索设备连接WI-FI", "happy", Lang::Sounds::P3_KAKA_WIFICONFIG);
}
else if(strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0){
application.Alert("BLE配网模式", "请使用手机APP搜索设备连接WI-FI", "happy", Lang::Sounds::P3_LALA_WIFICONFIG);
}
// 创建任务用于监控BLE配网状态
xTaskCreate([](void* param) {
WifiBoard* board = static_cast<WifiBoard*>(param); // 转换参数为WifiBoard指针
board->MonitorBleProvisioning();// 监控BLE配网状态
vTaskDelete(nullptr);// 删除任务,因为任务只执行一次
}, "ble_prov_monitor", 4096, this, 5, nullptr);// 创建任务优先级为5栈大小为4096字节
return true;// 启动成功返回true
}
// // BLE JSON Service 配网(暂不使用,保留代码)
// bool WifiBoard::StartBleJsonProvisioning() {
// ESP_LOGI(TAG, "🔵 正在启动BLE JSON配网服务...");
// Application::GetInstance().StopAudioProcessor();
// Application::GetInstance().ClearAudioQueue();
// if (!ble_json_service_.Initialize()) {
// ESP_LOGE(TAG, "❌ BLE JSON服务初始化失败");
// return false;
// }
// ble_json_service_.SetCommandCallback(
// [this](const std::string& cmd, int msg_id, cJSON* data) {
// Application::GetInstance().HandleBleJsonCommand(cmd, msg_id, data, ble_json_service_);
// });
// if (!ble_json_service_.Start("Airhub_Ble")) {
// ESP_LOGE(TAG, "❌ BLE JSON服务启动失败");
// return false;
// }
// ESP_LOGI(TAG, "✅ BLE JSON配网启动成功");
// ble_provisioning_active_ = true;
// ble_start_time_ = xTaskGetTickCount();
// auto display = GetDisplay();
// if (display) {
// display->ShowNotification("BLE配网模式", 30000);
// }
// auto& application = Application::GetInstance();
// if (strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0) {
// application.Alert("BLE配网模式", "请使用手机APP搜索设备连接WI-FI", "", Lang::Sounds::P3_KAKA_WIFICONFIG);
// } else if (strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0) {
// application.Alert("BLE配网模式", "请使用手机APP搜索设备连接WI-FI", "", Lang::Sounds::P3_LALA_WIFICONFIG);
// }
// while (true) {
// int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
// int min_free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
// ESP_LOGI(TAG, "BLE配网等待中... Free internal: %u minimal internal: %u", free_sram, min_free_sram);
// vTaskDelay(pdMS_TO_TICKS(10000));
// }
// return true;
// }
// 监控BLE配网状态
void WifiBoard::MonitorBleProvisioning() {
ESP_LOGI(TAG, "Starting BLE provisioning monitor...");
while (ble_provisioning_active_) {
TickType_t current_time = xTaskGetTickCount();
TickType_t elapsed_time = current_time - ble_start_time_;
// Check for timeout (2 minutes) - 仅记录日志不再切换到WiFi配网
if (elapsed_time >= pdMS_TO_TICKS(BLE_PROV_TIMEOUT_MS)) {
ESP_LOGW(TAG, "BLE provisioning timeout, but continuing BLE mode (no fallback to WiFi AP)");
// 增加延迟避免快速重新进入配网循环
ESP_LOGI(TAG, "🔵 BLE配网超时等待10秒后重置计时器继续等待配网");
vTaskDelay(pdMS_TO_TICKS(10000)); // 等待10秒,冷却期
// 重置计时器继续等待BLE配网
ble_start_time_ = xTaskGetTickCount();
ESP_LOGI(TAG, "🔵 计时器已重置继续等待BLE配网");
}
// Check if provisioning was successful
if (ble_provisioning_success_) {
ESP_LOGI(TAG, "BLE provisioning completed successfully");
ble_provisioning_active_ = false;
// Stop BLE provisioning
// 停止BLE配网
bluetooth_provisioning_.StopProvisioning();
// Try to connect to the configured WiFi
auto& wifi_station = WifiStation::GetInstance();
wifi_station.Start();
// 增加WiFi连接重试逻辑避免过快重新进入配网模式
int retry_count = 0; // 重试次数
const int max_retries = 3; // 最大重试次数
const int retry_timeout = 60 * 1000; // 60秒超时
// 重试连接WiFi
while (retry_count < max_retries) {
ESP_LOGI(TAG, "WiFi connection attempt %d/%d after BLE provisioning", retry_count + 1, max_retries);
// 等待WiFi连接成功
if (wifi_station.WaitForConnected(retry_timeout)) {
ESP_LOGI(TAG, "WiFi connection successful after BLE provisioning (attempt %d)", retry_count + 1);
auto display = GetDisplay();
if (display) {
display->ShowNotification("WiFi连接成功", 5000);
}
return;
}
retry_count++;// 增加重试次数
if (retry_count < max_retries) {
ESP_LOGW(TAG, "WiFi connection failed (attempt %d/%d), retrying in 10 seconds...", retry_count, max_retries);
vTaskDelay(pdMS_TO_TICKS(10000)); // 等待10秒后重试
wifi_station.Stop();
vTaskDelay(pdMS_TO_TICKS(2000)); // 等待2秒确保完全停止
wifi_station.Start(); // 重新启动WiFi连接
} else {
ESP_LOGW(TAG, "WiFi connection failed after %d attempts, entering AP mode", max_retries);
wifi_station.Stop();
wifi_config_mode_ = true;
EnterWifiConfigMode();
return;
}
}
}
// Wait before next check
vTaskDelay(pdMS_TO_TICKS(1000)); // 等待1秒后检查
}
}
// 处理BLE配网事件
void WifiBoard::OnBleProvisioningEvent(BluetoothProvisioningEvent event, void* data) {
switch (event) {
case BluetoothProvisioningEvent::CLIENT_CONNECTED:
ESP_LOGI(TAG, "BLE client connected");
{
auto display = GetDisplay();
if (display) {
display->ShowNotification("客户端已连接", 5000);
}
}
break;
// 客户端断开事件
case BluetoothProvisioningEvent::CLIENT_DISCONNECTED:
ESP_LOGI(TAG, "BLE client disconnected");
{
auto display = GetDisplay();
if (display) {
display->ShowNotification("客户端已断开", 5000);
}
}
break;
// 接收WiFi凭据事件
case BluetoothProvisioningEvent::WIFI_CREDENTIALS:
ESP_LOGI(TAG, "WiFi credentials received via BLE");
{
auto display = GetDisplay();
if (display) {
display->ShowNotification("WiFi凭据已接收", 5000);
}
}
break;
// 连接成功事件
case BluetoothProvisioningEvent::WIFI_CONNECTED:
ESP_LOGI(TAG, "设备配网成功,已连接到WiFi网络!");
ble_provisioning_success_ = true;
{
auto display = GetDisplay();
if (display) {
display->ShowNotification("WiFi连接成功", 5000);
}
auto& application = Application::GetInstance();
// application.PlaySound(Lang::Sounds::P3_LIANJIEWANGLUO); 原有蜡笔小新 音色播报
if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){
application.PlaySound(Lang::Sounds::P3_KAKA_LIANJIEWANGLUO);
}
else if(strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0){
application.PlaySound(Lang::Sounds::P3_LALA_LIANJIEWANGLUO);
}
}
break;
// 连接失败事件
case BluetoothProvisioningEvent::WIFI_FAILED:
ESP_LOGW(TAG, "WiFi connection failed via BLE");
ble_provisioning_active_ = false;
{
auto display = GetDisplay();
if (display) {
display->ShowNotification("WiFi连接失败", 5000);
}
}
break;
default:
break;
}
}
// BLE配网回调函数
void WifiBoard::BleProvisioningCallback(BluetoothProvisioningEvent event, void* data, void* user_data) {
WifiBoard* board = static_cast<WifiBoard*>(user_data);
if (board) {
board->OnBleProvisioningEvent(event, data);
}
}