diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 0a82a35..20e58c5 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -349,7 +349,7 @@ menu "蓝牙配网 (Bluetooth Provisioning)" config BLUETOOTH_PROVISIONING_DEVICE_NAME string "默认设备名称" depends on BLUETOOTH_PROVISIONING_ENABLE - default "BLUFI_Airhub" + default "Airhub_Ble" help 蓝牙配网时显示的默认设备名称。 可以在运行时通过API修改。 diff --git a/main/application.cc b/main/application.cc index 91c35b6..e8cb8c6 100644 --- a/main/application.cc +++ b/main/application.cc @@ -1574,8 +1574,7 @@ void Application::Start() { SetDeviceState(kDeviceStateIdle); - // 初始化 BLE JSON 通讯服务 - InitBleJsonService(); + // BLE JSON 通讯服务已移至 WifiBoard 中,仅在配网模式下启动 // 每次设备开机后idle状态下测试 自动检测并设置当前位置打印 //此逻辑为冗余操作,当前NVS中没有城市信息时会自动调用 位置查询API @@ -3052,33 +3051,12 @@ const char* Application::DeviceStateToString(DeviceState state) { return "unknown"; } -void Application::InitBleJsonService() { - ESP_LOGI(TAG, "初始化 BLE JSON 通讯服务..."); - - if (!ble_json_service_.Initialize()) { - ESP_LOGE(TAG, "BLE JSON 服务初始化失败"); - return; - } - - ble_json_service_.SetCommandCallback( - [this](const std::string& cmd, int msg_id, cJSON* data) { - HandleBleJsonCommand(cmd, msg_id, data); - }); - - if (!ble_json_service_.Start()) { - ESP_LOGE(TAG, "BLE JSON 服务启动失败"); - return; - } - - ESP_LOGI(TAG, "BLE JSON 通讯服务启动成功"); -} - -void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON* data) { +void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON* data, BleJsonService& service) { auto& board = Board::GetInstance(); // ---- ping ---- if (cmd == "ping") { - ble_json_service_.SendResponse(cmd, msg_id, 0, "pong"); + service.SendResponse(cmd, msg_id, 0, "pong"); return; } @@ -3106,7 +3084,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON cJSON_AddNumberToObject(resp, "rssi", ap.rssi); } - ble_json_service_.SendResponse(cmd, msg_id, 0, "ok", resp); + service.SendResponse(cmd, msg_id, 0, "ok", resp); cJSON_Delete(resp); return; } @@ -3121,7 +3099,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON cJSON_AddStringToObject(resp, "chip", SystemInfo::GetChipModelName().c_str()); cJSON_AddStringToObject(resp, "idf", app_desc->idf_ver); - ble_json_service_.SendResponse(cmd, msg_id, 0, "ok", resp); + service.SendResponse(cmd, msg_id, 0, "ok", resp); cJSON_Delete(resp); return; } @@ -3131,7 +3109,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON cJSON* ssid_item = cJSON_GetObjectItem(data, "ssid"); cJSON* pwd_item = cJSON_GetObjectItem(data, "pwd"); if (!ssid_item || !cJSON_IsString(ssid_item) || strlen(ssid_item->valuestring) == 0) { - ble_json_service_.SendResponse(cmd, msg_id, -1, "missing ssid"); + service.SendResponse(cmd, msg_id, -1, "missing ssid"); return; } @@ -3145,14 +3123,14 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON esp_err_t ret = esp_wifi_set_config(WIFI_IF_STA, &wifi_config); if (ret != ESP_OK) { - ble_json_service_.SendResponse(cmd, msg_id, -2, "set config failed"); + service.SendResponse(cmd, msg_id, -2, "set config failed"); return; } // 断开当前连接并重新连接 esp_wifi_disconnect(); ret = esp_wifi_connect(); - ble_json_service_.SendResponse(cmd, msg_id, 0, + service.SendResponse(cmd, msg_id, 0, ret == ESP_OK ? "connecting" : "connect failed"); return; } @@ -3163,7 +3141,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON scan_config.show_hidden = false; esp_err_t ret = esp_wifi_scan_start(&scan_config, true); // 阻塞扫描 if (ret != ESP_OK) { - ble_json_service_.SendResponse(cmd, msg_id, -1, "scan failed"); + service.SendResponse(cmd, msg_id, -1, "scan failed"); return; } @@ -3192,7 +3170,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON free(ap_list); } - ble_json_service_.SendResponse(cmd, msg_id, 0, "ok", resp); + service.SendResponse(cmd, msg_id, 0, "ok", resp); cJSON_Delete(resp); return; } @@ -3201,7 +3179,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON if (cmd == "set_vol") { cJSON* vol_item = cJSON_GetObjectItem(data, "vol"); if (!vol_item || !cJSON_IsNumber(vol_item)) { - ble_json_service_.SendResponse(cmd, msg_id, -1, "missing vol"); + service.SendResponse(cmd, msg_id, -1, "missing vol"); return; } int vol = vol_item->valueint; @@ -3216,13 +3194,13 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON s.SetInt("output_volume", vol); } - ble_json_service_.SendResponse(cmd, msg_id, 0, "ok"); + service.SendResponse(cmd, msg_id, 0, "ok"); return; } // ---- reboot: 重启设备 ---- if (cmd == "reboot") { - ble_json_service_.SendResponse(cmd, msg_id, 0, "rebooting"); + service.SendResponse(cmd, msg_id, 0, "rebooting"); vTaskDelay(pdMS_TO_TICKS(500)); // 等待响应发出 Reboot(); return; @@ -3231,10 +3209,10 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON // ---- ota: 触发 OTA 升级 ---- if (cmd == "ota") { if (device_state_ == kDeviceStateUpgrading) { - ble_json_service_.SendResponse(cmd, msg_id, -1, "already upgrading"); + service.SendResponse(cmd, msg_id, -1, "already upgrading"); return; } - ble_json_service_.SendResponse(cmd, msg_id, 0, "start ota"); + service.SendResponse(cmd, msg_id, 0, "start ota"); Schedule([this]() { CheckNewVersion(); }); @@ -3247,14 +3225,14 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON std::string states; if (thing_manager.GetStatesJson(states, true)) { cJSON* resp = cJSON_Parse(states.c_str()); - ble_json_service_.SendResponse(cmd, msg_id, 0, "ok", resp); + service.SendResponse(cmd, msg_id, 0, "ok", resp); if (resp) cJSON_Delete(resp); } else { - ble_json_service_.SendResponse(cmd, msg_id, 0, "ok"); + service.SendResponse(cmd, msg_id, 0, "ok"); } return; } // ---- 未知命令 ---- - ble_json_service_.SendResponse(cmd, msg_id, -99, "unknown cmd"); + service.SendResponse(cmd, msg_id, -99, "unknown cmd"); } diff --git a/main/application.h b/main/application.h index b9ee208..e6d9aba 100644 --- a/main/application.h +++ b/main/application.h @@ -109,6 +109,9 @@ public: bool IsDialogUploadEnabled() const { return dialog_upload_enabled_; }// 是否启用对话上传 void SetDialogUploadEnabled(bool enabled);// 设置对话上传状态 + // BLE JSON 命令处理 (由 WifiBoard 中的 BleJsonService 回调) + void HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON* data, BleJsonService& service); + private: Application();// 构造函数 ~Application();// 析构函数 @@ -193,10 +196,6 @@ private: void StartDialogWatchdog();// 启动对话看门狗 void StopDialogWatchdog(); // 停止对话看门狗 - // BLE JSON 通讯服务 - BleJsonService ble_json_service_; - void InitBleJsonService(); // 初始化 BLE JSON 通讯 - void HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON* data); // 处理 BLE 命令 const char* DeviceStateToString(DeviceState state); // 状态枚举转字符串 }; diff --git a/main/audio_codecs/audio_codec.h b/main/audio_codecs/audio_codec.h index 734c005..e6c0d9d 100644 --- a/main/audio_codecs/audio_codec.h +++ b/main/audio_codecs/audio_codec.h @@ -16,7 +16,7 @@ public: AudioCodec(); virtual ~AudioCodec(); - static constexpr int kDefaultOutputVolume = 30; // 默认输出音量 系统默认音量设置为100(最大音量),原来为70 产测固件使用 + static constexpr int kDefaultOutputVolume = 40; // 默认输出音量 系统默认音量设置为100(最大音量),原来为70 产测固件使用 inline static int default_output_volume() { return kDefaultOutputVolume; } virtual void SetOutputVolume(int volume); diff --git a/main/ble_service.cc b/main/ble_service.cc index 694cde4..29c4947 100644 --- a/main/ble_service.cc +++ b/main/ble_service.cc @@ -61,19 +61,19 @@ static uint8_t cccd_val[2] = {0x00, 0x00}; static esp_attr_value_t write_char_attr = { .attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN, - .attr_len = 0, + .attr_len = 1, .attr_value = write_char_val, }; static esp_attr_value_t notify_char_attr = { .attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN, - .attr_len = 0, + .attr_len = 1, .attr_value = notify_char_val, }; static esp_attr_value_t status_char_attr = { .attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN, - .attr_len = 0, + .attr_len = 1, .attr_value = status_char_val, }; @@ -354,10 +354,10 @@ bool BleJsonService::SendNotify(const char* json_str, uint16_t len) { // ============================================================ void BleJsonService::StartAdvertising() { - // 构建广播数据 + // 广播包:不放名称,避免超过31字节导致手机系统蓝牙搜索不到 esp_ble_adv_data_t adv_data = {}; adv_data.set_scan_rsp = false; - adv_data.include_name = true; + adv_data.include_name = false; // 名称放在 Scan Response 中 adv_data.include_txpower = true; adv_data.min_interval = 0x0006; adv_data.max_interval = 0x0010; @@ -372,7 +372,7 @@ void BleJsonService::StartAdvertising() { esp_ble_gap_config_adv_data(&adv_data); - // Scan response 中也加设备名 + // Scan Response:放完整设备名称,手机扫描时会主动请求 esp_ble_adv_data_t scan_rsp = {}; scan_rsp.set_scan_rsp = true; scan_rsp.include_name = true; @@ -388,12 +388,20 @@ void BleJsonService::GapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) { switch (event) { case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: - // 广播数据设置完成,等扫描应答数据也完成后再启动广播 - break; - case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: - // 扫描应答数据设置完成,启动广播 - esp_ble_gap_start_advertising(&ble_json_adv_params); + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: { + // 两个事件都到达后才启动广播 + static uint8_t adv_config_done = 0; + if (event == ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT) { + adv_config_done |= 0x01; + } else { + adv_config_done |= 0x02; + } + if (adv_config_done == 0x03) { + adv_config_done = 0; // 重置,供下次重新广播使用 + esp_ble_gap_start_advertising(&ble_json_adv_params); + } break; + } case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(TAG, "Advertising start failed: %d", param->adv_start_cmpl.status); @@ -531,7 +539,7 @@ void BleJsonService::HandleGattsEvent(esp_gatts_cb_event_t event, break; // ---- 客户端连接 ---- - case ESP_GATTS_CONNECT_EVT: + case ESP_GATTS_CONNECT_EVT: { conn_id_ = param->connect.conn_id; connected_ = true; notify_enabled_ = false; @@ -555,6 +563,7 @@ void BleJsonService::HandleGattsEvent(esp_gatts_cb_event_t event, // 连接后停止广播 (BLE 4.2 单连接时自动停止,但显式调用更安全) esp_ble_gap_stop_advertising(); break; + } // ---- 客户端断开 ---- case ESP_GATTS_DISCONNECT_EVT: diff --git a/main/ble_service.h b/main/ble_service.h index f9bb00e..17e4038 100644 --- a/main/ble_service.h +++ b/main/ble_service.h @@ -6,6 +6,7 @@ #ifdef ESP_PLATFORM #include +#include #include #include #endif diff --git a/main/ble_service_config.h b/main/ble_service_config.h index 74eeddf..d9cd819 100644 --- a/main/ble_service_config.h +++ b/main/ble_service_config.h @@ -29,7 +29,7 @@ extern "C" { #define BLE_JSON_CHAR_VAL_MAX_LEN 512 // 广播设备名称 -#define BLE_JSON_DEVICE_NAME "Kapi_BLE" +#define BLE_JSON_DEVICE_NAME "Airhub_Ble" // 广播参数 #define BLE_JSON_ADV_INT_MIN 0x40 // 40ms diff --git a/main/bluetooth_provisioning.cc b/main/bluetooth_provisioning.cc index 1d91352..190bc3b 100644 --- a/main/bluetooth_provisioning.cc +++ b/main/bluetooth_provisioning.cc @@ -1,16 +1,17 @@ /** * @file bluetooth_provisioning.cc - * @brief BluFi蓝牙配网模块实现文件 - * - * 本文件实现了BluFi蓝牙配网的核心功能,包括: - * - 蓝牙控制器和协议栈的初始化与管理 - * - BluFi服务的启动、停止和事件处理 - * - WiFi凭据的接收、验证和连接管理 - * - 配网状态机的管理和事件回调 - * - WiFi连接状态的监控和报告 - * - 配网成功后的自动保存和重启机制 - * - * 该实现基于ESP-IDF的BluFi API,提供了完整的蓝牙配网解决方案。 + * @brief 蓝牙配网模块实现(基于自定义 GATT Server) + * + * 使用自定义 BLE GATT Server + 原始广播数据(raw advertising)替代 BluFi API。 + * 采用 dzbj 项目验证的 esp_ble_gap_config_adv_data_raw() 方式, + * 确保手机系统蓝牙可搜索到设备名称。 + * + * 保留完整的 WiFi 配网业务逻辑: + * - WiFi 凭据接收、验证和连接管理 + * - 配网状态机和事件回调 + * - WiFi 连接状态监控和报告 + * - MAC 地址发送 + * - 配网成功后自动保存和重启 */ #include "bluetooth_provisioning.h" @@ -19,8 +20,8 @@ #include "esp_bt_main.h" #include "esp_bt_device.h" #include "esp_gap_ble_api.h" -#include "esp_blufi.h" -#include "esp_blufi_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" @@ -31,11 +32,10 @@ #include "assets/lang_config.h" #include #include "nvs_flash.h" - #include "nvs.h" #include -/// 日志标签,用于ESP_LOG系列函数的日志输出 +/// 日志标签 #define TAG "BluetoothProvisioning" /// WiFi连接成功事件位 @@ -43,91 +43,119 @@ /// WiFi连接失败事件位 #define WIFI_FAIL_BIT BIT1 -/// 静态单例实例指针,用于C回调函数访问类成员 +/// 静态单例实例指针 BluetoothProvisioning* BluetoothProvisioning::instance_ = nullptr; -/// WiFi事件组句柄,用于同步WiFi连接状态 +/// WiFi事件组句柄 static EventGroupHandle_t s_wifi_event_group = nullptr; /// WiFi连接重试计数器 static int s_retry_num = 0; /// 最大重试次数 -static const int MAX_RETRY = 2;//Wi-Fi连接最大重试次数(wifi连接失败最大重试次数) +static const int MAX_RETRY = 2; /// WiFi连接超时时间(毫秒) -static const int WIFI_CONNECT_TIMEOUT_MS = 30000; // 增强:从15秒延长到30秒 +static const int WIFI_CONNECT_TIMEOUT_MS = 30000; /// WiFi连接超时定时器句柄 static TimerHandle_t wifi_connect_timer = nullptr; -/** - * @brief BLUFI回调函数配置结构体 - * - * 配置BluFi服务的各种回调函数,包括事件处理、数据协商、 - * 加密解密和校验等功能。当前实现仅使用事件回调。 - */ -static esp_blufi_callbacks_t blufi_callbacks = { - .event_cb = BluetoothProvisioning::BlufiEventCallback, ///< 事件回调函数 - .negotiate_data_handler = nullptr, ///< 数据协商处理器(可选) - .encrypt_func = nullptr, ///< 加密函数(可选) - .decrypt_func = nullptr, ///< 解密函数(可选) - .checksum_func = nullptr, ///< 校验函数(可选) +// ============================================================ +// GATT 静态数据定义 +// ============================================================ + +// UUID 定义 +static esp_bt_uuid_t prov_service_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = PROV_SERVICE_UUID}, }; -/** - * @brief 构造函数 - * - * 初始化蓝牙配网对象的所有成员变量,设置初始状态, - * 清空WiFi凭据,并设置静态实例指针用于回调函数访问。 - */ -BluetoothProvisioning::BluetoothProvisioning() - : state_(BluetoothProvisioningState::IDLE) ///< 初始状态为空闲 - , callback_(nullptr) ///< 回调函数指针初始化为空 - , client_connected_(false) ///< 客户端连接状态初始化为未连接 - , initialized_(false) ///< 初始化状态标志为未初始化 - , delayed_disconnect_(false) ///< 延迟断开标志初始化为false - , wifi_connecting_(false) ///< WiFi连接状态标志初始化为false - , mac_address_sent_(false) { ///< MAC地址发送状态初始化为未发送 +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; + +// ============================================================ +// 构造 / 析构 +// ============================================================ + +BluetoothProvisioning::BluetoothProvisioning() + : state_(BluetoothProvisioningState::IDLE) + , callback_(nullptr) + , client_connected_(false) + , initialized_(false) + , delayed_disconnect_(false) + , wifi_connecting_(false) + , mac_address_sent_(false) { - // 清空WiFi凭据结构体 wifi_credentials_.ssid.clear(); wifi_credentials_.password.clear(); memset(wifi_credentials_.bssid, 0, sizeof(wifi_credentials_.bssid)); wifi_credentials_.bssid_set = false; - // 设置静态实例指针,用于C风格回调函数访问类成员 instance_ = this; - ESP_LOGI(TAG, "蓝牙配网对象创建完成"); } -/** - * @brief 析构函数 - * - * 清理蓝牙配网对象的所有资源,包括停止配网服务、 - * 释放蓝牙资源、清空静态实例指针等。 - */ BluetoothProvisioning::~BluetoothProvisioning() { - // 确保资源被正确释放,如果已初始化则进行反初始化 if (initialized_) { Deinitialize(); } - - // 清空静态实例指针 instance_ = nullptr; ESP_LOGI(TAG, "蓝牙配网对象销毁完成"); } -/** - * @brief 初始化蓝牙配网功能 - * - * 按顺序初始化以下组件: - * 1. WiFi模块(STA模式) - * 2. 蓝牙控制器 - * 3. Bluedroid协议栈 - * 4. BluFi服务和回调 - * 5. WiFi事件处理器 - * - * @return true 初始化成功,false 初始化失败 - */ +// ============================================================ +// Initialize — 初始化 BLE 栈 + GATT 服务 + WiFi 事件 +// ============================================================ + bool BluetoothProvisioning::Initialize() { if (initialized_) { ESP_LOGW(TAG, "蓝牙配网已经初始化"); @@ -135,16 +163,12 @@ bool BluetoothProvisioning::Initialize() { } SetState(BluetoothProvisioningState::INITIALIZING); - esp_err_t ret; // 步骤1: 初始化WiFi模块 ESP_LOGI(TAG, "初始化WiFi..."); - - // 创建默认WiFi STA网络接口 esp_netif_create_default_wifi_sta(); - // 使用默认配置初始化WiFi wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ret = esp_wifi_init(&cfg); if (ret != ESP_OK) { @@ -153,7 +177,6 @@ bool BluetoothProvisioning::Initialize() { return false; } - // 设置WiFi工作模式为STA(Station)模式 ret = esp_wifi_set_mode(WIFI_MODE_STA); if (ret != ESP_OK) { ESP_LOGE(TAG, "WiFi模式设置失败: %s", esp_err_to_name(ret)); @@ -161,16 +184,15 @@ bool BluetoothProvisioning::Initialize() { return false; } - // 启动WiFi服务 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 @@ -199,6 +221,7 @@ bool BluetoothProvisioning::Initialize() { return false; } + // 步骤3: 初始化 Bluedroid 协议栈 ESP_LOGI(TAG, "初始化Bluedroid协议栈..."); auto bd_status = esp_bluedroid_get_status(); if (bd_status == ESP_BLUEDROID_STATUS_ENABLED) { @@ -221,35 +244,35 @@ bool BluetoothProvisioning::Initialize() { return false; } - // 注意:设备名称将在StartProvisioning中设置 - ESP_LOGI(TAG, "蓝牙初始化完成,设备名称将在启动配网时设置"); + // 步骤4: 注册 GAP 和 GATTS 回调 (替代 BluFi 回调) + ESP_LOGI(TAG, "注册 BLE GAP/GATTS 回调..."); - // 步骤4: 注册BluFi服务和回调函数 - ESP_LOGI(TAG, "注册BLUFI回调函数..."); - - // 注册BluFi事件回调函数 - ret = esp_blufi_register_callbacks(&blufi_callbacks); + ret = esp_ble_gap_register_callback(GapEventHandler); if (ret != ESP_OK) { - ESP_LOGE(TAG, "BLUFI回调注册失败: %s", esp_err_to_name(ret)); + ESP_LOGE(TAG, "GAP回调注册失败: %s", esp_err_to_name(ret)); SetState(BluetoothProvisioningState::FAILED); return false; } - // 注册BLE GAP(Generic Access Profile)事件处理器 - ret = esp_ble_gap_register_callback(esp_blufi_gap_event_handler); + ret = esp_ble_gatts_register_callback(GattsEventHandler); if (ret != ESP_OK) { - ESP_LOGE(TAG, "BLE GAP事件处理器注册失败: %s", esp_err_to_name(ret)); + ESP_LOGE(TAG, "GATTS回调注册失败: %s", esp_err_to_name(ret)); SetState(BluetoothProvisioningState::FAILED); return false; } - ret = esp_blufi_profile_init(); - if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { - ESP_LOGE(TAG, "BLUFI配置文件初始化失败: %s", esp_err_to_name(ret)); + 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(); @@ -263,7 +286,6 @@ bool BluetoothProvisioning::Initialize() { // 步骤6: 注册WiFi和IP事件处理器 ESP_LOGI(TAG, "注册WiFi事件处理器..."); - // 注册WiFi事件处理器,监听所有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)); @@ -271,7 +293,6 @@ bool BluetoothProvisioning::Initialize() { return false; } - // 注册IP事件处理器,监听IP地址获取事件 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)); @@ -279,29 +300,19 @@ bool BluetoothProvisioning::Initialize() { return false; } - // 标记初始化完成,设置状态为空闲 initialized_ = true; SetState(BluetoothProvisioningState::IDLE); - ESP_LOGI(TAG, "蓝牙配网初始化完成"); + ESP_LOGI(TAG, "蓝牙配网初始化完成 (GATT Server 模式)"); ESP_LOGI(TAG, "蓝牙MAC地址: " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(esp_bt_dev_get_address())); return true; } -/** - * @brief 反初始化蓝牙配网功能 - * - * 按相反顺序清理所有初始化的组件和资源: - * 1. 停止配网服务 - * 2. 注销事件处理器 - * 3. 销毁WiFi事件组 - * 4. 反初始化BluFi服务 - * 5. 反初始化Bluedroid协议栈 - * 6. 反初始化蓝牙控制器 - * - * @return true 反初始化成功,false 反初始化失败 - */ +// ============================================================ +// Deinitialize +// ============================================================ + bool BluetoothProvisioning::Deinitialize() { if (!initialized_) { ESP_LOGW(TAG, "蓝牙配网未初始化"); @@ -310,43 +321,37 @@ bool BluetoothProvisioning::Deinitialize() { ESP_LOGI(TAG, "开始反初始化蓝牙配网..."); - // 步骤1: 停止配网服务(如果正在运行) - if (state_ != BluetoothProvisioningState::IDLE && + if (state_ != BluetoothProvisioningState::IDLE && state_ != BluetoothProvisioningState::STOPPED) { StopProvisioning(); } - // 步骤2: 注销事件处理器 esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &WiFiEventHandler); esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &IPEventHandler); - // 步骤3: 销毁WiFi事件同步机制 if (s_wifi_event_group != nullptr) { vEventGroupDelete(s_wifi_event_group); s_wifi_event_group = nullptr; } - // 步骤4: 反初始化BluFi服务 - esp_blufi_profile_deinit(); - - // 步骤5: 反初始化Bluedroid协议栈 esp_bluedroid_disable(); esp_bluedroid_deinit(); - - // 步骤6: 反初始化蓝牙控制器 esp_bt_controller_disable(); esp_bt_controller_deinit(); - // 标记为未初始化状态 initialized_ = false; SetState(BluetoothProvisioningState::STOPPED); ESP_LOGI(TAG, "蓝牙配网反初始化完成"); return true; } -// 开始配网 + +// ============================================================ +// StartProvisioning — 构建原始广播数据并启动广播 +// ============================================================ + bool BluetoothProvisioning::StartProvisioning(const char* device_name) { - ESP_LOGI(TAG, "🔵 开始启动BluFi配网服务..."); + ESP_LOGI(TAG, "🔵 开始启动蓝牙配网服务 (GATT Server)..."); ESP_LOGI(TAG, "🔍 检查初始化状态: initialized_ = %s", initialized_ ? "true" : "false"); if (!initialized_) { @@ -367,7 +372,6 @@ bool BluetoothProvisioning::StartProvisioning(const char* device_name) { client_connected_ = false; s_retry_num = 0; - // 重置MAC地址发送状态,为新的配网会话做准备 ResetMacSendingState(); ESP_LOGI(TAG, "🔄 MAC地址发送状态已重置"); @@ -377,85 +381,67 @@ bool BluetoothProvisioning::StartProvisioning(const char* device_name) { ESP_LOGI(TAG, "🗑️ 删除已保存的SSID: %s", wifi_credentials_.ssid.c_str()); } if (!wifi_credentials_.password.empty()) { - ESP_LOGI(TAG, "🗑️ 删除已保存的WiFi密码 (长度: %d)", wifi_credentials_.password.length()); + 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凭据清除完成,准备接收新的配网信息"); - // 开始BLUFI广播 - esp_blufi_adv_start(); - ESP_LOGI(TAG, "BLUFI广播已启动"); + // 设置设备名称 + esp_err_t ret = esp_ble_gap_set_device_name(device_name); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "❌ 设置蓝牙设备名称失败: %s", esp_err_to_name(ret)); + return false; + } - // // 配置自定义广播数据包以确保设备名称正确显示(改蓝牙名称 必备操作) - // //===================================================================================================== - // esp_ble_adv_data_t adv_data = {}; - // adv_data.set_scan_rsp = false; - // adv_data.include_name = true; // 包含设备名称 - // adv_data.include_txpower = true; - // adv_data.min_interval = 0x0006; - // adv_data.max_interval = 0x0010; - // adv_data.appearance = 0x00; + // 构建原始广播数据 (dzbj 方式: 直接放入设备名,手机系统蓝牙可搜索) + uint8_t name_len = strlen(device_name); + int offset = 0; - // // 添加厂商特定数据 - // static uint8_t manufacturer_data[] = {0xFF, 0xFF, 0x00, 0x00}; // ESP厂商ID - // adv_data.manufacturer_len = sizeof(manufacturer_data); - // adv_data.p_manufacturer_data = manufacturer_data; + // 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; - // adv_data.service_data_len = 0; - // adv_data.p_service_data = NULL; - // // 添加BluFi服务UUID,确保ESP官方APP能够识别 - // // UUID: 0000FFFF-0000-1000-8000-00805F9B34FB (小端序) - // static uint8_t blufi_service_uuid128[16] = { - // 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x10, - // 0x00, 0x80, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb - // }; - // adv_data.service_uuid_len = sizeof(blufi_service_uuid128); - // adv_data.p_service_uuid = blufi_service_uuid128; - // adv_data.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT); - // //=================================================== + // 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], device_name, name_len); + offset += name_len; - // // 重新设置设备名称并配置广播数据 - // esp_err_t ret = esp_ble_gap_set_device_name(device_name); - // if (ret != ESP_OK) { - // ESP_LOGE(TAG, "❌ 设置蓝牙设备名称失败: %s", esp_err_to_name(ret)); - // return false; - // } + // TX Power Level + prov_adv_raw_data[offset++] = 0x02; + prov_adv_raw_data[offset++] = ESP_BLE_AD_TYPE_TX_PWR; + prov_adv_raw_data[offset++] = 0x09; - // ret = esp_ble_gap_config_adv_data(&adv_data); - // if (ret != ESP_OK) { - // ESP_LOGE(TAG, "❌ 配置广播数据失败: %s", esp_err_to_name(ret)); - // return false; - // } + // 16-bit Service UUID Complete + prov_adv_raw_data[offset++] = 0x03; + prov_adv_raw_data[offset++] = ESP_BLE_AD_TYPE_16SRV_CMPL; + prov_adv_raw_data[offset++] = PROV_SERVICE_UUID & 0xFF; + prov_adv_raw_data[offset++] = (PROV_SERVICE_UUID >> 8) & 0xFF; - // // 配置广播参数(按官方示例设置) - // //================================================== - // esp_ble_adv_params_t adv_params = {}; - // adv_params.adv_int_min = 0x100; // 100ms间隔 - // adv_params.adv_int_max = 0x100; // 100ms间隔 - // adv_params.adv_type = ADV_TYPE_IND; - // adv_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; - // adv_params.channel_map = ADV_CHNL_ALL; - // adv_params.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; - // // 配置完所有广播数据后再启动广播 - // //================================================== + prov_adv_raw_len = offset; - // ret = esp_ble_gap_start_advertising(&adv_params); - // if (ret != ESP_OK) { - // ESP_LOGE(TAG, "❌ 启动广播失败: %s", esp_err_to_name(ret)); - // return false; - // } - // ESP_LOGI(TAG, "✅ 蓝牙设备名称和广播数据配置成功: %s", device_name); - // //===================================================================================================== + ESP_LOGI(TAG, "📡 原始广播数据构建完成,长度: %d 字节", prov_adv_raw_len); - SetState(BluetoothProvisioningState::ADVERTISING);// 设置蓝牙状态为广播中 + // 配置原始广播数据 (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) { @@ -465,48 +451,524 @@ bool BluetoothProvisioning::StopProvisioning() { ESP_LOGI(TAG, "停止蓝牙配网..."); - // 停止BLUFI广播 - esp_blufi_adv_stop(); + esp_ble_gap_stop_advertising(); - // 如果有客户端连接,断开连接 - if (client_connected_) { - esp_blufi_disconnect(); + 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; } -// 向客户端/小程序 报告WiFi连接状态 +// ============================================================ +// 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: + // 原始广播数据设置完成,启动广播 (dzbj 方式) + 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(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(payload), payload_len); + { + wifi_config_t wifi_config = {}; + strncpy(reinterpret_cast(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(payload), payload_len); + { + wifi_config_t wifi_config = {}; + strncpy(reinterpret_cast(wifi_config.sta.ssid), + wifi_credentials_.ssid.c_str(), + sizeof(wifi_config.sta.ssid) - 1); + strncpy(reinterpret_cast(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(wifi_config.sta.ssid), + wifi_credentials_.ssid.c_str(), + sizeof(wifi_config.sta.ssid) - 1); + strncpy(reinterpret_cast(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", + 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; } - wifi_mode_t mode; - esp_wifi_get_mode(&mode);//获取当前WiFi模式 + 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成功!"); - esp_err_t ret = esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, nullptr); - ESP_LOGI(TAG, "🔍 [DEBUG] WiFi成功报告发送结果: %s", esp_err_to_name(ret)); } else { ESP_LOGI(TAG, "向客户端报告连接WiFi失败,原因: %d", reason); - esp_blufi_extra_info_t info; - memset(&info, 0, sizeof(info)); - info.sta_conn_end_reason_set = true; - info.sta_conn_end_reason = reason; - esp_err_t ret = esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, &info); - ESP_LOGI(TAG, "🔍 [DEBUG] WiFi失败报告发送结果: %s", esp_err_to_name(ret)); } } -// 将WI-FI扫描列表 发送给客户端(Wi-Fi扫描) +// ============================================================ +// 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列表"); @@ -515,35 +977,47 @@ void BluetoothProvisioning::SendWiFiList(const wifi_ap_record_t* ap_list, uint16 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; } - // 转换为BLUFI格式 - esp_blufi_ap_record_t* blufi_ap_list = new esp_blufi_ap_record_t[ap_count]; - if (blufi_ap_list == nullptr) { - ESP_LOGE(TAG, "内存分配失败"); - return; - } - - for (uint16_t i = 0; i < ap_count; i++) { - blufi_ap_list[i].rssi = ap_list[i].rssi; - memcpy(blufi_ap_list[i].ssid, ap_list[i].ssid, sizeof(ap_list[i].ssid)); - } - ESP_LOGI(TAG, "向客户端发送WiFi列表,共%d个AP", ap_count); - esp_blufi_send_wifi_list(ap_count, blufi_ap_list); - delete[] blufi_ap_list; + if (notify_enabled_) { + for (uint16_t i = 0; i < ap_count; i++) { + uint8_t ssid_len = strlen(reinterpret_cast(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; } - // 获取设备MAC地址 uint8_t mac[6]; esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, mac); if (ret != ESP_OK) { @@ -551,12 +1025,10 @@ bool BluetoothProvisioning::SendMacAddressReliably() { return false; } - // 格式化MAC地址字符串 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]); - // 检查是否已经发送过MAC地址(WiFi MAC地址不会因重启而变化) if (mac_address_sent_) { ESP_LOGI(TAG, "MAC地址已发送过,跳过重复发送: %s", mac_str); return true; @@ -564,12 +1036,10 @@ bool BluetoothProvisioning::SendMacAddressReliably() { 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; @@ -577,24 +1047,29 @@ bool BluetoothProvisioning::SendMacAddressReliably() { ESP_LOGI(TAG, "发送MAC地址尝试 %d/%d: %s", attempt, MAX_SEND_ATTEMPTS, mac_str); - // 创建包含MAC地址的自定义数据(还原原有格式) + // 构建自定义数据包 (保留原有格式) char mac_data[32]; - snprintf(mac_data, sizeof(mac_data), "STA_MAC:%02x:%02x:%02x:%02x:%02x:%02x", + 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]); - // 发送带前缀的MAC地址数据 - ret = esp_blufi_send_custom_data((uint8_t*)mac_data, strlen(mac_data)); + 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); - if (ret == ESP_OK) { + 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; @@ -603,10 +1078,8 @@ bool BluetoothProvisioning::SendMacAddressReliably() { return false; } } else { - ESP_LOGW(TAG, "❌ MAC地址发送失败 (尝试 %d/%d): %s, 错误: %s", - attempt, MAX_SEND_ATTEMPTS, mac_str, esp_err_to_name(ret)); - - // 如果不是最后一次尝试,等待后重试 + 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)); } @@ -622,18 +1095,22 @@ void BluetoothProvisioning::ResetMacSendingState() { 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", + "IDLE", "INITIALIZING", "ADVERTISING", "CONNECTED", "PROVISIONING", "SUCCESS", "FAILED", "STOPPED" }; - ESP_LOGI(TAG, "🔄 BluFi状态变化: %s -> %s", - state_names[static_cast(old_state)], + ESP_LOGI(TAG, "🔄 配网状态变化: %s -> %s", + state_names[static_cast(old_state)], state_names[static_cast(new_state)]); TriggerCallback(BluetoothProvisioningEvent::STATE_CHANGED, nullptr); @@ -648,272 +1125,17 @@ void BluetoothProvisioning::TriggerCallback(BluetoothProvisioningEvent event, vo std::string BluetoothProvisioning::GetStateString() const { const char* state_names[] = { - "IDLE", "INITIALIZING", "ADVERTISING", "CONNECTED", + "IDLE", "INITIALIZING", "ADVERTISING", "CONNECTED", "PROVISIONING", "SUCCESS", "FAILED", "STOPPED" }; return std::string(state_names[static_cast(state_)]); } -// BLUFI事件回调函数 -void BluetoothProvisioning::BlufiEventCallback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t* param) { - if (instance_ == nullptr) { - ESP_LOGE(TAG, "实例指针为空"); - return; - } +// ============================================================ +// WiFi 事件处理 (保留原有业务逻辑) +// ============================================================ - // 打印事件详细信息 - const char* event_name = "UNKNOWN"; - switch (event) { - case ESP_BLUFI_EVENT_INIT_FINISH: event_name = "INIT_FINISH"; break; - case ESP_BLUFI_EVENT_DEINIT_FINISH: event_name = "DEINIT_FINISH"; break; - case ESP_BLUFI_EVENT_BLE_CONNECT: event_name = "BLE_CONNECT"; break; - case ESP_BLUFI_EVENT_BLE_DISCONNECT: event_name = "BLE_DISCONNECT"; break; - case ESP_BLUFI_EVENT_RECV_CUSTOM_DATA: event_name = "RECV_CUSTOM_DATA"; break; - case ESP_BLUFI_EVENT_RECV_STA_SSID: event_name = "RECV_STA_SSID"; break; - case ESP_BLUFI_EVENT_RECV_STA_PASSWD: event_name = "RECV_STA_PASSWD"; break; - case ESP_BLUFI_EVENT_GET_WIFI_LIST: event_name = "GET_WIFI_LIST"; break; - default: break; - } - // 打印事件参数 - ESP_LOGI(TAG, "🔔 BluFi事件回调: %d (%s), param=%p", event, event_name, param); - - switch (event) { - case ESP_BLUFI_EVENT_INIT_FINISH: - ESP_LOGI(TAG, "✅ BLUFI初始化完成"); - break; - - case ESP_BLUFI_EVENT_DEINIT_FINISH: - ESP_LOGI(TAG, "BLUFI反初始化完成"); - break; - - // 客户端连接事件 - case ESP_BLUFI_EVENT_BLE_CONNECT: - ESP_LOGI(TAG, "📱 BluFi客户端已连接");//GATT连接成功建立 - ESP_LOGI(TAG, "🔍 [DEBUG] 设置client_connected_为true"); - instance_->client_connected_ = true;//GATT连接成功建立,标志位设置为true - // 重置MAC地址发送状态,为新的配网会话做准备 - instance_->ResetMacSendingState(); - ESP_LOGI(TAG, "🔄 MAC地址发送状态已重置"); - instance_->SetState(BluetoothProvisioningState::CONNECTED); //GATT连接成功建立,状态设置为CONNECTED - instance_->TriggerCallback(BluetoothProvisioningEvent::CLIENT_CONNECTED, nullptr);//回调通知,通知上层应用有客户端连接 - - // // 在Wi-Fi连接前向客户端发送Mac地址 - // // 🆕 在BluFi客户端连接成功后立即发送MAC地址 - // ESP_LOGI(TAG, "📡 BluFi客户端连接成功,立即发送设备MAC地址"); - // if (instance_->SendMacAddressReliably()) { - // ESP_LOGI(TAG, "✅ BluFi连接后MAC地址发送成功"); - // } else { - // ESP_LOGW(TAG, "⚠️ BluFi连接后MAC地址发送失败,将在Wi-Fi连接成功后重试"); - // } - - // 停止广播 - esp_blufi_adv_stop(); - ESP_LOGI(TAG, "🔍 [DEBUG] BLE连接处理完成,client_connected_=%s", - instance_->client_connected_ ? "true" : "false"); - break; - // 客户端断开连接事件 - case ESP_BLUFI_EVENT_BLE_DISCONNECT: - ESP_LOGI(TAG, "📱 BluFi客户端已断开连接,当前状态: %s", - instance_->GetStateString().c_str()); - ESP_LOGI(TAG, "🔍 [DEBUG] 设置client_connected_为false"); - instance_->client_connected_ = false; - - // 如果正在配网过程中,延迟处理断开事件,给WiFi连接更多时间 - if (instance_->state_ == BluetoothProvisioningState::PROVISIONING) { - ESP_LOGW(TAG, "⚠️ 配网过程中BLE断开,延迟5秒后处理以等待WiFi连接完成"); - // 设置一个标志,延迟处理断开 - instance_->delayed_disconnect_ = true; - // 创建延迟任务 - xTaskCreate([](void* param) { - vTaskDelay(pdMS_TO_TICKS(2000)); // 缩短延迟到2秒 - BluetoothProvisioning* self = static_cast(param); - if (self->delayed_disconnect_) { - if (self->state_ == BluetoothProvisioningState::PROVISIONING && self->wifi_connecting_) { - ESP_LOGW(TAG, "⏰ BLE延迟断开,但WiFi仍在连接中,继续等待"); - // WiFi仍在连接,不断开BLE,让WiFi超时定时器处理 - } else if (self->state_ == BluetoothProvisioningState::PROVISIONING) { - ESP_LOGW(TAG, "⏰ 延迟处理BLE断开,WiFi连接可能已超时"); - self->SetState(BluetoothProvisioningState::ADVERTISING); - self->TriggerCallback(BluetoothProvisioningEvent::CLIENT_DISCONNECTED, nullptr); - esp_blufi_adv_start(); - } - self->delayed_disconnect_ = false; - } - vTaskDelete(nullptr); - }, "delayed_disconnect", 2048, instance_, 1, nullptr); - } else { - instance_->SetState(BluetoothProvisioningState::ADVERTISING); - instance_->TriggerCallback(BluetoothProvisioningEvent::CLIENT_DISCONNECTED, nullptr); - // 重新开始广播 - esp_blufi_adv_start(); - } - break; - - // 设置WiFi模式 - case ESP_BLUFI_EVENT_SET_WIFI_OPMODE: - ESP_LOGI(TAG, "设置WiFi模式: %d", param->wifi_mode.op_mode); - esp_wifi_set_mode(param->wifi_mode.op_mode); - break; - - // 请求连接到AP (Wi-Fi) - case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP: - ESP_LOGI(TAG, "📡 请求连接到AP,SSID: %s", instance_->wifi_credentials_.ssid.c_str()); - ESP_LOGI(TAG, "🔍 [DEBUG] 当前状态: %s, client_connected_: %s", - instance_->GetStateString().c_str(), - instance_->client_connected_ ? "true" : "false"); - instance_->SetState(BluetoothProvisioningState::PROVISIONING); - instance_->delayed_disconnect_ = false; // 重置延迟断开标志 - s_retry_num = 0; // 重置重试计数 - ESP_LOGI(TAG, "🔄 重置WiFi重试计数,开始连接流程"); - // 断开当前WiFi连接(如果有) - esp_wifi_disconnect(); - vTaskDelay(pdMS_TO_TICKS(100)); // 短暂延迟确保断开完成 - // 连接到新的AP - esp_wifi_connect();//连接到新的 Wi-Fi - ESP_LOGI(TAG, "🚀 已发起WiFi连接请求"); - break; - - // 请求断开AP连接 - case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP: - ESP_LOGI(TAG, "请求断开AP连接"); - esp_wifi_disconnect();//断开当前连接的AP - break; - - // 接收到WI-FI的 SSID - case ESP_BLUFI_EVENT_RECV_STA_SSID: - ESP_LOGI(TAG, "📶 收到WiFi SSID: %.*s", param->sta_ssid.ssid_len, param->sta_ssid.ssid); - instance_->wifi_credentials_.ssid.assign( - reinterpret_cast(param->sta_ssid.ssid), - param->sta_ssid.ssid_len);//保存Wi-Fi的 SSID - - // 设置WiFi配置 - { - wifi_config_t wifi_config = {}; - strncpy(reinterpret_cast(wifi_config.sta.ssid), - instance_->wifi_credentials_.ssid.c_str(), - sizeof(wifi_config.sta.ssid) - 1); - esp_wifi_set_config(WIFI_IF_STA, &wifi_config);//设置WiFi配置 - } - break; - - // 接收到WI-FI的 密码 - case ESP_BLUFI_EVENT_RECV_STA_PASSWD: - ESP_LOGI(TAG, "🔐 收到WiFi密码 (长度: %d)", param->sta_passwd.passwd_len); - instance_->wifi_credentials_.password.assign( - reinterpret_cast(param->sta_passwd.passwd), - param->sta_passwd.passwd_len);// 保存WI-FI密码凭证 - - // 设置WiFi配置 - { - wifi_config_t wifi_config = {}; - strncpy(reinterpret_cast(wifi_config.sta.ssid), - instance_->wifi_credentials_.ssid.c_str(), - sizeof(wifi_config.sta.ssid) - 1); - strncpy(reinterpret_cast(wifi_config.sta.password), - instance_->wifi_credentials_.password.c_str(), - sizeof(wifi_config.sta.password) - 1); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));//设置WiFi配置 - } - - // 重置重试计数器 - s_retry_num = 0; - instance_->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(); // 核心连接函数,连接到WiFi - ESP_LOGI(TAG, "📡 已发起WiFi连接请求,启动15秒超时监控"); - - instance_->TriggerCallback(BluetoothProvisioningEvent::WIFI_CREDENTIALS, - &instance_->wifi_credentials_); - break; - - // 接收到WI-FI的 BSSID 特定接入点MAC地址 - case ESP_BLUFI_EVENT_RECV_STA_BSSID: - ESP_LOGI(TAG, "收到BSSID"); - memcpy(instance_->wifi_credentials_.bssid, param->sta_bssid.bssid, 6); - instance_->wifi_credentials_.bssid_set = true;// 标记BSSID已设置 - - // 设置WiFi配置 - { - wifi_config_t wifi_config = {}; - strncpy(reinterpret_cast(wifi_config.sta.ssid), - instance_->wifi_credentials_.ssid.c_str(), - sizeof(wifi_config.sta.ssid) - 1);// 复制SSID到配置 - strncpy(reinterpret_cast(wifi_config.sta.password), - instance_->wifi_credentials_.password.c_str(), - sizeof(wifi_config.sta.password) - 1);// 复制密码到配置 - memcpy(wifi_config.sta.bssid, instance_->wifi_credentials_.bssid, 6); - wifi_config.sta.bssid_set = true; - esp_wifi_set_config(WIFI_IF_STA, &wifi_config);//设置WiFi配置 - } - break; - - // 客户端请求WiFi状态 - case ESP_BLUFI_EVENT_GET_WIFI_STATUS: - ESP_LOGI(TAG, "客户端请求WiFi状态"); - // 这里可以发送当前WiFi状态 - break; - - // 新增代码 - //======================================================================== - // 客户端请求WiFi列表 - case ESP_BLUFI_EVENT_GET_WIFI_LIST: - ESP_LOGI(TAG, "📱 手机APP请求获取WiFi列表,开始扫描周围WiFi网络"); - // 启动WiFi扫描 - { - wifi_scan_config_t scan_config = {}; - scan_config.ssid = nullptr; // 扫描所有SSID - scan_config.bssid = nullptr; // 扫描所有BSSID - 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 ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE: - ESP_LOGI(TAG, "收到断开BLE连接请求"); - esp_blufi_disconnect(); - break; - - default: - ESP_LOGD(TAG, "未处理的BLUFI事件: %d", event); - break; - } -} - -// 处理WiFi事件 -void BluetoothProvisioning::WiFiEventHandler(void* arg, esp_event_base_t event_base, +void BluetoothProvisioning::WiFiEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { BluetoothProvisioning* self = static_cast(arg); @@ -927,12 +1149,10 @@ void BluetoothProvisioning::WiFiEventHandler(void* arg, esp_event_base_t event_b wifi_event_sta_connected_t* event = static_cast(event_data); ESP_LOGI(TAG, "✅ WiFi连接成功,SSID: %.*s,等待获取IP地址", event->ssid_len, event->ssid); - // 停止WiFi连接超时定时器 if (wifi_connect_timer) { xTimerStop(wifi_connect_timer, 0); } - // 清除连接状态标志 self->wifi_connecting_ = false; self->delayed_disconnect_ = false; break; @@ -942,33 +1162,28 @@ void BluetoothProvisioning::WiFiEventHandler(void* arg, esp_event_base_t event_b wifi_event_sta_disconnected_t* event = static_cast(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();//连接到WiFi + 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, + 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; @@ -979,12 +1194,10 @@ void BluetoothProvisioning::WiFiEventHandler(void* arg, esp_event_base_t event_b } break; } - // 新增代码 - //======================================================================== - case WIFI_EVENT_SCAN_DONE: { - ESP_LOGI(TAG, "📡 WiFi扫描完成,准备发送WiFi列表给手机APP"); - // 获取扫描结果 + 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) { @@ -995,42 +1208,34 @@ void BluetoothProvisioning::WiFiEventHandler(void* arg, esp_event_base_t event_b 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扫描结果,准备发送给手机APP"); + ESP_LOGI(TAG, "✅ 成功获取WiFi扫描结果"); - // 打印扫描到的WiFi列表(调试用) for (int i = 0; i < ap_count; i++) { - ESP_LOGD(TAG, "WiFi[%d]: SSID=%s, RSSI=%d, 加密=%d", + ESP_LOGD(TAG, "WiFi[%d]: SSID=%s, RSSI=%d, 加密=%d", i, ap_list[i].ssid, ap_list[i].rssi, ap_list[i].authmode); } - // 发送WiFi列表给手机APP self->SendWiFiList(ap_list, ap_count); - ESP_LOGI(TAG, "📤 WiFi列表已发送给手机APP,包含 %d 个热点", ap_count); + ESP_LOGI(TAG, "📤 WiFi列表已发送,包含 %d 个热点", ap_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; @@ -1038,7 +1243,11 @@ void BluetoothProvisioning::WiFiEventHandler(void* arg, esp_event_base_t event_b } } -void BluetoothProvisioning::IPEventHandler(void* arg, esp_event_base_t event_base, +// ============================================================ +// 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(arg); @@ -1056,21 +1265,17 @@ void BluetoothProvisioning::IPEventHandler(void* arg, esp_event_base_t event_bas wifi_connect_timer = nullptr; } - // 清除状态标志 self->wifi_connecting_ = false; self->delayed_disconnect_ = false; - // 设置WiFi连接成功标志 if (s_wifi_event_group) { xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } - // 如果BluFi客户端已连接,发送WiFi连接成功报告 - ESP_LOGI(TAG, "🔍 [DEBUG] 检查BluFi客户端连接状态: client_connected_=%s", + // 发送MAC地址 + ESP_LOGI(TAG, "🔍 [DEBUG] 检查客户端连接状态: client_connected_=%s", self->client_connected_ ? "true" : "false"); if (self && self->client_connected_) { - // ================================================================================== - // 使用专用的可靠MAC地址发送函数(优化版本2) ESP_LOGI(TAG, "🔍 [DEBUG] 使用专用函数发送设备MAC地址..."); bool mac_sent = self->SendMacAddressReliably(); if (mac_sent) { @@ -1078,49 +1283,16 @@ void BluetoothProvisioning::IPEventHandler(void* arg, esp_event_base_t event_bas } else { ESP_LOGW(TAG, "⚠️ 设备MAC地址发送失败"); } - // ================================================================================== - // ================================================================================== - // 注释:由于只需要发送设备MAC地址,暂时注释掉WiFi连接报告相关代码 - // 这样可以避免发送不必要的信息(如SSID等) - // ================================================================================== - /* - ESP_LOGI(TAG, "🔍 [DEBUG] 准备发送WiFi连接成功报告给手机APP"); - wifi_mode_t mode; - esp_wifi_get_mode(&mode); - - esp_blufi_extra_info_t info; - memset(&info, 0, sizeof(esp_blufi_extra_info_t)); - - // 获取当前连接的WiFi信息 - wifi_ap_record_t ap_info; - if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) { - // memcpy(info.sta_bssid, ap_info.bssid, 6); // 发送路由器MAC地址 - // info.sta_bssid_set = true; // 设置BSSID已获取 - info.sta_bssid_set = false; // 明确标记不发送BSSID - - info.sta_ssid = ap_info.ssid; - info.sta_ssid_len = strlen((char*)ap_info.ssid); - // ESP_LOGI(TAG, "🔍 [DEBUG] 获取到WiFi信息: SSID=%.*s",info.sta_ssid_len, info.sta_ssid); - } - - // 发送WiFi连接成功报告 - esp_err_t ret = esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info); - if (ret == ESP_OK) { - ESP_LOGI(TAG, "✅ 已向手机APP发送WiFi连接成功报告"); - } else { - ESP_LOGW(TAG, "⚠️ 发送WiFi连接成功报告失败: %s", esp_err_to_name(ret)); - } - */ ESP_LOGI(TAG, "🔍 [DEBUG] 已跳过WiFi连接报告发送,仅发送设备MAC地址"); } else { - ESP_LOGW(TAG, "🔍 [DEBUG] 无法发送WiFi连接成功报告: client_connected_=%s", + 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);// 设置WiFi存储模式为FLASH + esp_err_t storage_ret = esp_wifi_set_storage(WIFI_STORAGE_FLASH); if (storage_ret == ESP_OK) { ESP_LOGI(TAG, "✅ WiFi配置将自动保存到NVS存储"); } else { @@ -1128,8 +1300,8 @@ void BluetoothProvisioning::IPEventHandler(void* arg, esp_event_base_t event_bas } // 手动获取当前WiFi配置并保存到NVS列表 - wifi_config_t wifi_config;// 定义WiFi配置结构体 - esp_err_t get_config_ret = esp_wifi_get_config(WIFI_IF_STA, &wifi_config);// 获取当前WiFi配置 + 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(); @@ -1140,7 +1312,7 @@ void BluetoothProvisioning::IPEventHandler(void* arg, esp_event_base_t event_bas } auto& application = Application::GetInstance(); - bool skip_session = application.ShouldSkipDialogIdleSession();// 是否跳过对话待机会话 + 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){ @@ -1150,7 +1322,7 @@ void BluetoothProvisioning::IPEventHandler(void* arg, esp_event_base_t event_bas application.PlaySound(Lang::Sounds::P3_LALA_LIANJIEWANGLUO); } } else { - application.ClearDialogIdleSkipSession();// 清除对话待机会话标志位 + application.ClearDialogIdleSkipSession(); } // 更新状态和触发回调 @@ -1159,13 +1331,13 @@ void BluetoothProvisioning::IPEventHandler(void* arg, esp_event_base_t event_bas self->TriggerCallback(BluetoothProvisioningEvent::WIFI_CONNECTED, &event->ip_info.ip); self->ReportWiFiStatus(true, 0); - ESP_LOGI(TAG, "📋 配网流程完成,状态: %s, client_connected_: %s", - self->GetStateString().c_str(), + 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));// 配网成功后,设备重启,会自动连接到新的WiFi网络 + vTaskDelay(pdMS_TO_TICKS(2000)); ESP_LOGI(TAG, "🔄 强制重启设备..."); esp_restart(); break; diff --git a/main/bluetooth_provisioning.h b/main/bluetooth_provisioning.h index 29aeb9e..e9f74d4 100644 --- a/main/bluetooth_provisioning.h +++ b/main/bluetooth_provisioning.h @@ -2,11 +2,11 @@ /** * @file bluetooth_provisioning.h - * @brief BluFi蓝牙配网模块头文件 - * - * 本文件定义了BluFi蓝牙配网的相关接口,包括配网状态管理、 - * 事件处理、WiFi凭据传输等功能。提供简单易用的C++接口 - * 封装ESP-IDF的BLUFI功能,用于通过蓝牙进行WiFi配网操作。 + * @brief 蓝牙配网模块头文件(基于自定义 GATT Server) + * + * 使用自定义 BLE GATT Server 替代 BluFi API 实现配网通讯, + * 采用原始广播数据(raw advertising)确保手机系统蓝牙可发现。 + * 保留完整的 WiFi 配网业务逻辑、状态机和事件回调。 */ #include @@ -17,25 +17,55 @@ // 使用条件编译避免IDE环境中的头文件错误 #ifdef ESP_PLATFORM -#include "esp_blufi_api.h" +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_gatt_common_api.h" #include "esp_wifi.h" #include "esp_event.h" #else // 在非ESP环境中定义必要的类型和常量 -typedef int esp_blufi_cb_event_t; -typedef void* esp_blufi_cb_param_t; +typedef int esp_event_base_t; typedef void* wifi_ap_record_t; -typedef void* esp_event_base_t; #endif +// ============================================================ +// 配网协议命令定义 (手机 → 设备, WRITE 特征) +// ============================================================ +#define PROV_CMD_SET_SSID 0x01 // 设置 WiFi SSID +#define PROV_CMD_SET_PASSWORD 0x02 // 设置 WiFi 密码 +#define PROV_CMD_SET_BSSID 0x03 // 设置 BSSID (6字节) +#define PROV_CMD_CONNECT_AP 0x04 // 请求连接 WiFi +#define PROV_CMD_DISCONNECT_AP 0x05 // 请求断开 WiFi +#define PROV_CMD_GET_WIFI_LIST 0x06 // 请求 WiFi 列表 +#define PROV_CMD_DISCONNECT_BLE 0x07 // 请求断开 BLE +#define PROV_CMD_SET_WIFI_MODE 0x08 // 设置 WiFi 模式 +#define PROV_CMD_GET_WIFI_STATUS 0x09 // 获取 WiFi 状态 +#define PROV_CMD_CUSTOM_DATA 0x10 // 自定义数据 + +// ============================================================ +// 配网协议响应定义 (设备 → 手机, NOTIFY 特征) +// ============================================================ +#define PROV_RESP_WIFI_STATUS 0x81 // WiFi 状态: [success(1)][reason(1)] +#define PROV_RESP_WIFI_LIST 0x82 // WiFi 列表: [rssi(1)][ssid_len(1)][ssid...] +#define PROV_RESP_WIFI_LIST_END 0x83 // WiFi 列表结束标记 +#define PROV_RESP_CUSTOM_DATA 0x84 // 自定义数据 + +// ============================================================ +// GATT 服务配置 +// ============================================================ +#define PROV_SERVICE_UUID 0xABF0 // 配网服务 UUID +#define PROV_CHAR_WRITE_UUID 0xABF1 // 写入特征 UUID (手机→设备) +#define PROV_CHAR_NOTIFY_UUID 0xABF2 // 通知特征 UUID (设备→手机) +#define PROV_APP_ID 2 // GATTS App ID +#define PROV_HANDLE_NUM 8 // Service handle 数量 +#define PROV_LOCAL_MTU 512 // 本地 MTU + /** * @brief 蓝牙配网状态枚举 - * - * 定义BluFi配网过程中的各种状态,用于状态机管理和状态监控 */ enum class BluetoothProvisioningState { IDLE, //< 空闲状态,未启动配网 - INITIALIZING, //< 初始化中,正在初始化蓝牙和BluFi服务 + INITIALIZING, //< 初始化中,正在初始化蓝牙和服务 ADVERTISING, //< 广播中,等待手机客户端连接 CONNECTED, //< 已连接,手机客户端已连接到设备 PROVISIONING, //< 配网中,正在接收和处理WiFi凭据 @@ -46,255 +76,100 @@ enum class BluetoothProvisioningState { /** * @brief 蓝牙配网事件类型 - * - * 定义配网过程中可能发生的各种事件,用于事件回调和状态通知 */ enum class BluetoothProvisioningEvent { - STATE_CHANGED, //< 状态改变事件,配网状态发生变化 - WIFI_CREDENTIALS, //< 收到WiFi凭据事件,从手机接收到WiFi信息 - WIFI_CONNECTED, //< WiFi连接成功事件,设备成功连接到WiFi网络 - WIFI_FAILED, //< WiFi连接失败事件,设备连接WiFi失败 - CLIENT_CONNECTED, //< 客户端连接事件,手机客户端连接到设备 - CLIENT_DISCONNECTED //< 客户端断开事件,手机客户端断开连接 + STATE_CHANGED, //< 状态改变事件 + WIFI_CREDENTIALS, //< 收到WiFi凭据事件 + WIFI_CONNECTED, //< WiFi连接成功事件 + WIFI_FAILED, //< WiFi连接失败事件 + CLIENT_CONNECTED, //< 客户端连接事件 + CLIENT_DISCONNECTED //< 客户端断开事件 }; /** * @brief WiFi凭据结构体 - * - * 存储从手机客户端接收到的WiFi连接信息 */ struct WiFiCredentials { std::string ssid; //< WiFi网络名称(SSID) std::string password; //< WiFi网络密码 - uint8_t bssid[6]; //< WiFi接入点的MAC地址(BSSID),可选 - bool bssid_set; //< 是否设置了BSSID,用于指定特定的接入点 + uint8_t bssid[6]; //< WiFi接入点的MAC地址(BSSID) + bool bssid_set; //< 是否设置了BSSID }; /** * @brief 蓝牙配网事件回调函数类型 - * @param event 事件类型 - * @param data 事件数据(可选) */ using BluetoothProvisioningCallback = std::function; /** - * @brief 蓝牙配网封装类 - * - * 该类封装了ESP-IDF的BLUFI功能,提供简单易用的C++接口 - * 用于通过蓝牙进行WiFi配网操作。支持状态管理、事件回调、WiFi凭据接收等功能。 - * - * 典型使用流程: - * 1. 创建BluetoothProvisioning实例 - * 2. 设置事件回调函数 - * 3. 调用StartProvisioning()开始配网 - * 4. 处理回调事件 - * 5. 配网完成后调用StopProvisioning() + * @brief 蓝牙配网封装类(基于自定义 GATT Server) + * + * 使用自定义 BLE GATT Server + 原始广播数据替代 BluFi API, + * 手机系统蓝牙可直接搜索到设备。保留完整的 WiFi 配网业务逻辑。 */ class BluetoothProvisioning { public: - /** - * @brief 构造函数 - * - * 初始化蓝牙配网对象,设置默认参数和状态 - */ BluetoothProvisioning(); - - /** - * @brief 析构函数 - * - * 清理资源,停止配网服务,释放蓝牙相关资源 - */ ~BluetoothProvisioning(); - /** - * @brief 初始化蓝牙配网功能 - * - * 初始化蓝牙控制器、蓝牙栈和BluFi服务,为配网做准备 - * - * @return true 初始化成功,false 初始化失败 - */ bool Initialize(); - - /** - * @brief 反初始化蓝牙配网功能 - * - * 清理蓝牙资源,释放内存,恢复系统状态 - * - * @return true 反初始化成功,false 反初始化失败 - */ bool Deinitialize(); - - /** - * @brief 开始蓝牙配网 - * - * 启动BluFi服务,开始广播等待手机客户端连接 - * - * @param device_name 蓝牙设备名称(可选,默认为"BLUFI_Airhub"),手机端会看到此名称 - * @return true 启动成功,false 启动失败 - */ bool StartProvisioning(const char* device_name = BLU_NAME); - - /** - * @brief 停止蓝牙配网 - * - * 停止BluFi服务,断开客户端连接,停止蓝牙广播 - * - * @return true 停止成功,false 停止失败 - */ bool StopProvisioning(); - /** - * @brief 获取当前配网状态 - * @return 当前状态 - */ BluetoothProvisioningState GetState() const { return state_; } - - /** - * @brief 设置事件回调函数 - * - * 设置用于接收配网事件通知的回调函数 - * - * @param callback 事件回调函数,当配网过程中发生事件时会被调用 - */ void SetCallback(BluetoothProvisioningCallback callback) { callback_ = callback; } - - /** - * @brief 获取最后收到的WiFi凭据 - * - * 返回从手机客户端接收到的WiFi连接信息 - * - * @return WiFi凭据结构体的常量引用 - */ const WiFiCredentials& GetWiFiCredentials() const { return wifi_credentials_; } - - /** - * @brief 检查是否已连接客户端 - * - * 检查当前是否有手机客户端连接到设备 - * - * @return true 已连接,false 未连接 - */ bool IsClientConnected() const { return client_connected_; } - - /** - * @brief 获取当前状态的字符串表示 - * - * 将当前配网状态转换为可读的字符串形式,便于调试和日志输出 - * - * @return 状态字符串 - */ std::string GetStateString() const; - /** - * @brief 发送WiFi连接状态报告 - * - * 向手机客户端报告WiFi连接尝试的结果 - * - * @param success 连接是否成功 - * @param reason 失败原因代码(仅在失败时有效) - */ void ReportWiFiStatus(bool success, uint8_t reason = 0); - - /** - * @brief 发送WiFi扫描结果 - * - * 将扫描到的WiFi接入点列表发送给手机客户端 - * - * @param ap_list WiFi接入点记录数组 - * @param ap_count 接入点数量 - */ void SendWiFiList(const wifi_ap_record_t* ap_list, uint16_t ap_count); - - /** - * @brief 可靠地发送设备MAC地址给手机客户端 - * - * 该函数实现了增强的MAC地址发送机制,包括: - * - 多次重试机制,提高发送成功率 - * - 连接状态双重检查,避免竞争条件 - * - 重复发送检测,避免发送相同MAC地址 - * - 详细的错误处理和日志记录 - * - * @return true 发送成功,false 发送失败 - */ bool SendMacAddressReliably(); - - /** - * @brief 重置MAC地址发送状态 - * - * 在新的配网会话开始时调用,清除之前的发送记录 - * 允许重新发送MAC地址 - */ void ResetMacSendingState(); private: - BluetoothProvisioningState state_; //< 当前配网状态 - BluetoothProvisioningCallback callback_; //< 用户设置的事件回调函数 - WiFiCredentials wifi_credentials_; //< 存储接收到的WiFi凭据信息 - bool client_connected_; //< 客户端连接状态标志 - bool initialized_; //< 蓝牙配网模块初始化状态标志 - bool delayed_disconnect_; //< 延迟断开连接标志,用于优雅断开 - bool wifi_connecting_; //< WiFi连接进行中标志 - bool mac_address_sent_; //< MAC地址发送状态标志,避免重复发送 - - // 静态实例指针,用于C回调函数访问 - static BluetoothProvisioning* instance_; //< 单例实例指针,用于静态回调函数访问类成员 - - /** - * @brief 设置状态并触发回调 - * - * 内部状态管理函数,更新当前状态并通知回调函数 - * - * @param new_state 新的配网状态 - */ + BluetoothProvisioningState state_; + BluetoothProvisioningCallback callback_; + WiFiCredentials wifi_credentials_; + bool client_connected_; + bool initialized_; + bool delayed_disconnect_; + bool wifi_connecting_; + bool mac_address_sent_; + + static BluetoothProvisioning* instance_; + void SetState(BluetoothProvisioningState new_state); - - /** - * @brief 触发事件回调 - * - * 向用户注册的回调函数发送事件通知 - * - * @param event 事件类型 - * @param data 事件相关数据指针(可选) - */ void TriggerCallback(BluetoothProvisioningEvent event, void* data = nullptr); - -public: - /** - * @brief BluFi事件回调函数(静态函数) - * - * ESP-IDF BluFi库的静态回调函数,处理所有BluFi相关事件 - * - * @param event BluFi事件类型 - * @param param 事件参数结构体指针 - */ - static void BlufiEventCallback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t* param); - -private: - /** - * @brief WiFi事件处理函数 - * - * 处理WiFi连接、断开等相关事件 - * - * @param arg 用户参数 - * @param event_base 事件基础类型 - * @param event_id 事件ID - * @param event_data 事件数据 - */ + +#ifdef ESP_PLATFORM + // GATT 相关成员 + esp_gatt_if_t gatts_if_ = ESP_GATT_IF_NONE; + uint16_t service_handle_ = 0; + uint16_t write_char_handle_ = 0; + uint16_t notify_char_handle_ = 0; + uint16_t notify_cccd_handle_ = 0; + uint16_t conn_id_ = 0; + bool notify_enabled_ = false; + uint16_t mtu_ = 23; + + // BLE 回调 + static void GattsEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); + static void GapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); + void HandleGattsEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); + + // GATT 辅助方法 + void CreateService(esp_gatt_if_t gatts_if); + void StartAdvertising(); + bool SendNotify(const uint8_t* data, uint16_t len); + void ProcessWriteData(const uint8_t* data, uint16_t len); + + // WiFi 事件处理 static void WiFiEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data); - - /** - * @brief IP事件处理函数 - * - * 处理IP地址获取等网络相关事件 - * - * @param arg 用户参数 - * @param event_base 事件基础类型 - * @param event_id 事件ID - * @param event_data 事件数据 - */ static void IPEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data); - - // 禁用拷贝构造和赋值操作 +#endif + BluetoothProvisioning(const BluetoothProvisioning&) = delete; BluetoothProvisioning& operator=(const BluetoothProvisioning&) = delete; -}; \ No newline at end of file +}; diff --git a/main/boards/common/wifi_board.cc b/main/boards/common/wifi_board.cc index a401362..299ecd0 100644 --- a/main/boards/common/wifi_board.cc +++ b/main/boards/common/wifi_board.cc @@ -72,22 +72,21 @@ std::string WifiBoard::GetBoardType() { void WifiBoard::EnterWifiConfigMode() { ESP_LOGI(TAG, "🔵 进入配网模式 - 使用BluFi蓝牙配网"); - // 直接启动BluFi配网,不再回退到WiFi AP模式 - bool blufi_success = StartBluFiProvisioning(); - ESP_LOGI(TAG, "🔍 BluFi配网启动结果: %s", blufi_success ? "成功" : "失败"); + // 使用 BluFi 蓝牙配网 + bool success = StartBluFiProvisioning(); + ESP_LOGI(TAG, "🔍 BluFi配网启动结果: %s", success ? "成功" : "失败"); - if (blufi_success) { + if (success) { ESP_LOGI(TAG, "✅ BluFi配网启动成功,等待手机连接"); return; } - ESP_LOGW(TAG, "⚠️ BluFi配网启动失败,将持续重试BluFi配网(不使用WiFi AP模式)"); - ESP_LOGI(TAG, "🔄 持续重试BluFi蓝牙配网..."); + ESP_LOGW(TAG, "⚠️ BluFi配网启动失败,将持续重试"); - // 持续重试BluFi配网 + // 持续重试 while (true) { - vTaskDelay(pdMS_TO_TICKS(5000)); // 等待5秒后重试 - ESP_LOGI(TAG, "🔄 重试启动BluFi蓝牙配网..."); + vTaskDelay(pdMS_TO_TICKS(5000)); + ESP_LOGI(TAG, "🔄 重试启动BluFi配网..."); if (StartBluFiProvisioning()) { ESP_LOGI(TAG, "✅ BluFi配网重试成功,等待手机连接"); return; @@ -152,17 +151,14 @@ void WifiBoard::StartNetwork() { auto& ssid_manager = SsidManager::GetInstance(); // 获取SSID管理器实例 auto ssid_list = ssid_manager.GetSsidList(); // 获取SSID列表 if (ssid_list.empty()) { - ESP_LOGI(TAG, "🔍 未找到WiFi凭据,启动BluFi蓝牙配网..."); + ESP_LOGI(TAG, "🔍 未找到WiFi凭据,启动BluFi配网..."); if (StartBluFiProvisioning()) { - ESP_LOGI(TAG, "✅ BluFi蓝牙配网启动成功,等待手机连接..."); - // BluFi配网启动成功,等待完成或超时 + ESP_LOGI(TAG, "✅ BluFi配网启动成功,等待手机连接..."); return; } else { - // BluFi配网启动失败,继续尝试重新启动BluFi配网 - ESP_LOGW(TAG, "❌ BluFi蓝牙配网启动失败,将持续重试BluFi配网"); - // 延迟后重试BluFi配网 - vTaskDelay(pdMS_TO_TICKS(5000)); // 等待5秒后重试 - ESP_LOGI(TAG, "🔄 重试启动BluFi蓝牙配网..."); + ESP_LOGW(TAG, "❌ BluFi配网启动失败,将重试"); + vTaskDelay(pdMS_TO_TICKS(5000)); + ESP_LOGI(TAG, "🔄 重试启动BluFi配网..."); StartBluFiProvisioning(); return; } @@ -418,6 +414,63 @@ bool WifiBoard::StartBluFiProvisioning() { return true;// 启动成功,返回true } +// 使用 BLE JSON Service 进行配网 (替代 BluFi) +bool WifiBoard::StartBleJsonProvisioning() { + ESP_LOGI(TAG, "🔵 正在启动BLE JSON配网服务..."); + + Application::GetInstance().StopAudioProcessor(); + Application::GetInstance().ClearAudioQueue(); + + // 初始化 BLE JSON 服务 + if (!ble_json_service_.Initialize()) { + ESP_LOGE(TAG, "❌ BLE JSON服务初始化失败"); + return false; + } + + // 设置命令回调,转发给 Application 处理 + ble_json_service_.SetCommandCallback( + [this](const std::string& cmd, int msg_id, cJSON* data) { + Application::GetInstance().HandleBleJsonCommand(cmd, msg_id, data, ble_json_service_); + }); + + // 使用与 BluFi 相同的设备名启动 + std::string device_name = BLU_NAME; + if (!ble_json_service_.Start(device_name.c_str())) { + ESP_LOGE(TAG, "❌ BLE JSON服务启动失败"); + return false; + } + + ESP_LOGI(TAG, "✅ BLE JSON配网启动成功,设备名称: %s", device_name.c_str()); + + blufi_provisioning_active_ = true; + blufi_start_time_ = xTaskGetTickCount(); + + // 显示配网通知 + auto display = GetDisplay(); + if (display) { + std::string notification = "BLE配网模式\n设备名: " + device_name; + display->ShowNotification(notification.c_str(), 30000); + } + + // 播放配网提示音 + auto& application = Application::GetInstance(); + if (strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0) { + application.Alert("BLE配网模式", ("请使用手机APP连接设备: " + device_name).c_str(), "", Lang::Sounds::P3_KAKA_WIFICONFIG); + } else if (strcmp(CONFIG_DEVICE_ROLE, "RTC_Test") == 0) { + application.Alert("BLE配网模式", ("请使用手机APP连接设备: " + device_name).c_str(), "", 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; +} + // 监控BluFi配网状态 void WifiBoard::MonitorBluFiProvisioning() { ESP_LOGI(TAG, "Starting BluFi provisioning monitor..."); diff --git a/main/boards/common/wifi_board.h b/main/boards/common/wifi_board.h index f271ba6..3967b72 100644 --- a/main/boards/common/wifi_board.h +++ b/main/boards/common/wifi_board.h @@ -12,6 +12,7 @@ #include "board.h" #include "bluetooth_provisioning.h" +#include "ble_service.h" #include #include #include @@ -33,7 +34,8 @@ protected: bool blufi_provisioning_success_ = false; ///< BluFi配网成功状态标志 TickType_t blufi_start_time_ = 0; ///< BluFi配网开始时间戳 static const TickType_t BLUFI_TIMEOUT_MS = 300000; ///< BluFi配网超时时间(5分钟),避免过快重新进入配网 - BluetoothProvisioning bluetooth_provisioning_; ///< BluFi蓝牙配网实例对象 + BluetoothProvisioning bluetooth_provisioning_; ///< BluFi蓝牙配网实例对象(暂停使用) + BleJsonService ble_json_service_; ///< BLE JSON 配网服务实例 /** * @brief 构造函数 @@ -63,6 +65,9 @@ protected: */ bool StartBluFiProvisioning(); + // 使用 BLE JSON Service 替代 BluFi 进行配网 + bool StartBleJsonProvisioning(); + /** * @brief 监控BluFi配网进程 * 监控配网状态变化,处理超时和异常情况 diff --git a/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc b/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc index dcac8ac..b09d591 100644 --- a/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc +++ b/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc @@ -1124,11 +1124,14 @@ public: }); } - // 电量上报逻辑:每30秒上报一次(启动3秒后) + // 电量上报逻辑:每30秒上报一次(启动3秒后),配网模式下跳过 if (battery_report_enabled_) { battery_report_ticks_++; if (battery_report_ticks_ % kBatteryReportInterval == 0) { - ReportBatteryToServer(battery_level_); + auto& wifi_station = WifiStation::GetInstance(); + if (wifi_station.IsConnected()) { + ReportBatteryToServer(battery_level_); + } } } diff --git a/tests/ble_provision_test.py b/tests/ble_provision_test.py new file mode 100644 index 0000000..f6ff8d9 --- /dev/null +++ b/tests/ble_provision_test.py @@ -0,0 +1,356 @@ +#!/usr/bin/env python3 +""" +BLE 配网协议测试脚本 + +用途: 模拟手机端,通过 BLE 与 ESP32 设备通信,测试自定义 GATT 配网协议。 +依赖: pip install bleak +运行: + # 交互式配网(输入 SSID 和密码) + python tests/ble_provision_test.py --ssid "MyWiFi" --pwd "12345678" + + # 扫描 WiFi 列表 + python tests/ble_provision_test.py --scan-wifi + + # 查询 WiFi 状态 + python tests/ble_provision_test.py --get-status + + # 指定设备名 + python tests/ble_provision_test.py --device "Airhub_Ble" --ssid "MyWiFi" --pwd "12345678" + +协议说明: + Service UUID: 0xABF0 + Write Char: 0xABF1 (手机→设备, 二进制命令) + Notify Char: 0xABF2 (设备→手机, 二进制响应) + + 命令格式: [cmd(1字节)] + [payload...] + 响应格式: [resp(1字节)] + [data...] +""" + +import argparse +import asyncio +import sys +import time +import struct + +try: + from bleak import BleakClient, BleakScanner + from bleak.backends.characteristic import BleakGATTCharacteristic +except ImportError: + print("错误: 缺少 bleak 库,请执行: pip install bleak") + sys.exit(1) + +# ============================================================ +# BLE 参数定义 (与 bluetooth_provisioning.h 一致) +# ============================================================ + +SERVICE_UUID = "0000abf0-0000-1000-8000-00805f9b34fb" +CHAR_WRITE_UUID = "0000abf1-0000-1000-8000-00805f9b34fb" +CHAR_NOTIFY_UUID = "0000abf2-0000-1000-8000-00805f9b34fb" +DEFAULT_DEVICE = "Airhub_Ble" + +# 命令码 (手机→设备) +CMD_SET_SSID = 0x01 +CMD_SET_PASSWORD = 0x02 +CMD_SET_BSSID = 0x03 +CMD_CONNECT_AP = 0x04 +CMD_DISCONNECT_AP = 0x05 +CMD_GET_WIFI_LIST = 0x06 +CMD_DISCONNECT_BLE = 0x07 +CMD_SET_WIFI_MODE = 0x08 +CMD_GET_WIFI_STATUS = 0x09 +CMD_CUSTOM_DATA = 0x10 + +# 响应码 (设备→手机) +RESP_WIFI_STATUS = 0x81 +RESP_WIFI_LIST = 0x82 +RESP_WIFI_LIST_END = 0x83 +RESP_CUSTOM_DATA = 0x84 + + +class BleProvisionTester: + """BLE 配网协议测试器""" + + def __init__(self, device_name: str, timeout: float = 10.0): + self.device_name = device_name + self.timeout = timeout + self.client = None + self.notifications = [] + self._notify_event = asyncio.Event() + + def _on_notify(self, sender: BleakGATTCharacteristic, data: bytearray): + """NOTIFY 回调""" + self.notifications.append(bytes(data)) + self._notify_event.set() + self._print_notification(data) + + @staticmethod + def _print_notification(data: bytearray): + """解析并打印 NOTIFY 数据""" + if len(data) < 1: + print(f" <- [空数据]") + return + + resp_type = data[0] + hex_str = data.hex(" ") + + if resp_type == RESP_WIFI_STATUS: + success = data[1] if len(data) > 1 else 0 + reason = data[2] if len(data) > 2 else 0 + status = "成功" if success == 1 else f"失败 (原因码: {reason})" + print(f" <- [WiFi状态] {status} (raw: {hex_str})") + + elif resp_type == RESP_WIFI_LIST: + if len(data) >= 3: + rssi = struct.unpack("b", bytes([data[1]]))[0] # 有符号 + ssid_len = data[2] + ssid = data[3:3 + ssid_len].decode("utf-8", errors="replace") + print(f" <- [WiFi] RSSI={rssi}dBm SSID=\"{ssid}\"") + else: + print(f" <- [WiFi列表] 数据不完整 (raw: {hex_str})") + + elif resp_type == RESP_WIFI_LIST_END: + print(f" <- [WiFi列表结束]") + + elif resp_type == RESP_CUSTOM_DATA: + payload = data[1:] + print(f" <- [自定义数据] {payload.hex(' ')} text=\"{payload.decode('utf-8', errors='replace')}\"") + + else: + print(f" <- [未知响应 0x{resp_type:02x}] {hex_str}") + + async def _wait_notifications(self, timeout: float = None, count: int = 1): + """等待指定数量的通知""" + timeout = timeout or self.timeout + start = len(self.notifications) + deadline = time.monotonic() + timeout + while len(self.notifications) - start < count: + remaining = deadline - time.monotonic() + if remaining <= 0: + break + self._notify_event.clear() + try: + await asyncio.wait_for(self._notify_event.wait(), timeout=remaining) + except asyncio.TimeoutError: + break + return self.notifications[start:] + + async def scan_and_connect(self): + """扫描并连接设备""" + print(f"正在扫描设备 '{self.device_name}'...") + device = await BleakScanner.find_device_by_name( + self.device_name, timeout=10.0 + ) + if not device: + print(f"未找到设备 '{self.device_name}',请确认设备已开机且处于配网模式") + return False + + print(f"找到设备: {device.address}") + print(f"正在连接...") + + self.client = BleakClient(device, timeout=15.0) + await self.client.connect() + print(f"已连接, MTU={self.client.mtu_size}") + + # 验证服务 + svc = self.client.services.get_service(SERVICE_UUID) + if not svc: + print(f"错误: 未发现配网服务 (UUID: 0xABF0)") + return False + print(f"发现配网服务 0xABF0") + + # 启用 NOTIFY + await self.client.start_notify(CHAR_NOTIFY_UUID, self._on_notify) + print(f"NOTIFY 已启用 (0xABF2)") + await asyncio.sleep(0.3) + return True + + async def send_cmd(self, cmd: int, payload: bytes = b""): + """发送二进制命令""" + data = bytes([cmd]) + payload + hex_str = data.hex(" ") + print(f" -> [0x{cmd:02x}] {hex_str}") + await self.client.write_gatt_char(CHAR_WRITE_UUID, data, response=True) + + async def provision_wifi(self, ssid: str, password: str): + """执行 WiFi 配网流程""" + print(f"\n{'='*50}") + print(f" 开始配网: SSID=\"{ssid}\"") + print(f"{'='*50}\n") + + # 第1步: 设置 SSID + print("[1/3] 发送 SSID...") + await self.send_cmd(CMD_SET_SSID, ssid.encode("utf-8")) + await asyncio.sleep(0.3) + + # 第2步: 设置密码(设置密码后设备会自动发起连接) + print("[2/3] 发送密码...") + await self.send_cmd(CMD_SET_PASSWORD, password.encode("utf-8")) + + # 第3步: 等待连接结果 + print("[3/3] 等待WiFi连接结果 (最长30秒)...") + result = await self._wait_wifi_result(timeout=30.0) + + if result is None: + print("\n超时: 未收到WiFi连接结果") + # 可尝试显式发送连接命令 + print("尝试发送显式连接命令...") + await self.send_cmd(CMD_CONNECT_AP) + result = await self._wait_wifi_result(timeout=30.0) + + if result is None: + print("\n配网结果: 超时,未收到设备响应") + elif result: + print("\n配网结果: WiFi 连接成功!") + else: + print("\n配网结果: WiFi 连接失败") + + return result + + async def _wait_wifi_result(self, timeout: float = 30.0): + """等待 WiFi 状态通知""" + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + remaining = deadline - time.monotonic() + if remaining <= 0: + break + self._notify_event.clear() + try: + await asyncio.wait_for(self._notify_event.wait(), timeout=remaining) + except asyncio.TimeoutError: + break + # 检查最新通知 + if self.notifications: + last = self.notifications[-1] + if len(last) >= 2 and last[0] == RESP_WIFI_STATUS: + return last[1] == 1 # 1=成功, 0=失败 + return None + + async def scan_wifi_list(self): + """请求 WiFi 扫描列表""" + print(f"\n{'='*50}") + print(f" 扫描 WiFi 列表") + print(f"{'='*50}\n") + + self.notifications.clear() + await self.send_cmd(CMD_GET_WIFI_LIST) + + # WiFi 扫描需要时间,等待列表结束标记 + print("等待扫描结果 (最长15秒)...") + deadline = time.monotonic() + 15.0 + wifi_list = [] + got_end = False + + while time.monotonic() < deadline and not got_end: + remaining = deadline - time.monotonic() + if remaining <= 0: + break + self._notify_event.clear() + try: + await asyncio.wait_for(self._notify_event.wait(), timeout=remaining) + except asyncio.TimeoutError: + break + + # 只处理最新一条通知 + if self.notifications: + n = self.notifications[-1] + if len(n) >= 3 and n[0] == RESP_WIFI_LIST: + rssi = struct.unpack("b", bytes([n[1]]))[0] + ssid_len = n[2] + ssid = n[3:3 + ssid_len].decode("utf-8", errors="replace") + wifi_list.append({"ssid": ssid, "rssi": rssi}) + elif len(n) >= 1 and n[0] == RESP_WIFI_LIST_END: + got_end = True + + if wifi_list: + print(f"\n扫描到 {len(wifi_list)} 个WiFi网络:") + print(f" {'序号':>4} {'RSSI':>6} {'SSID'}") + print(f" {'─'*4} {'─'*6} {'─'*30}") + for i, w in enumerate(wifi_list, 1): + print(f" {i:>4} {w['rssi']:>4}dBm {w['ssid']}") + else: + print("未扫描到WiFi网络") + + return wifi_list + + async def get_wifi_status(self): + """查询 WiFi 状态""" + print(f"\n{'='*50}") + print(f" 查询 WiFi 状态") + print(f"{'='*50}\n") + + self.notifications.clear() + await self.send_cmd(CMD_GET_WIFI_STATUS) + await self._wait_notifications(timeout=5.0, count=1) + + async def disconnect(self): + """断开连接""" + if self.client and self.client.is_connected: + try: + await self.client.stop_notify(CHAR_NOTIFY_UUID) + except Exception: + pass + await self.client.disconnect() + print("已断开BLE连接") + + +async def main(): + parser = argparse.ArgumentParser( + description="BLE 配网协议测试脚本", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +使用示例: + # WiFi 配网 + python tests/ble_provision_test.py --ssid "MyWiFi" --pwd "12345678" + + # 扫描 WiFi 列表 + python tests/ble_provision_test.py --scan-wifi + + # 查询 WiFi 状态 + python tests/ble_provision_test.py --get-status + """ + ) + parser.add_argument("--device", default=DEFAULT_DEVICE, + help=f"BLE 设备名称 (默认: {DEFAULT_DEVICE})") + parser.add_argument("--ssid", help="要连接的 WiFi SSID") + parser.add_argument("--pwd", default="", help="WiFi 密码") + parser.add_argument("--scan-wifi", action="store_true", + help="扫描 WiFi 列表") + parser.add_argument("--get-status", action="store_true", + help="查询 WiFi 连接状态") + parser.add_argument("--timeout", type=float, default=10.0, + help="命令超时秒数 (默认: 10.0)") + args = parser.parse_args() + + # 至少指定一个操作 + if not args.ssid and not args.scan_wifi and not args.get_status: + parser.print_help() + print("\n请指定操作: --ssid/--scan-wifi/--get-status") + return 1 + + tester = BleProvisionTester(device_name=args.device, timeout=args.timeout) + + # 扫描连接 + if not await tester.scan_and_connect(): + return 1 + + try: + # 执行操作 + if args.scan_wifi: + await tester.scan_wifi_list() + + if args.get_status: + await tester.get_wifi_status() + + if args.ssid: + result = await tester.provision_wifi(args.ssid, args.pwd) + return 0 if result else 1 + + finally: + await tester.disconnect() + + return 0 + + +if __name__ == "__main__": + exit_code = asyncio.run(main()) + sys.exit(exit_code or 0)