修复 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>
638 lines
25 KiB
C++
638 lines
25 KiB
C++
/**
|
||
* @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);
|
||
}
|
||
}
|