1418 lines
52 KiB
C++
1418 lines
52 KiB
C++
/**
|
||
* @file bluetooth_provisioning.cc
|
||
* @brief 蓝牙配网模块实现(基于自定义 GATT Server)
|
||
*
|
||
* 使用自定义 BLE GATT Server + 原始广播数据(raw advertising)实现配网。
|
||
* 采用 dzbj 项目验证的 esp_ble_gap_config_adv_data_raw() 方式,
|
||
* 确保手机系统蓝牙可搜索到设备名称。
|
||
*
|
||
* 保留完整的 WiFi 配网业务逻辑:
|
||
* - WiFi 凭据接收、验证和连接管理
|
||
* - 配网状态机和事件回调
|
||
* - WiFi 连接状态监控和报告
|
||
* - MAC 地址发送
|
||
* - 配网成功后自动保存和重启
|
||
*/
|
||
|
||
#include "bluetooth_provisioning.h"
|
||
#include "esp_log.h"
|
||
#include "esp_bt.h"
|
||
#include "esp_bt_main.h"
|
||
#include "esp_bt_device.h"
|
||
#include "esp_gap_ble_api.h"
|
||
#include "esp_gatts_api.h"
|
||
#include "esp_gatt_common_api.h"
|
||
#include "esp_wifi.h"
|
||
#include "esp_netif.h"
|
||
#include "freertos/event_groups.h"
|
||
#include "freertos/timers.h"
|
||
#include "freertos/task.h"
|
||
#include "esp_system.h"
|
||
#include "application.h"
|
||
#include "assets/lang_config.h"
|
||
#include <ssid_manager.h>
|
||
#include "nvs_flash.h"
|
||
#include "nvs.h"
|
||
#include <cstring>
|
||
|
||
/// 日志标签
|
||
#define TAG "BluetoothProvisioning"
|
||
|
||
/// WiFi连接成功事件位
|
||
#define WIFI_CONNECTED_BIT BIT0
|
||
/// WiFi连接失败事件位
|
||
#define WIFI_FAIL_BIT BIT1
|
||
|
||
/// 静态单例实例指针
|
||
BluetoothProvisioning* BluetoothProvisioning::instance_ = nullptr;
|
||
|
||
/// WiFi事件组句柄
|
||
static EventGroupHandle_t s_wifi_event_group = nullptr;
|
||
|
||
/// WiFi连接重试计数器
|
||
static int s_retry_num = 0;
|
||
/// 最大重试次数
|
||
static const int MAX_RETRY = 2;
|
||
/// WiFi连接超时时间(毫秒)
|
||
static const int WIFI_CONNECT_TIMEOUT_MS = 30000;
|
||
/// WiFi连接超时定时器句柄
|
||
static TimerHandle_t wifi_connect_timer = nullptr;
|
||
|
||
// ============================================================
|
||
// GATT 静态数据定义
|
||
// ============================================================
|
||
|
||
// UUID 定义
|
||
static esp_bt_uuid_t prov_service_uuid = {
|
||
.len = ESP_UUID_LEN_16,
|
||
.uuid = {.uuid16 = PROV_SERVICE_UUID},
|
||
};
|
||
|
||
static esp_bt_uuid_t prov_write_char_uuid = {
|
||
.len = ESP_UUID_LEN_16,
|
||
.uuid = {.uuid16 = PROV_CHAR_WRITE_UUID},
|
||
};
|
||
|
||
static esp_bt_uuid_t prov_notify_char_uuid = {
|
||
.len = ESP_UUID_LEN_16,
|
||
.uuid = {.uuid16 = PROV_CHAR_NOTIFY_UUID},
|
||
};
|
||
|
||
static esp_bt_uuid_t cccd_uuid = {
|
||
.len = ESP_UUID_LEN_16,
|
||
.uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG},
|
||
};
|
||
|
||
// 特征值缓冲区
|
||
static uint8_t prov_write_val[PROV_LOCAL_MTU] = {0};
|
||
static uint8_t prov_notify_val[PROV_LOCAL_MTU] = {0};
|
||
static uint8_t cccd_val[2] = {0x00, 0x00};
|
||
|
||
static esp_attr_value_t prov_write_attr = {
|
||
.attr_max_len = PROV_LOCAL_MTU,
|
||
.attr_len = 1,
|
||
.attr_value = prov_write_val,
|
||
};
|
||
|
||
static esp_attr_value_t prov_notify_attr = {
|
||
.attr_max_len = PROV_LOCAL_MTU,
|
||
.attr_len = 1,
|
||
.attr_value = prov_notify_val,
|
||
};
|
||
|
||
static esp_attr_value_t cccd_attr = {
|
||
.attr_max_len = 2,
|
||
.attr_len = 2,
|
||
.attr_value = cccd_val,
|
||
};
|
||
|
||
// 广播参数 (参考 dzbj: 快速广播间隔,确保手机快速发现)
|
||
static esp_ble_adv_params_t prov_adv_params = {
|
||
.adv_int_min = 0x20, // 20ms (dzbj 相同)
|
||
.adv_int_max = 0x40, // 40ms
|
||
.adv_type = ADV_TYPE_IND,
|
||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||
.peer_addr = {0},
|
||
.peer_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||
.channel_map = ADV_CHNL_ALL,
|
||
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||
};
|
||
|
||
// 原始广播数据 (运行时动态构建)
|
||
static uint8_t prov_adv_raw_data[31];
|
||
static uint8_t prov_adv_raw_len = 0;
|
||
|
||
// 扫描响应数据 (携带 TX Power + BLE MAC 地址)
|
||
static uint8_t prov_scan_rsp_data[31];
|
||
static uint8_t prov_scan_rsp_len = 0;
|
||
|
||
// ============================================================
|
||
// 构造 / 析构
|
||
// ============================================================
|
||
|
||
BluetoothProvisioning::BluetoothProvisioning()
|
||
: state_(BluetoothProvisioningState::IDLE)
|
||
, callback_(nullptr)
|
||
, client_connected_(false)
|
||
, initialized_(false)
|
||
, delayed_disconnect_(false)
|
||
, wifi_connecting_(false)
|
||
, mac_address_sent_(false) {
|
||
|
||
wifi_credentials_.ssid.clear();
|
||
wifi_credentials_.password.clear();
|
||
memset(wifi_credentials_.bssid, 0, sizeof(wifi_credentials_.bssid));
|
||
wifi_credentials_.bssid_set = false;
|
||
|
||
instance_ = this;
|
||
ESP_LOGI(TAG, "蓝牙配网对象创建完成");
|
||
}
|
||
|
||
BluetoothProvisioning::~BluetoothProvisioning() {
|
||
if (initialized_) {
|
||
Deinitialize();
|
||
}
|
||
instance_ = nullptr;
|
||
ESP_LOGI(TAG, "蓝牙配网对象销毁完成");
|
||
}
|
||
|
||
// ============================================================
|
||
// Initialize — 初始化 BLE 栈 + GATT 服务 + WiFi 事件
|
||
// ============================================================
|
||
|
||
bool BluetoothProvisioning::Initialize() {
|
||
if (initialized_) {
|
||
ESP_LOGW(TAG, "蓝牙配网已经初始化");
|
||
return true;
|
||
}
|
||
|
||
SetState(BluetoothProvisioningState::INITIALIZING);
|
||
esp_err_t ret;
|
||
|
||
// 步骤1: 初始化WiFi模块
|
||
ESP_LOGI(TAG, "初始化WiFi...");
|
||
esp_netif_create_default_wifi_sta();
|
||
|
||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||
ret = esp_wifi_init(&cfg);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "WiFi初始化失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
|
||
ret = esp_wifi_set_mode(WIFI_MODE_STA);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "WiFi模式设置失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
|
||
ret = esp_wifi_start();
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "WiFi启动失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
ESP_LOGI(TAG, "WiFi初始化完成");
|
||
|
||
// 步骤2: 初始化蓝牙控制器
|
||
ESP_LOGI(TAG, "初始化蓝牙控制器...");
|
||
esp_bt_controller_status_t ctl_status = esp_bt_controller_get_status();
|
||
#if CONFIG_IDF_TARGET_ESP32
|
||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||
#else
|
||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||
#endif
|
||
if (ctl_status == ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||
esp_bt_controller_disable();
|
||
ctl_status = esp_bt_controller_get_status();
|
||
}
|
||
if (ctl_status == ESP_BT_CONTROLLER_STATUS_INITED) {
|
||
esp_bt_controller_deinit();
|
||
}
|
||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||
ret = esp_bt_controller_init(&bt_cfg);
|
||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||
ESP_LOGE(TAG, "蓝牙控制器初始化失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||
ESP_LOGE(TAG, "蓝牙控制器启用失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
|
||
// 步骤3: 初始化 Bluedroid 协议栈
|
||
ESP_LOGI(TAG, "初始化Bluedroid协议栈...");
|
||
auto bd_status = esp_bluedroid_get_status();
|
||
if (bd_status == ESP_BLUEDROID_STATUS_ENABLED) {
|
||
esp_bluedroid_disable();
|
||
bd_status = esp_bluedroid_get_status();
|
||
}
|
||
if (bd_status == ESP_BLUEDROID_STATUS_INITIALIZED) {
|
||
esp_bluedroid_deinit();
|
||
}
|
||
ret = esp_bluedroid_init();
|
||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||
ESP_LOGE(TAG, "Bluedroid初始化失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
ret = esp_bluedroid_enable();
|
||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||
ESP_LOGE(TAG, "Bluedroid启用失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
|
||
// 步骤4: 注册 GAP 和 GATTS 回调
|
||
ESP_LOGI(TAG, "注册 BLE GAP/GATTS 回调...");
|
||
|
||
ret = esp_ble_gap_register_callback(GapEventHandler);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "GAP回调注册失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
|
||
ret = esp_ble_gatts_register_callback(GattsEventHandler);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "GATTS回调注册失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
|
||
ret = esp_ble_gatts_app_register(PROV_APP_ID);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "GATTS App注册失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
|
||
ret = esp_ble_gatt_set_local_mtu(PROV_LOCAL_MTU);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGW(TAG, "设置MTU失败: %s (可能已设置)", esp_err_to_name(ret));
|
||
}
|
||
|
||
// 步骤5: 创建WiFi事件同步机制
|
||
if (s_wifi_event_group == nullptr) {
|
||
s_wifi_event_group = xEventGroupCreate();
|
||
if (s_wifi_event_group == nullptr) {
|
||
ESP_LOGE(TAG, "WiFi事件组创建失败");
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 步骤6: 注册WiFi和IP事件处理器
|
||
ESP_LOGI(TAG, "注册WiFi事件处理器...");
|
||
|
||
ret = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WiFiEventHandler, this);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "WiFi事件处理器注册失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
|
||
ret = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &IPEventHandler, this);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "IP事件处理器注册失败: %s", esp_err_to_name(ret));
|
||
SetState(BluetoothProvisioningState::FAILED);
|
||
return false;
|
||
}
|
||
|
||
initialized_ = true;
|
||
SetState(BluetoothProvisioningState::IDLE);
|
||
|
||
ESP_LOGI(TAG, "蓝牙配网初始化完成 (GATT Server 模式)");
|
||
ESP_LOGI(TAG, "蓝牙MAC地址: " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(esp_bt_dev_get_address()));
|
||
|
||
return true;
|
||
}
|
||
|
||
// ============================================================
|
||
// Deinitialize
|
||
// ============================================================
|
||
|
||
bool BluetoothProvisioning::Deinitialize() {
|
||
if (!initialized_) {
|
||
ESP_LOGW(TAG, "蓝牙配网未初始化");
|
||
return true;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "开始反初始化蓝牙配网...");
|
||
|
||
if (state_ != BluetoothProvisioningState::IDLE &&
|
||
state_ != BluetoothProvisioningState::STOPPED) {
|
||
StopProvisioning();
|
||
}
|
||
|
||
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &WiFiEventHandler);
|
||
esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &IPEventHandler);
|
||
|
||
if (s_wifi_event_group != nullptr) {
|
||
vEventGroupDelete(s_wifi_event_group);
|
||
s_wifi_event_group = nullptr;
|
||
}
|
||
|
||
esp_bluedroid_disable();
|
||
esp_bluedroid_deinit();
|
||
esp_bt_controller_disable();
|
||
esp_bt_controller_deinit();
|
||
|
||
initialized_ = false;
|
||
SetState(BluetoothProvisioningState::STOPPED);
|
||
|
||
ESP_LOGI(TAG, "蓝牙配网反初始化完成");
|
||
return true;
|
||
}
|
||
|
||
// ============================================================
|
||
// StartProvisioning — 构建原始广播数据并启动广播
|
||
// ============================================================
|
||
|
||
bool BluetoothProvisioning::StartProvisioning() {
|
||
ESP_LOGI(TAG, "🔵 开始启动蓝牙配网服务 (GATT Server)...");
|
||
ESP_LOGI(TAG, "🔍 检查初始化状态: initialized_ = %s", initialized_ ? "true" : "false");
|
||
|
||
if (!initialized_) {
|
||
ESP_LOGE(TAG, "❌ 蓝牙配网未初始化,无法启动");
|
||
return false;
|
||
}
|
||
|
||
if (state_ == BluetoothProvisioningState::ADVERTISING ||
|
||
state_ == BluetoothProvisioningState::CONNECTED ||
|
||
state_ == BluetoothProvisioningState::PROVISIONING) {
|
||
ESP_LOGW(TAG, "⚠️ 蓝牙配网已在运行中");
|
||
return true;
|
||
}
|
||
|
||
// 重置状态
|
||
client_connected_ = false;
|
||
s_retry_num = 0;
|
||
|
||
ResetMacSendingState();
|
||
ESP_LOGI(TAG, "🔄 MAC地址发送状态已重置");
|
||
|
||
// 清空之前的WiFi凭据
|
||
ESP_LOGI(TAG, "🧹 清除之前的WiFi凭据...");
|
||
if (!wifi_credentials_.ssid.empty()) {
|
||
ESP_LOGI(TAG, "🗑️ 删除已保存的SSID: %s", wifi_credentials_.ssid.c_str());
|
||
}
|
||
if (!wifi_credentials_.password.empty()) {
|
||
ESP_LOGI(TAG, "🗑️ 删除已保存的WiFi密码 (长度: %d)", (int)wifi_credentials_.password.length());
|
||
}
|
||
wifi_credentials_.ssid.clear();
|
||
wifi_credentials_.password.clear();
|
||
wifi_credentials_.bssid_set = false;
|
||
ESP_LOGI(TAG, "✅ WiFi凭据清除完成,准备接收新的配网信息");
|
||
|
||
// 构建设备名称: "Airhub_" + BLE MAC 明文
|
||
char ble_device_name[32];
|
||
const uint8_t* ble_addr = esp_bt_dev_get_address();
|
||
if (ble_addr) {
|
||
snprintf(ble_device_name, sizeof(ble_device_name),
|
||
"Airhub_%02x:%02x:%02x:%02x:%02x:%02x",
|
||
ble_addr[0], ble_addr[1], ble_addr[2],
|
||
ble_addr[3], ble_addr[4], ble_addr[5]);
|
||
} else {
|
||
strcpy(ble_device_name, "Airhub_Ble");
|
||
ESP_LOGW(TAG, "获取BLE MAC失败,使用默认名称: %s", ble_device_name);
|
||
}
|
||
|
||
// 设置设备名称
|
||
esp_err_t ret = esp_ble_gap_set_device_name(ble_device_name);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "❌ 设置蓝牙设备名称失败: %s", esp_err_to_name(ret));
|
||
return false;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "📡 蓝牙设备名称: %s", ble_device_name);
|
||
|
||
// 构建广播数据 (Flags + 设备名)
|
||
uint8_t name_len = strlen(ble_device_name);
|
||
int offset = 0;
|
||
|
||
// Flags: LE General Discoverable + BR/EDR Not Supported
|
||
prov_adv_raw_data[offset++] = 0x02;
|
||
prov_adv_raw_data[offset++] = ESP_BLE_AD_TYPE_FLAG;
|
||
prov_adv_raw_data[offset++] = 0x06;
|
||
|
||
// Complete Local Name
|
||
prov_adv_raw_data[offset++] = name_len + 1;
|
||
prov_adv_raw_data[offset++] = ESP_BLE_AD_TYPE_NAME_CMPL;
|
||
memcpy(&prov_adv_raw_data[offset], ble_device_name, name_len);
|
||
offset += name_len;
|
||
|
||
prov_adv_raw_len = offset;
|
||
|
||
ESP_LOGI(TAG, "📡 广播数据构建完成,长度: %d 字节", prov_adv_raw_len);
|
||
|
||
// 构建扫描响应数据 (TX Power + Service UUID)
|
||
int rsp_offset = 0;
|
||
|
||
// TX Power Level
|
||
prov_scan_rsp_data[rsp_offset++] = 0x02;
|
||
prov_scan_rsp_data[rsp_offset++] = ESP_BLE_AD_TYPE_TX_PWR;
|
||
prov_scan_rsp_data[rsp_offset++] = 0x09;
|
||
|
||
// 16-bit Service UUID Complete
|
||
prov_scan_rsp_data[rsp_offset++] = 0x03;
|
||
prov_scan_rsp_data[rsp_offset++] = ESP_BLE_AD_TYPE_16SRV_CMPL;
|
||
prov_scan_rsp_data[rsp_offset++] = PROV_SERVICE_UUID & 0xFF;
|
||
prov_scan_rsp_data[rsp_offset++] = (PROV_SERVICE_UUID >> 8) & 0xFF;
|
||
|
||
prov_scan_rsp_len = rsp_offset;
|
||
|
||
ESP_LOGI(TAG, "📡 扫描响应数据构建完成,长度: %d 字节", prov_scan_rsp_len);
|
||
|
||
// 配置广播数据 (GAP回调中会依次设置扫描响应数据并启动广播)
|
||
ret = esp_ble_gap_config_adv_data_raw(prov_adv_raw_data, prov_adv_raw_len);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "❌ 配置广播数据失败: %s", esp_err_to_name(ret));
|
||
return false;
|
||
}
|
||
|
||
SetState(BluetoothProvisioningState::ADVERTISING);
|
||
ESP_LOGI(TAG, "蓝牙配网广播已启动,等待客户端连接...");
|
||
|
||
return true;
|
||
}
|
||
|
||
// ============================================================
|
||
// StopProvisioning
|
||
// ============================================================
|
||
|
||
bool BluetoothProvisioning::StopProvisioning() {
|
||
if (state_ == BluetoothProvisioningState::IDLE ||
|
||
state_ == BluetoothProvisioningState::STOPPED) {
|
||
ESP_LOGW(TAG, "蓝牙配网未在运行");
|
||
return true;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "停止蓝牙配网...");
|
||
|
||
esp_ble_gap_stop_advertising();
|
||
|
||
if (client_connected_ && gatts_if_ != ESP_GATT_IF_NONE) {
|
||
esp_ble_gatts_close(gatts_if_, conn_id_);
|
||
}
|
||
|
||
client_connected_ = false;
|
||
notify_enabled_ = false;
|
||
|
||
SetState(BluetoothProvisioningState::IDLE);
|
||
ESP_LOGI(TAG, "蓝牙配网已停止");
|
||
|
||
return true;
|
||
}
|
||
|
||
// ============================================================
|
||
// StartAdvertising — 启动 BLE 广播
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::StartAdvertising() {
|
||
// 重新配置原始广播数据,GAP回调中启动广播
|
||
esp_ble_gap_config_adv_data_raw(prov_adv_raw_data, prov_adv_raw_len);
|
||
}
|
||
|
||
// ============================================================
|
||
// GAP 事件回调 (static)
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::GapEventHandler(esp_gap_ble_cb_event_t event,
|
||
esp_ble_gap_cb_param_t* param) {
|
||
switch (event) {
|
||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
||
// 广播数据设置完成,接着配置扫描响应数据
|
||
ESP_LOGI(TAG, "📡 广播数据设置完成,配置扫描响应数据");
|
||
if (prov_scan_rsp_len > 0) {
|
||
esp_ble_gap_config_scan_rsp_data_raw(prov_scan_rsp_data, prov_scan_rsp_len);
|
||
} else {
|
||
esp_ble_gap_start_advertising(&prov_adv_params);
|
||
}
|
||
break;
|
||
|
||
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
|
||
// 扫描响应数据设置完成,启动广播
|
||
ESP_LOGI(TAG, "📡 扫描响应数据设置完成,启动广播");
|
||
esp_ble_gap_start_advertising(&prov_adv_params);
|
||
break;
|
||
|
||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||
ESP_LOGE(TAG, "❌ 广播启动失败: %d", param->adv_start_cmpl.status);
|
||
} else {
|
||
ESP_LOGI(TAG, "✅ 广播启动成功");
|
||
}
|
||
break;
|
||
|
||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||
ESP_LOGI(TAG, "广播已停止");
|
||
break;
|
||
|
||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||
ESP_LOGI(TAG, "连接参数更新: status=%d, conn_int=%d, latency=%d, timeout=%d",
|
||
param->update_conn_params.status,
|
||
param->update_conn_params.conn_int,
|
||
param->update_conn_params.latency,
|
||
param->update_conn_params.timeout);
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// GATTS 事件回调 (static 分发)
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::GattsEventHandler(esp_gatts_cb_event_t event,
|
||
esp_gatt_if_t gatts_if,
|
||
esp_ble_gatts_cb_param_t* param) {
|
||
if (event == ESP_GATTS_REG_EVT) {
|
||
if (param->reg.app_id != PROV_APP_ID) {
|
||
return;
|
||
}
|
||
} else {
|
||
if (!instance_ || gatts_if != instance_->gatts_if_) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (instance_) {
|
||
instance_->HandleGattsEvent(event, gatts_if, param);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// HandleGattsEvent — 实例内处理
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::HandleGattsEvent(esp_gatts_cb_event_t event,
|
||
esp_gatt_if_t gatts_if,
|
||
esp_ble_gatts_cb_param_t* param) {
|
||
switch (event) {
|
||
|
||
// ---- App 注册完成,保存 gatts_if,创建 Service ----
|
||
case ESP_GATTS_REG_EVT:
|
||
if (param->reg.status == ESP_GATT_OK) {
|
||
gatts_if_ = gatts_if;
|
||
ESP_LOGI(TAG, "✅ GATTS App 注册成功, gatts_if=%d", gatts_if);
|
||
CreateService(gatts_if);
|
||
} else {
|
||
ESP_LOGE(TAG, "❌ GATTS App 注册失败, status=%d", param->reg.status);
|
||
}
|
||
break;
|
||
|
||
// ---- Service 创建完成,添加 WRITE Characteristic ----
|
||
case ESP_GATTS_CREATE_EVT:
|
||
if (param->create.status == ESP_GATT_OK) {
|
||
service_handle_ = param->create.service_handle;
|
||
ESP_LOGI(TAG, "Service 创建成功, handle=%d", service_handle_);
|
||
|
||
// 添加 WRITE Characteristic (手机 → 设备)
|
||
esp_gatt_char_prop_t write_prop = ESP_GATT_CHAR_PROP_BIT_WRITE;
|
||
esp_ble_gatts_add_char(service_handle_, &prov_write_char_uuid,
|
||
ESP_GATT_PERM_WRITE,
|
||
write_prop, &prov_write_attr, nullptr);
|
||
} else {
|
||
ESP_LOGE(TAG, "Service 创建失败: %d", param->create.status);
|
||
}
|
||
break;
|
||
|
||
// ---- Characteristic 添加完成 ----
|
||
case ESP_GATTS_ADD_CHAR_EVT:
|
||
if (param->add_char.status != ESP_GATT_OK) {
|
||
ESP_LOGE(TAG, "添加特征失败: uuid=0x%04x status=%d",
|
||
param->add_char.char_uuid.uuid.uuid16, param->add_char.status);
|
||
break;
|
||
}
|
||
|
||
if (param->add_char.char_uuid.uuid.uuid16 == PROV_CHAR_WRITE_UUID) {
|
||
write_char_handle_ = param->add_char.attr_handle;
|
||
ESP_LOGI(TAG, "WRITE 特征添加成功, handle=%d", write_char_handle_);
|
||
|
||
// 添加 NOTIFY Characteristic (设备 → 手机)
|
||
esp_gatt_char_prop_t notify_prop = ESP_GATT_CHAR_PROP_BIT_NOTIFY | ESP_GATT_CHAR_PROP_BIT_READ;
|
||
esp_ble_gatts_add_char(service_handle_, &prov_notify_char_uuid,
|
||
ESP_GATT_PERM_READ,
|
||
notify_prop, &prov_notify_attr, nullptr);
|
||
|
||
} else if (param->add_char.char_uuid.uuid.uuid16 == PROV_CHAR_NOTIFY_UUID) {
|
||
notify_char_handle_ = param->add_char.attr_handle;
|
||
ESP_LOGI(TAG, "NOTIFY 特征添加成功, handle=%d", notify_char_handle_);
|
||
|
||
// 为 NOTIFY 特征添加 CCCD 描述符
|
||
esp_ble_gatts_add_char_descr(service_handle_, &cccd_uuid,
|
||
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||
&cccd_attr, nullptr);
|
||
}
|
||
break;
|
||
|
||
// ---- CCCD 描述符添加完成,启动服务 ----
|
||
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
|
||
if (param->add_char_descr.status == ESP_GATT_OK) {
|
||
notify_cccd_handle_ = param->add_char_descr.attr_handle;
|
||
ESP_LOGI(TAG, "CCCD 添加成功, handle=%d", notify_cccd_handle_);
|
||
|
||
// 所有特征添加完毕,启动服务
|
||
esp_ble_gatts_start_service(service_handle_);
|
||
} else {
|
||
ESP_LOGE(TAG, "CCCD 添加失败: %d", param->add_char_descr.status);
|
||
}
|
||
break;
|
||
|
||
// ---- Service 启动完成 ----
|
||
case ESP_GATTS_START_EVT:
|
||
if (param->start.status == ESP_GATT_OK) {
|
||
ESP_LOGI(TAG, "✅ GATT Service 启动成功");
|
||
}
|
||
break;
|
||
|
||
// ---- 客户端连接 ----
|
||
case ESP_GATTS_CONNECT_EVT: {
|
||
conn_id_ = param->connect.conn_id;
|
||
client_connected_ = true;
|
||
notify_enabled_ = false;
|
||
mtu_ = 23;
|
||
|
||
ESP_LOGI(TAG, "📱 客户端已连接, conn_id=%d, addr=%02x:%02x:%02x:%02x:%02x:%02x",
|
||
conn_id_,
|
||
param->connect.remote_bda[0], param->connect.remote_bda[1],
|
||
param->connect.remote_bda[2], param->connect.remote_bda[3],
|
||
param->connect.remote_bda[4], param->connect.remote_bda[5]);
|
||
|
||
ESP_LOGI(TAG, "🔍 [DEBUG] 设置client_connected_为true");
|
||
|
||
// 重置MAC地址发送状态
|
||
ResetMacSendingState();
|
||
ESP_LOGI(TAG, "🔄 MAC地址发送状态已重置");
|
||
|
||
// 更新连接参数
|
||
esp_ble_conn_update_params_t conn_params = {};
|
||
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||
conn_params.latency = 0;
|
||
conn_params.max_int = 0x20; // 40ms
|
||
conn_params.min_int = 0x10; // 20ms
|
||
conn_params.timeout = 400; // 4s
|
||
esp_ble_gap_update_conn_params(&conn_params);
|
||
|
||
// 停止广播
|
||
esp_ble_gap_stop_advertising();
|
||
|
||
SetState(BluetoothProvisioningState::CONNECTED);
|
||
TriggerCallback(BluetoothProvisioningEvent::CLIENT_CONNECTED, nullptr);
|
||
|
||
ESP_LOGI(TAG, "🔍 [DEBUG] BLE连接处理完成,client_connected_=%s",
|
||
client_connected_ ? "true" : "false");
|
||
break;
|
||
}
|
||
|
||
// ---- 客户端断开 ----
|
||
case ESP_GATTS_DISCONNECT_EVT: {
|
||
ESP_LOGI(TAG, "📱 客户端已断开连接, reason=0x%x, 当前状态: %s",
|
||
param->disconnect.reason, GetStateString().c_str());
|
||
ESP_LOGI(TAG, "🔍 [DEBUG] 设置client_connected_为false");
|
||
client_connected_ = false;
|
||
notify_enabled_ = false;
|
||
mtu_ = 23;
|
||
|
||
// 如果正在配网过程中,延迟处理断开事件
|
||
if (state_ == BluetoothProvisioningState::PROVISIONING) {
|
||
ESP_LOGW(TAG, "⚠️ 配网过程中BLE断开,延迟5秒后处理以等待WiFi连接完成");
|
||
delayed_disconnect_ = true;
|
||
xTaskCreate([](void* param) {
|
||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||
BluetoothProvisioning* self = static_cast<BluetoothProvisioning*>(param);
|
||
if (self->delayed_disconnect_) {
|
||
if (self->state_ == BluetoothProvisioningState::PROVISIONING && self->wifi_connecting_) {
|
||
ESP_LOGW(TAG, "⏰ BLE延迟断开,但WiFi仍在连接中,继续等待");
|
||
} else if (self->state_ == BluetoothProvisioningState::PROVISIONING) {
|
||
ESP_LOGW(TAG, "⏰ 延迟处理BLE断开,WiFi连接可能已超时");
|
||
self->SetState(BluetoothProvisioningState::ADVERTISING);
|
||
self->TriggerCallback(BluetoothProvisioningEvent::CLIENT_DISCONNECTED, nullptr);
|
||
self->StartAdvertising();
|
||
}
|
||
self->delayed_disconnect_ = false;
|
||
}
|
||
vTaskDelete(nullptr);
|
||
}, "delayed_disconnect", 2048, instance_, 1, nullptr);
|
||
} else {
|
||
SetState(BluetoothProvisioningState::ADVERTISING);
|
||
TriggerCallback(BluetoothProvisioningEvent::CLIENT_DISCONNECTED, nullptr);
|
||
// 重新启动广播
|
||
StartAdvertising();
|
||
}
|
||
break;
|
||
}
|
||
|
||
// ---- MTU 协商完成 ----
|
||
case ESP_GATTS_MTU_EVT:
|
||
mtu_ = param->mtu.mtu;
|
||
ESP_LOGI(TAG, "MTU 更新: %d", mtu_);
|
||
break;
|
||
|
||
// ---- WRITE 事件 ----
|
||
case ESP_GATTS_WRITE_EVT:
|
||
if (param->write.handle == write_char_handle_) {
|
||
// 配网协议数据
|
||
ProcessWriteData(param->write.value, param->write.len);
|
||
} else if (param->write.handle == notify_cccd_handle_ && param->write.len == 2) {
|
||
// CCCD 写入: 开启/关闭 NOTIFY
|
||
uint16_t cccd_value = param->write.value[0] | (param->write.value[1] << 8);
|
||
notify_enabled_ = (cccd_value == 0x0001);
|
||
ESP_LOGI(TAG, "NOTIFY %s", notify_enabled_ ? "已启用" : "已禁用");
|
||
}
|
||
|
||
if (param->write.need_rsp) {
|
||
esp_ble_gatts_send_response(gatts_if_, param->write.conn_id,
|
||
param->write.trans_id, ESP_GATT_OK, nullptr);
|
||
}
|
||
break;
|
||
|
||
// ---- READ 事件 ----
|
||
case ESP_GATTS_READ_EVT:
|
||
ESP_LOGD(TAG, "Read event, handle=%d", param->read.handle);
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// CreateService
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::CreateService(esp_gatt_if_t gatts_if) {
|
||
esp_gatt_srvc_id_t service_id = {};
|
||
service_id.is_primary = true;
|
||
service_id.id.inst_id = 0;
|
||
service_id.id.uuid.len = ESP_UUID_LEN_16;
|
||
service_id.id.uuid.uuid.uuid16 = PROV_SERVICE_UUID;
|
||
|
||
esp_ble_gatts_create_service(gatts_if, &service_id, PROV_HANDLE_NUM);
|
||
}
|
||
|
||
// ============================================================
|
||
// ProcessWriteData — 处理手机发送的配网协议数据
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::ProcessWriteData(const uint8_t* data, uint16_t len) {
|
||
if (!data || len < 1) return;
|
||
|
||
uint8_t cmd = data[0];
|
||
const uint8_t* payload = data + 1;
|
||
uint16_t payload_len = len - 1;
|
||
|
||
switch (cmd) {
|
||
// 接收到WiFi SSID
|
||
case PROV_CMD_SET_SSID:
|
||
ESP_LOGI(TAG, "📶 收到WiFi SSID: %.*s", payload_len, payload);
|
||
wifi_credentials_.ssid.assign(reinterpret_cast<const char*>(payload), payload_len);
|
||
{
|
||
wifi_config_t wifi_config = {};
|
||
strncpy(reinterpret_cast<char*>(wifi_config.sta.ssid),
|
||
wifi_credentials_.ssid.c_str(),
|
||
sizeof(wifi_config.sta.ssid) - 1);
|
||
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
|
||
}
|
||
break;
|
||
|
||
// 接收到WiFi密码
|
||
case PROV_CMD_SET_PASSWORD:
|
||
ESP_LOGI(TAG, "🔐 收到WiFi密码 (长度: %d)", payload_len);
|
||
wifi_credentials_.password.assign(reinterpret_cast<const char*>(payload), payload_len);
|
||
{
|
||
wifi_config_t wifi_config = {};
|
||
strncpy(reinterpret_cast<char*>(wifi_config.sta.ssid),
|
||
wifi_credentials_.ssid.c_str(),
|
||
sizeof(wifi_config.sta.ssid) - 1);
|
||
strncpy(reinterpret_cast<char*>(wifi_config.sta.password),
|
||
wifi_credentials_.password.c_str(),
|
||
sizeof(wifi_config.sta.password) - 1);
|
||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||
}
|
||
|
||
// 重置重试计数器
|
||
s_retry_num = 0;
|
||
wifi_connecting_ = true;
|
||
|
||
// 启动WiFi连接超时定时器
|
||
if (wifi_connect_timer) {
|
||
xTimerStop(wifi_connect_timer, 0);
|
||
xTimerDelete(wifi_connect_timer, 0);
|
||
}
|
||
wifi_connect_timer = xTimerCreate("wifi_timeout",
|
||
pdMS_TO_TICKS(WIFI_CONNECT_TIMEOUT_MS),
|
||
pdFALSE, nullptr,
|
||
[](TimerHandle_t timer) {
|
||
ESP_LOGW(TAG, "⏰ WiFi连接超时,强制失败处理");
|
||
if (instance_ && instance_->wifi_connecting_) {
|
||
instance_->wifi_connecting_ = false;
|
||
instance_->delayed_disconnect_ = false;
|
||
instance_->SetState(BluetoothProvisioningState::FAILED);
|
||
instance_->TriggerCallback(BluetoothProvisioningEvent::WIFI_FAILED, nullptr);
|
||
instance_->ReportWiFiStatus(false, WIFI_REASON_UNSPECIFIED);
|
||
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
||
}
|
||
});
|
||
xTimerStart(wifi_connect_timer, 0);
|
||
|
||
esp_wifi_connect();
|
||
ESP_LOGI(TAG, "📡 已发起WiFi连接请求,启动超时监控");
|
||
|
||
TriggerCallback(BluetoothProvisioningEvent::WIFI_CREDENTIALS, &wifi_credentials_);
|
||
break;
|
||
|
||
// 接收到BSSID
|
||
case PROV_CMD_SET_BSSID:
|
||
if (payload_len >= 6) {
|
||
ESP_LOGI(TAG, "收到BSSID");
|
||
memcpy(wifi_credentials_.bssid, payload, 6);
|
||
wifi_credentials_.bssid_set = true;
|
||
|
||
wifi_config_t wifi_config = {};
|
||
strncpy(reinterpret_cast<char*>(wifi_config.sta.ssid),
|
||
wifi_credentials_.ssid.c_str(),
|
||
sizeof(wifi_config.sta.ssid) - 1);
|
||
strncpy(reinterpret_cast<char*>(wifi_config.sta.password),
|
||
wifi_credentials_.password.c_str(),
|
||
sizeof(wifi_config.sta.password) - 1);
|
||
memcpy(wifi_config.sta.bssid, wifi_credentials_.bssid, 6);
|
||
wifi_config.sta.bssid_set = true;
|
||
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
|
||
}
|
||
break;
|
||
|
||
// 请求连接到AP
|
||
case PROV_CMD_CONNECT_AP:
|
||
ESP_LOGI(TAG, "📡 请求连接到AP,SSID: %s", wifi_credentials_.ssid.c_str());
|
||
ESP_LOGI(TAG, "🔍 [DEBUG] 当前状态: %s, client_connected_: %s",
|
||
GetStateString().c_str(), client_connected_ ? "true" : "false");
|
||
SetState(BluetoothProvisioningState::PROVISIONING);
|
||
delayed_disconnect_ = false;
|
||
s_retry_num = 0;
|
||
ESP_LOGI(TAG, "🔄 重置WiFi重试计数,开始连接流程");
|
||
esp_wifi_disconnect();
|
||
vTaskDelay(pdMS_TO_TICKS(100));
|
||
esp_wifi_connect();
|
||
ESP_LOGI(TAG, "🚀 已发起WiFi连接请求");
|
||
break;
|
||
|
||
// 请求断开AP
|
||
case PROV_CMD_DISCONNECT_AP:
|
||
ESP_LOGI(TAG, "请求断开AP连接");
|
||
esp_wifi_disconnect();
|
||
break;
|
||
|
||
// 请求WiFi列表
|
||
case PROV_CMD_GET_WIFI_LIST:
|
||
ESP_LOGI(TAG, "📱 手机请求获取WiFi列表,开始扫描");
|
||
{
|
||
wifi_scan_config_t scan_config = {};
|
||
scan_config.ssid = nullptr;
|
||
scan_config.bssid = nullptr;
|
||
scan_config.channel = 0;
|
||
scan_config.show_hidden = true;
|
||
scan_config.scan_type = WIFI_SCAN_TYPE_ACTIVE;
|
||
scan_config.scan_time.active.min = 100;
|
||
scan_config.scan_time.active.max = 300;
|
||
|
||
esp_err_t ret = esp_wifi_scan_start(&scan_config, false);
|
||
if (ret == ESP_OK) {
|
||
ESP_LOGI(TAG, "🔍 WiFi扫描已启动");
|
||
} else {
|
||
ESP_LOGE(TAG, "❌ WiFi扫描启动失败: %s", esp_err_to_name(ret));
|
||
}
|
||
}
|
||
break;
|
||
|
||
// 请求断开BLE
|
||
case PROV_CMD_DISCONNECT_BLE:
|
||
ESP_LOGI(TAG, "收到断开BLE连接请求");
|
||
if (gatts_if_ != ESP_GATT_IF_NONE) {
|
||
esp_ble_gatts_close(gatts_if_, conn_id_);
|
||
}
|
||
break;
|
||
|
||
// 设置WiFi模式
|
||
case PROV_CMD_SET_WIFI_MODE:
|
||
if (payload_len >= 1) {
|
||
ESP_LOGI(TAG, "设置WiFi模式: %d", payload[0]);
|
||
esp_wifi_set_mode((wifi_mode_t)payload[0]);
|
||
}
|
||
break;
|
||
|
||
// 获取WiFi状态
|
||
case PROV_CMD_GET_WIFI_STATUS:
|
||
ESP_LOGI(TAG, "客户端请求WiFi状态");
|
||
break;
|
||
|
||
// 自定义数据
|
||
case PROV_CMD_CUSTOM_DATA:
|
||
ESP_LOGI(TAG, "收到自定义数据, 长度: %d", payload_len);
|
||
break;
|
||
|
||
default:
|
||
ESP_LOGW(TAG, "未知命令: 0x%02x", cmd);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// SendNotify — 通过 GATT NOTIFY 发送数据
|
||
// ============================================================
|
||
|
||
bool BluetoothProvisioning::SendNotify(const uint8_t* data, uint16_t len) {
|
||
if (gatts_if_ == ESP_GATT_IF_NONE || !client_connected_) {
|
||
return false;
|
||
}
|
||
|
||
uint16_t max_payload = mtu_ - 3;
|
||
if (len > max_payload) {
|
||
ESP_LOGW(TAG, "数据长度 %d 超过 MTU payload %d,截断", len, max_payload);
|
||
len = max_payload;
|
||
}
|
||
|
||
esp_err_t ret = esp_ble_gatts_send_indicate(
|
||
gatts_if_, conn_id_, notify_char_handle_,
|
||
len, (uint8_t*)data, false);
|
||
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "发送 NOTIFY 失败: %s", esp_err_to_name(ret));
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ============================================================
|
||
// ReportWiFiStatus — 通过 GATT NOTIFY 报告WiFi连接状态
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::ReportWiFiStatus(bool success, uint8_t reason) {
|
||
ESP_LOGI(TAG, "🔍 [DEBUG] ReportWiFiStatus调用: success=%s, client_connected_=%s",
|
||
success ? "true" : "false", client_connected_ ? "true" : "false");
|
||
if (!client_connected_) {
|
||
ESP_LOGW(TAG, "客户端未连接,无法发送WiFi状态");
|
||
return;
|
||
}
|
||
|
||
uint8_t buf[3];
|
||
buf[0] = PROV_RESP_WIFI_STATUS;
|
||
buf[1] = success ? 1 : 0;
|
||
buf[2] = reason;
|
||
|
||
if (notify_enabled_) {
|
||
SendNotify(buf, 3);
|
||
}
|
||
|
||
if (success) {
|
||
ESP_LOGI(TAG, "向客户端报告设备连接WiFi成功!");
|
||
} else {
|
||
ESP_LOGI(TAG, "向客户端报告连接WiFi失败,原因: %d", reason);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// SendWiFiList — 通过 GATT NOTIFY 发送WiFi扫描列表
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::SendWiFiList(const wifi_ap_record_t* ap_list, uint16_t ap_count) {
|
||
if (!client_connected_) {
|
||
ESP_LOGW(TAG, "客户端未连接,无法发送WiFi列表");
|
||
return;
|
||
}
|
||
|
||
if (ap_list == nullptr || ap_count == 0) {
|
||
ESP_LOGW(TAG, "WiFi列表为空");
|
||
// 发送结束标记
|
||
if (notify_enabled_) {
|
||
uint8_t end = PROV_RESP_WIFI_LIST_END;
|
||
SendNotify(&end, 1);
|
||
}
|
||
return;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "向客户端发送WiFi列表,共%d个AP", ap_count);
|
||
|
||
if (notify_enabled_) {
|
||
for (uint16_t i = 0; i < ap_count; i++) {
|
||
uint8_t ssid_len = strlen(reinterpret_cast<const char*>(ap_list[i].ssid));
|
||
uint8_t buf[3 + 32]; // cmd + rssi + ssid_len + ssid
|
||
buf[0] = PROV_RESP_WIFI_LIST;
|
||
buf[1] = (uint8_t)(ap_list[i].rssi & 0xFF);
|
||
buf[2] = ssid_len;
|
||
memcpy(&buf[3], ap_list[i].ssid, ssid_len);
|
||
|
||
SendNotify(buf, 3 + ssid_len);
|
||
vTaskDelay(pdMS_TO_TICKS(20)); // 每条之间稍作延迟
|
||
}
|
||
|
||
// 发送结束标记
|
||
uint8_t end = PROV_RESP_WIFI_LIST_END;
|
||
SendNotify(&end, 1);
|
||
}
|
||
|
||
ESP_LOGI(TAG, "📤 WiFi列表已发送给客户端,包含 %d 个热点", ap_count);
|
||
}
|
||
|
||
// ============================================================
|
||
// SendMacAddressReliably — 通过 GATT NOTIFY 可靠发送MAC地址
|
||
// ============================================================
|
||
|
||
bool BluetoothProvisioning::SendMacAddressReliably() {
|
||
if (!client_connected_) {
|
||
ESP_LOGW(TAG, "客户端未连接,无法发送MAC地址");
|
||
return false;
|
||
}
|
||
|
||
uint8_t mac[6];
|
||
esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, mac);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "获取MAC地址失败: %s", esp_err_to_name(ret));
|
||
return false;
|
||
}
|
||
|
||
char mac_str[18];
|
||
snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X",
|
||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||
|
||
if (mac_address_sent_) {
|
||
ESP_LOGI(TAG, "MAC地址已发送过,跳过重复发送: %s", mac_str);
|
||
return true;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "开始可靠发送MAC地址: %s", mac_str);
|
||
|
||
const int MAX_SEND_ATTEMPTS = 3;
|
||
const int RETRY_DELAY_MS = 50;
|
||
|
||
for (int attempt = 1; attempt <= MAX_SEND_ATTEMPTS; attempt++) {
|
||
if (!client_connected_) {
|
||
ESP_LOGW(TAG, "发送前检查发现客户端已断开连接 (尝试 %d/%d)", attempt, MAX_SEND_ATTEMPTS);
|
||
return false;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "发送MAC地址尝试 %d/%d: %s", attempt, MAX_SEND_ATTEMPTS, mac_str);
|
||
|
||
// 构建自定义数据包 (保留原有格式)
|
||
char mac_data[32];
|
||
snprintf(mac_data, sizeof(mac_data), "STA_MAC:%02x:%02x:%02x:%02x:%02x:%02x",
|
||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||
|
||
uint8_t buf[1 + 32];
|
||
buf[0] = PROV_RESP_CUSTOM_DATA;
|
||
uint8_t data_len = strlen(mac_data);
|
||
memcpy(&buf[1], mac_data, data_len);
|
||
|
||
bool ok = false;
|
||
if (notify_enabled_) {
|
||
ok = SendNotify(buf, 1 + data_len);
|
||
} else {
|
||
ESP_LOGW(TAG, "NOTIFY未启用,尝试直接发送");
|
||
ok = SendNotify(buf, 1 + data_len);
|
||
}
|
||
|
||
if (ok) {
|
||
ESP_LOGI(TAG, "✅ MAC地址发送成功 (尝试 %d/%d): %s", attempt, MAX_SEND_ATTEMPTS, mac_str);
|
||
mac_address_sent_ = true;
|
||
vTaskDelay(pdMS_TO_TICKS(100));
|
||
|
||
if (client_connected_) {
|
||
ESP_LOGI(TAG, "MAC地址发送完成,连接状态正常");
|
||
return true;
|
||
} else {
|
||
ESP_LOGW(TAG, "MAC地址发送后检测到连接断开");
|
||
return false;
|
||
}
|
||
} else {
|
||
ESP_LOGW(TAG, "❌ MAC地址发送失败 (尝试 %d/%d): %s",
|
||
attempt, MAX_SEND_ATTEMPTS, mac_str);
|
||
if (attempt < MAX_SEND_ATTEMPTS) {
|
||
vTaskDelay(pdMS_TO_TICKS(RETRY_DELAY_MS));
|
||
}
|
||
}
|
||
}
|
||
|
||
ESP_LOGE(TAG, "MAC地址发送失败,已达到最大重试次数: %s", mac_str);
|
||
return false;
|
||
}
|
||
|
||
void BluetoothProvisioning::ResetMacSendingState() {
|
||
mac_address_sent_ = false;
|
||
ESP_LOGI(TAG, "MAC地址发送状态已重置");
|
||
}
|
||
|
||
// ============================================================
|
||
// 状态管理
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::SetState(BluetoothProvisioningState new_state) {
|
||
if (state_ != new_state) {
|
||
BluetoothProvisioningState old_state = state_;
|
||
state_ = new_state;
|
||
|
||
const char* state_names[] = {
|
||
"IDLE", "INITIALIZING", "ADVERTISING", "CONNECTED",
|
||
"PROVISIONING", "SUCCESS", "FAILED", "STOPPED"
|
||
};
|
||
|
||
ESP_LOGI(TAG, "🔄 配网状态变化: %s -> %s",
|
||
state_names[static_cast<int>(old_state)],
|
||
state_names[static_cast<int>(new_state)]);
|
||
|
||
TriggerCallback(BluetoothProvisioningEvent::STATE_CHANGED, nullptr);
|
||
}
|
||
}
|
||
|
||
void BluetoothProvisioning::TriggerCallback(BluetoothProvisioningEvent event, void* data) {
|
||
if (callback_) {
|
||
callback_(event, data);
|
||
}
|
||
}
|
||
|
||
std::string BluetoothProvisioning::GetStateString() const {
|
||
const char* state_names[] = {
|
||
"IDLE", "INITIALIZING", "ADVERTISING", "CONNECTED",
|
||
"PROVISIONING", "SUCCESS", "FAILED", "STOPPED"
|
||
};
|
||
return std::string(state_names[static_cast<int>(state_)]);
|
||
}
|
||
|
||
// ============================================================
|
||
// WiFi 事件处理 (保留原有业务逻辑)
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::WiFiEventHandler(void* arg, esp_event_base_t event_base,
|
||
int32_t event_id, void* event_data) {
|
||
BluetoothProvisioning* self = static_cast<BluetoothProvisioning*>(arg);
|
||
|
||
if (event_base == WIFI_EVENT) {
|
||
switch (event_id) {
|
||
case WIFI_EVENT_STA_START:
|
||
ESP_LOGI(TAG, "WiFi STA启动");
|
||
break;
|
||
|
||
case WIFI_EVENT_STA_CONNECTED: {
|
||
wifi_event_sta_connected_t* event = static_cast<wifi_event_sta_connected_t*>(event_data);
|
||
ESP_LOGI(TAG, "✅ WiFi连接成功,SSID: %.*s,等待获取IP地址", event->ssid_len, event->ssid);
|
||
|
||
if (wifi_connect_timer) {
|
||
xTimerStop(wifi_connect_timer, 0);
|
||
}
|
||
|
||
self->wifi_connecting_ = false;
|
||
self->delayed_disconnect_ = false;
|
||
break;
|
||
}
|
||
|
||
case WIFI_EVENT_STA_DISCONNECTED: {
|
||
wifi_event_sta_disconnected_t* event = static_cast<wifi_event_sta_disconnected_t*>(event_data);
|
||
ESP_LOGI(TAG, "WiFi断开连接,原因: %d", event->reason);
|
||
|
||
if (self->state_ != BluetoothProvisioningState::PROVISIONING) {
|
||
ESP_LOGD(TAG, "非配网状态下的WiFi断开,忽略处理");
|
||
break;
|
||
}
|
||
|
||
if (s_retry_num < MAX_RETRY) {
|
||
esp_err_t ret = esp_wifi_connect();
|
||
s_retry_num++;
|
||
ESP_LOGI(TAG, "🔄 立即重试连接WiFi (%d/%d),断开原因: %d,重试结果: %s",
|
||
s_retry_num, MAX_RETRY, event->reason,
|
||
ret == ESP_OK ? "成功" : "失败");
|
||
|
||
if (wifi_connect_timer && ret == ESP_OK) {
|
||
xTimerReset(wifi_connect_timer, 0);
|
||
}
|
||
} else {
|
||
ESP_LOGE(TAG, "❌ WiFi连接失败,已达到最大重试次数,断开原因: %d", event->reason);
|
||
|
||
if (wifi_connect_timer) {
|
||
xTimerStop(wifi_connect_timer, 0);
|
||
}
|
||
|
||
self->wifi_connecting_ = false;
|
||
self->delayed_disconnect_ = false;
|
||
|
||
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
||
self->SetState(BluetoothProvisioningState::FAILED);
|
||
self->TriggerCallback(BluetoothProvisioningEvent::WIFI_FAILED, &event->reason);
|
||
self->ReportWiFiStatus(false, event->reason);
|
||
}
|
||
break;
|
||
}
|
||
|
||
case WIFI_EVENT_SCAN_DONE: {
|
||
ESP_LOGI(TAG, "📡 WiFi扫描完成,准备发送WiFi列表");
|
||
|
||
uint16_t ap_count = 0;
|
||
esp_err_t ret = esp_wifi_scan_get_ap_num(&ap_count);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "❌ 获取WiFi扫描结果数量失败: %s", esp_err_to_name(ret));
|
||
break;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "📊 扫描到 %d 个WiFi热点", ap_count);
|
||
|
||
if (ap_count > 0) {
|
||
wifi_ap_record_t* ap_list = (wifi_ap_record_t*)malloc(sizeof(wifi_ap_record_t) * ap_count);
|
||
if (ap_list == nullptr) {
|
||
ESP_LOGE(TAG, "❌ 分配WiFi扫描结果内存失败");
|
||
break;
|
||
}
|
||
|
||
ret = esp_wifi_scan_get_ap_records(&ap_count, ap_list);
|
||
if (ret == ESP_OK) {
|
||
ESP_LOGI(TAG, "✅ 成功获取WiFi扫描结果");
|
||
|
||
// 过滤: 只保留2.4GHz频段(信道1-14),去重相同SSID(保留信号最强的)
|
||
// ESP-IDF返回结果已按RSSI降序排列,第一个出现的同名SSID即为信号最强
|
||
uint16_t filtered_count = 0;
|
||
for (uint16_t i = 0; i < ap_count; i++) {
|
||
// 过滤5GHz频段 (信道 > 14)
|
||
if (ap_list[i].primary > 14) {
|
||
ESP_LOGD(TAG, "过滤5GHz: SSID=%s, 信道=%d, RSSI=%d",
|
||
ap_list[i].ssid, ap_list[i].primary, ap_list[i].rssi);
|
||
continue;
|
||
}
|
||
|
||
// 过滤空SSID(隐藏网络)
|
||
uint8_t ssid_len = strlen(reinterpret_cast<const char*>(ap_list[i].ssid));
|
||
if (ssid_len == 0) {
|
||
continue;
|
||
}
|
||
|
||
// SSID去重: 检查是否已存在相同SSID
|
||
bool duplicate = false;
|
||
for (uint16_t j = 0; j < filtered_count; j++) {
|
||
if (strcmp(reinterpret_cast<const char*>(ap_list[j].ssid),
|
||
reinterpret_cast<const char*>(ap_list[i].ssid)) == 0) {
|
||
duplicate = true;
|
||
break;
|
||
}
|
||
}
|
||
if (duplicate) {
|
||
continue;
|
||
}
|
||
|
||
// 将符合条件的AP移到前面
|
||
if (filtered_count != i) {
|
||
ap_list[filtered_count] = ap_list[i];
|
||
}
|
||
filtered_count++;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "📊 过滤后剩余 %d 个2.4GHz热点 (原始: %d)", filtered_count, ap_count);
|
||
|
||
self->SendWiFiList(ap_list, filtered_count);
|
||
ESP_LOGI(TAG, "📤 WiFi列表已发送,包含 %d 个热点", filtered_count);
|
||
} else {
|
||
ESP_LOGE(TAG, "❌ 获取WiFi扫描结果详细信息失败: %s", esp_err_to_name(ret));
|
||
}
|
||
|
||
free(ap_list);
|
||
} else {
|
||
ESP_LOGW(TAG, "⚠️ 未扫描到任何WiFi热点");
|
||
self->SendWiFiList(nullptr, 0);
|
||
}
|
||
break;
|
||
}
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// IP 事件处理 (保留原有业务逻辑: 保存WiFi、发送MAC、重启)
|
||
// ============================================================
|
||
|
||
void BluetoothProvisioning::IPEventHandler(void* arg, esp_event_base_t event_base,
|
||
int32_t event_id, void* event_data) {
|
||
BluetoothProvisioning* self = static_cast<BluetoothProvisioning*>(arg);
|
||
|
||
switch (event_id) {
|
||
case IP_EVENT_STA_GOT_IP: {
|
||
ip_event_got_ip_t* event = static_cast<ip_event_got_ip_t*>(event_data);
|
||
ESP_LOGI(TAG, "✅ WiFi获取IP地址成功: " IPSTR, IP2STR(&event->ip_info.ip));
|
||
|
||
s_retry_num = 0;
|
||
|
||
// 停止WiFi连接超时定时器
|
||
if (wifi_connect_timer) {
|
||
xTimerStop(wifi_connect_timer, 0);
|
||
xTimerDelete(wifi_connect_timer, 0);
|
||
wifi_connect_timer = nullptr;
|
||
}
|
||
|
||
self->wifi_connecting_ = false;
|
||
self->delayed_disconnect_ = false;
|
||
|
||
if (s_wifi_event_group) {
|
||
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||
}
|
||
|
||
// 发送MAC地址
|
||
ESP_LOGI(TAG, "🔍 [DEBUG] 检查客户端连接状态: client_connected_=%s",
|
||
self->client_connected_ ? "true" : "false");
|
||
if (self && self->client_connected_) {
|
||
ESP_LOGI(TAG, "🔍 [DEBUG] 使用专用函数发送设备MAC地址...");
|
||
bool mac_sent = self->SendMacAddressReliably();
|
||
if (mac_sent) {
|
||
ESP_LOGI(TAG, "✅ 设备MAC地址发送成功");
|
||
} else {
|
||
ESP_LOGW(TAG, "⚠️ 设备MAC地址发送失败");
|
||
}
|
||
|
||
ESP_LOGI(TAG, "🔍 [DEBUG] 已跳过WiFi连接报告发送,仅发送设备MAC地址");
|
||
} else {
|
||
ESP_LOGW(TAG, "🔍 [DEBUG] 无法发送: client_connected_=%s",
|
||
self->client_connected_ ? "true" : "false");
|
||
}
|
||
|
||
// 启用WiFi配置自动保存到NVS存储
|
||
ESP_LOGI(TAG, "💾 启用WiFi配置自动保存到NVS存储...");
|
||
esp_err_t storage_ret = esp_wifi_set_storage(WIFI_STORAGE_FLASH);
|
||
if (storage_ret == ESP_OK) {
|
||
ESP_LOGI(TAG, "✅ WiFi配置将自动保存到NVS存储");
|
||
} else {
|
||
ESP_LOGW(TAG, "⚠️ 设置WiFi存储模式失败: %s", esp_err_to_name(storage_ret));
|
||
}
|
||
|
||
// 手动获取当前WiFi配置并保存到NVS列表
|
||
wifi_config_t wifi_config;
|
||
esp_err_t get_config_ret = esp_wifi_get_config(WIFI_IF_STA, &wifi_config);
|
||
if (get_config_ret == ESP_OK) {
|
||
ESP_LOGI(TAG, "📋 获取当前WiFi配置成功,SSID: %s", wifi_config.sta.ssid);
|
||
auto& ssid_manager = SsidManager::GetInstance();
|
||
ssid_manager.AddSsid((const char*)wifi_config.sta.ssid, (const char*)wifi_config.sta.password);
|
||
ESP_LOGI(TAG, "✅ WiFi凭据已保存到NVS列表");
|
||
} else {
|
||
ESP_LOGW(TAG, "⚠️ 获取当前WiFi配置失败: %s", esp_err_to_name(get_config_ret));
|
||
}
|
||
|
||
auto& application = Application::GetInstance();
|
||
bool skip_session = application.ShouldSkipDialogIdleSession();
|
||
ESP_LOGI(TAG, "BluetoothProvisioning WIFI_CONNECTED skip_session=%d", (int)skip_session);
|
||
if (!skip_session) {
|
||
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);
|
||
}
|
||
} else {
|
||
application.ClearDialogIdleSkipSession();
|
||
}
|
||
|
||
// 更新状态和触发回调
|
||
ESP_LOGI(TAG, "🔍 准备设置状态为SUCCESS并触发回调");
|
||
self->SetState(BluetoothProvisioningState::SUCCESS);
|
||
self->TriggerCallback(BluetoothProvisioningEvent::WIFI_CONNECTED, &event->ip_info.ip);
|
||
self->ReportWiFiStatus(true, 0);
|
||
|
||
ESP_LOGI(TAG, "📋 配网流程完成,状态: %s, client_connected_: %s",
|
||
self->GetStateString().c_str(),
|
||
self->client_connected_ ? "true" : "false");
|
||
|
||
// 延迟2000ms后强制重启设备
|
||
ESP_LOGI(TAG, "⏰ 延迟2000ms后重启设备以确保配置生效...");
|
||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||
ESP_LOGI(TAG, "🔄 强制重启设备...");
|
||
esp_restart();
|
||
break;
|
||
}
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|