重构蓝牙配网: 替换BluFi为自定义GATT Server,修复手机蓝牙不可见问题
核心改动: - bluetooth_provisioning: 使用 esp_ble_gap_config_adv_data_raw() 原始广播 替代 BluFi API,采用自定义 GATT Server (Service 0xABF0, Write 0xABF1, Notify 0xABF2) 实现二进制配网协议,保留全部WiFi配网业务逻辑 - ble_service: 广播包名称移至 Scan Response,避免超31字节限制; GAP事件改用位掩码确保 adv_data 和 scan_rsp 都完成后再启动广播 - application: BLE JSON 服务从 Application 移至 WifiBoard 管理, HandleBleJsonCommand 改为接收 BleJsonService 引用参数 - wifi_board: 新增 StartBleJsonProvisioning(),配网入口切换回 StartBluFiProvisioning() 使用重构后的 GATT Server - 蓝牙设备名统一为 "Airhub_Ble" - 配网模式下跳过电量上报,避免无WiFi时HTTP请求失败 - 新增 tests/ble_provision_test.py 配网协议测试脚本 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
02ae116488
commit
77c7283d09
@ -349,7 +349,7 @@ menu "蓝牙配网 (Bluetooth Provisioning)"
|
|||||||
config BLUETOOTH_PROVISIONING_DEVICE_NAME
|
config BLUETOOTH_PROVISIONING_DEVICE_NAME
|
||||||
string "默认设备名称"
|
string "默认设备名称"
|
||||||
depends on BLUETOOTH_PROVISIONING_ENABLE
|
depends on BLUETOOTH_PROVISIONING_ENABLE
|
||||||
default "BLUFI_Airhub"
|
default "Airhub_Ble"
|
||||||
help
|
help
|
||||||
蓝牙配网时显示的默认设备名称。
|
蓝牙配网时显示的默认设备名称。
|
||||||
可以在运行时通过API修改。
|
可以在运行时通过API修改。
|
||||||
|
|||||||
@ -1574,8 +1574,7 @@ void Application::Start() {
|
|||||||
|
|
||||||
SetDeviceState(kDeviceStateIdle);
|
SetDeviceState(kDeviceStateIdle);
|
||||||
|
|
||||||
// 初始化 BLE JSON 通讯服务
|
// BLE JSON 通讯服务已移至 WifiBoard 中,仅在配网模式下启动
|
||||||
InitBleJsonService();
|
|
||||||
|
|
||||||
// 每次设备开机后idle状态下测试 自动检测并设置当前位置打印
|
// 每次设备开机后idle状态下测试 自动检测并设置当前位置打印
|
||||||
//此逻辑为冗余操作,当前NVS中没有城市信息时会自动调用 位置查询API
|
//此逻辑为冗余操作,当前NVS中没有城市信息时会自动调用 位置查询API
|
||||||
@ -3052,33 +3051,12 @@ const char* Application::DeviceStateToString(DeviceState state) {
|
|||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::InitBleJsonService() {
|
void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON* data, BleJsonService& service) {
|
||||||
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) {
|
|
||||||
auto& board = Board::GetInstance();
|
auto& board = Board::GetInstance();
|
||||||
|
|
||||||
// ---- ping ----
|
// ---- ping ----
|
||||||
if (cmd == "ping") {
|
if (cmd == "ping") {
|
||||||
ble_json_service_.SendResponse(cmd, msg_id, 0, "pong");
|
service.SendResponse(cmd, msg_id, 0, "pong");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3106,7 +3084,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON
|
|||||||
cJSON_AddNumberToObject(resp, "rssi", ap.rssi);
|
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);
|
cJSON_Delete(resp);
|
||||||
return;
|
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, "chip", SystemInfo::GetChipModelName().c_str());
|
||||||
cJSON_AddStringToObject(resp, "idf", app_desc->idf_ver);
|
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);
|
cJSON_Delete(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3131,7 +3109,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON
|
|||||||
cJSON* ssid_item = cJSON_GetObjectItem(data, "ssid");
|
cJSON* ssid_item = cJSON_GetObjectItem(data, "ssid");
|
||||||
cJSON* pwd_item = cJSON_GetObjectItem(data, "pwd");
|
cJSON* pwd_item = cJSON_GetObjectItem(data, "pwd");
|
||||||
if (!ssid_item || !cJSON_IsString(ssid_item) || strlen(ssid_item->valuestring) == 0) {
|
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;
|
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);
|
esp_err_t ret = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
|
||||||
if (ret != ESP_OK) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 断开当前连接并重新连接
|
// 断开当前连接并重新连接
|
||||||
esp_wifi_disconnect();
|
esp_wifi_disconnect();
|
||||||
ret = esp_wifi_connect();
|
ret = esp_wifi_connect();
|
||||||
ble_json_service_.SendResponse(cmd, msg_id, 0,
|
service.SendResponse(cmd, msg_id, 0,
|
||||||
ret == ESP_OK ? "connecting" : "connect failed");
|
ret == ESP_OK ? "connecting" : "connect failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3163,7 +3141,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON
|
|||||||
scan_config.show_hidden = false;
|
scan_config.show_hidden = false;
|
||||||
esp_err_t ret = esp_wifi_scan_start(&scan_config, true); // 阻塞扫描
|
esp_err_t ret = esp_wifi_scan_start(&scan_config, true); // 阻塞扫描
|
||||||
if (ret != ESP_OK) {
|
if (ret != ESP_OK) {
|
||||||
ble_json_service_.SendResponse(cmd, msg_id, -1, "scan failed");
|
service.SendResponse(cmd, msg_id, -1, "scan failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3192,7 +3170,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON
|
|||||||
free(ap_list);
|
free(ap_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
ble_json_service_.SendResponse(cmd, msg_id, 0, "ok", resp);
|
service.SendResponse(cmd, msg_id, 0, "ok", resp);
|
||||||
cJSON_Delete(resp);
|
cJSON_Delete(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3201,7 +3179,7 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON
|
|||||||
if (cmd == "set_vol") {
|
if (cmd == "set_vol") {
|
||||||
cJSON* vol_item = cJSON_GetObjectItem(data, "vol");
|
cJSON* vol_item = cJSON_GetObjectItem(data, "vol");
|
||||||
if (!vol_item || !cJSON_IsNumber(vol_item)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
int vol = vol_item->valueint;
|
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);
|
s.SetInt("output_volume", vol);
|
||||||
}
|
}
|
||||||
|
|
||||||
ble_json_service_.SendResponse(cmd, msg_id, 0, "ok");
|
service.SendResponse(cmd, msg_id, 0, "ok");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- reboot: 重启设备 ----
|
// ---- reboot: 重启设备 ----
|
||||||
if (cmd == "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)); // 等待响应发出
|
vTaskDelay(pdMS_TO_TICKS(500)); // 等待响应发出
|
||||||
Reboot();
|
Reboot();
|
||||||
return;
|
return;
|
||||||
@ -3231,10 +3209,10 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON
|
|||||||
// ---- ota: 触发 OTA 升级 ----
|
// ---- ota: 触发 OTA 升级 ----
|
||||||
if (cmd == "ota") {
|
if (cmd == "ota") {
|
||||||
if (device_state_ == kDeviceStateUpgrading) {
|
if (device_state_ == kDeviceStateUpgrading) {
|
||||||
ble_json_service_.SendResponse(cmd, msg_id, -1, "already upgrading");
|
service.SendResponse(cmd, msg_id, -1, "already upgrading");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ble_json_service_.SendResponse(cmd, msg_id, 0, "start ota");
|
service.SendResponse(cmd, msg_id, 0, "start ota");
|
||||||
Schedule([this]() {
|
Schedule([this]() {
|
||||||
CheckNewVersion();
|
CheckNewVersion();
|
||||||
});
|
});
|
||||||
@ -3247,14 +3225,14 @@ void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON
|
|||||||
std::string states;
|
std::string states;
|
||||||
if (thing_manager.GetStatesJson(states, true)) {
|
if (thing_manager.GetStatesJson(states, true)) {
|
||||||
cJSON* resp = cJSON_Parse(states.c_str());
|
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);
|
if (resp) cJSON_Delete(resp);
|
||||||
} else {
|
} else {
|
||||||
ble_json_service_.SendResponse(cmd, msg_id, 0, "ok");
|
service.SendResponse(cmd, msg_id, 0, "ok");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 未知命令 ----
|
// ---- 未知命令 ----
|
||||||
ble_json_service_.SendResponse(cmd, msg_id, -99, "unknown cmd");
|
service.SendResponse(cmd, msg_id, -99, "unknown cmd");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,6 +109,9 @@ public:
|
|||||||
bool IsDialogUploadEnabled() const { return dialog_upload_enabled_; }// 是否启用对话上传
|
bool IsDialogUploadEnabled() const { return dialog_upload_enabled_; }// 是否启用对话上传
|
||||||
void SetDialogUploadEnabled(bool enabled);// 设置对话上传状态
|
void SetDialogUploadEnabled(bool enabled);// 设置对话上传状态
|
||||||
|
|
||||||
|
// BLE JSON 命令处理 (由 WifiBoard 中的 BleJsonService 回调)
|
||||||
|
void HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON* data, BleJsonService& service);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application();// 构造函数
|
Application();// 构造函数
|
||||||
~Application();// 析构函数
|
~Application();// 析构函数
|
||||||
@ -193,10 +196,6 @@ private:
|
|||||||
void StartDialogWatchdog();// 启动对话看门狗
|
void StartDialogWatchdog();// 启动对话看门狗
|
||||||
void StopDialogWatchdog(); // 停止对话看门狗
|
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); // 状态枚举转字符串
|
const char* DeviceStateToString(DeviceState state); // 状态枚举转字符串
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public:
|
|||||||
AudioCodec();
|
AudioCodec();
|
||||||
virtual ~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; }
|
inline static int default_output_volume() { return kDefaultOutputVolume; }
|
||||||
|
|
||||||
virtual void SetOutputVolume(int volume);
|
virtual void SetOutputVolume(int volume);
|
||||||
|
|||||||
@ -61,19 +61,19 @@ static uint8_t cccd_val[2] = {0x00, 0x00};
|
|||||||
|
|
||||||
static esp_attr_value_t write_char_attr = {
|
static esp_attr_value_t write_char_attr = {
|
||||||
.attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN,
|
.attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN,
|
||||||
.attr_len = 0,
|
.attr_len = 1,
|
||||||
.attr_value = write_char_val,
|
.attr_value = write_char_val,
|
||||||
};
|
};
|
||||||
|
|
||||||
static esp_attr_value_t notify_char_attr = {
|
static esp_attr_value_t notify_char_attr = {
|
||||||
.attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN,
|
.attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN,
|
||||||
.attr_len = 0,
|
.attr_len = 1,
|
||||||
.attr_value = notify_char_val,
|
.attr_value = notify_char_val,
|
||||||
};
|
};
|
||||||
|
|
||||||
static esp_attr_value_t status_char_attr = {
|
static esp_attr_value_t status_char_attr = {
|
||||||
.attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN,
|
.attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN,
|
||||||
.attr_len = 0,
|
.attr_len = 1,
|
||||||
.attr_value = status_char_val,
|
.attr_value = status_char_val,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -354,10 +354,10 @@ bool BleJsonService::SendNotify(const char* json_str, uint16_t len) {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void BleJsonService::StartAdvertising() {
|
void BleJsonService::StartAdvertising() {
|
||||||
// 构建广播数据
|
// 广播包:不放名称,避免超过31字节导致手机系统蓝牙搜索不到
|
||||||
esp_ble_adv_data_t adv_data = {};
|
esp_ble_adv_data_t adv_data = {};
|
||||||
adv_data.set_scan_rsp = false;
|
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.include_txpower = true;
|
||||||
adv_data.min_interval = 0x0006;
|
adv_data.min_interval = 0x0006;
|
||||||
adv_data.max_interval = 0x0010;
|
adv_data.max_interval = 0x0010;
|
||||||
@ -372,7 +372,7 @@ void BleJsonService::StartAdvertising() {
|
|||||||
|
|
||||||
esp_ble_gap_config_adv_data(&adv_data);
|
esp_ble_gap_config_adv_data(&adv_data);
|
||||||
|
|
||||||
// Scan response 中也加设备名
|
// Scan Response:放完整设备名称,手机扫描时会主动请求
|
||||||
esp_ble_adv_data_t scan_rsp = {};
|
esp_ble_adv_data_t scan_rsp = {};
|
||||||
scan_rsp.set_scan_rsp = true;
|
scan_rsp.set_scan_rsp = true;
|
||||||
scan_rsp.include_name = 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) {
|
esp_ble_gap_cb_param_t* param) {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
||||||
// 广播数据设置完成,等扫描应答数据也完成后再启动广播
|
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: {
|
||||||
break;
|
// 两个事件都到达后才启动广播
|
||||||
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) {
|
||||||
esp_ble_gap_start_advertising(&ble_json_adv_params);
|
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;
|
break;
|
||||||
|
}
|
||||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||||
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||||
ESP_LOGE(TAG, "Advertising start failed: %d", param->adv_start_cmpl.status);
|
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;
|
break;
|
||||||
|
|
||||||
// ---- 客户端连接 ----
|
// ---- 客户端连接 ----
|
||||||
case ESP_GATTS_CONNECT_EVT:
|
case ESP_GATTS_CONNECT_EVT: {
|
||||||
conn_id_ = param->connect.conn_id;
|
conn_id_ = param->connect.conn_id;
|
||||||
connected_ = true;
|
connected_ = true;
|
||||||
notify_enabled_ = false;
|
notify_enabled_ = false;
|
||||||
@ -555,6 +563,7 @@ void BleJsonService::HandleGattsEvent(esp_gatts_cb_event_t event,
|
|||||||
// 连接后停止广播 (BLE 4.2 单连接时自动停止,但显式调用更安全)
|
// 连接后停止广播 (BLE 4.2 单连接时自动停止,但显式调用更安全)
|
||||||
esp_ble_gap_stop_advertising();
|
esp_ble_gap_stop_advertising();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 客户端断开 ----
|
// ---- 客户端断开 ----
|
||||||
case ESP_GATTS_DISCONNECT_EVT:
|
case ESP_GATTS_DISCONNECT_EVT:
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
#include <esp_gatts_api.h>
|
#include <esp_gatts_api.h>
|
||||||
|
#include <esp_gap_ble_api.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/queue.h>
|
#include <freertos/queue.h>
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -29,7 +29,7 @@ extern "C" {
|
|||||||
#define BLE_JSON_CHAR_VAL_MAX_LEN 512
|
#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
|
#define BLE_JSON_ADV_INT_MIN 0x40 // 40ms
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @file bluetooth_provisioning.h
|
* @file bluetooth_provisioning.h
|
||||||
* @brief BluFi蓝牙配网模块头文件
|
* @brief 蓝牙配网模块头文件(基于自定义 GATT Server)
|
||||||
*
|
*
|
||||||
* 本文件定义了BluFi蓝牙配网的相关接口,包括配网状态管理、
|
* 使用自定义 BLE GATT Server 替代 BluFi API 实现配网通讯,
|
||||||
* 事件处理、WiFi凭据传输等功能。提供简单易用的C++接口
|
* 采用原始广播数据(raw advertising)确保手机系统蓝牙可发现。
|
||||||
* 封装ESP-IDF的BLUFI功能,用于通过蓝牙进行WiFi配网操作。
|
* 保留完整的 WiFi 配网业务逻辑、状态机和事件回调。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@ -17,25 +17,55 @@
|
|||||||
|
|
||||||
// 使用条件编译避免IDE环境中的头文件错误
|
// 使用条件编译避免IDE环境中的头文件错误
|
||||||
#ifdef ESP_PLATFORM
|
#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_wifi.h"
|
||||||
#include "esp_event.h"
|
#include "esp_event.h"
|
||||||
#else
|
#else
|
||||||
// 在非ESP环境中定义必要的类型和常量
|
// 在非ESP环境中定义必要的类型和常量
|
||||||
typedef int esp_blufi_cb_event_t;
|
typedef int esp_event_base_t;
|
||||||
typedef void* esp_blufi_cb_param_t;
|
|
||||||
typedef void* wifi_ap_record_t;
|
typedef void* wifi_ap_record_t;
|
||||||
typedef void* esp_event_base_t;
|
|
||||||
#endif
|
#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 蓝牙配网状态枚举
|
* @brief 蓝牙配网状态枚举
|
||||||
*
|
|
||||||
* 定义BluFi配网过程中的各种状态,用于状态机管理和状态监控
|
|
||||||
*/
|
*/
|
||||||
enum class BluetoothProvisioningState {
|
enum class BluetoothProvisioningState {
|
||||||
IDLE, //< 空闲状态,未启动配网
|
IDLE, //< 空闲状态,未启动配网
|
||||||
INITIALIZING, //< 初始化中,正在初始化蓝牙和BluFi服务
|
INITIALIZING, //< 初始化中,正在初始化蓝牙和服务
|
||||||
ADVERTISING, //< 广播中,等待手机客户端连接
|
ADVERTISING, //< 广播中,等待手机客户端连接
|
||||||
CONNECTED, //< 已连接,手机客户端已连接到设备
|
CONNECTED, //< 已连接,手机客户端已连接到设备
|
||||||
PROVISIONING, //< 配网中,正在接收和处理WiFi凭据
|
PROVISIONING, //< 配网中,正在接收和处理WiFi凭据
|
||||||
@ -46,255 +76,100 @@ enum class BluetoothProvisioningState {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 蓝牙配网事件类型
|
* @brief 蓝牙配网事件类型
|
||||||
*
|
|
||||||
* 定义配网过程中可能发生的各种事件,用于事件回调和状态通知
|
|
||||||
*/
|
*/
|
||||||
enum class BluetoothProvisioningEvent {
|
enum class BluetoothProvisioningEvent {
|
||||||
STATE_CHANGED, //< 状态改变事件,配网状态发生变化
|
STATE_CHANGED, //< 状态改变事件
|
||||||
WIFI_CREDENTIALS, //< 收到WiFi凭据事件,从手机接收到WiFi信息
|
WIFI_CREDENTIALS, //< 收到WiFi凭据事件
|
||||||
WIFI_CONNECTED, //< WiFi连接成功事件,设备成功连接到WiFi网络
|
WIFI_CONNECTED, //< WiFi连接成功事件
|
||||||
WIFI_FAILED, //< WiFi连接失败事件,设备连接WiFi失败
|
WIFI_FAILED, //< WiFi连接失败事件
|
||||||
CLIENT_CONNECTED, //< 客户端连接事件,手机客户端连接到设备
|
CLIENT_CONNECTED, //< 客户端连接事件
|
||||||
CLIENT_DISCONNECTED //< 客户端断开事件,手机客户端断开连接
|
CLIENT_DISCONNECTED //< 客户端断开事件
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief WiFi凭据结构体
|
* @brief WiFi凭据结构体
|
||||||
*
|
|
||||||
* 存储从手机客户端接收到的WiFi连接信息
|
|
||||||
*/
|
*/
|
||||||
struct WiFiCredentials {
|
struct WiFiCredentials {
|
||||||
std::string ssid; //< WiFi网络名称(SSID)
|
std::string ssid; //< WiFi网络名称(SSID)
|
||||||
std::string password; //< WiFi网络密码
|
std::string password; //< WiFi网络密码
|
||||||
uint8_t bssid[6]; //< WiFi接入点的MAC地址(BSSID),可选
|
uint8_t bssid[6]; //< WiFi接入点的MAC地址(BSSID)
|
||||||
bool bssid_set; //< 是否设置了BSSID,用于指定特定的接入点
|
bool bssid_set; //< 是否设置了BSSID
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 蓝牙配网事件回调函数类型
|
* @brief 蓝牙配网事件回调函数类型
|
||||||
* @param event 事件类型
|
|
||||||
* @param data 事件数据(可选)
|
|
||||||
*/
|
*/
|
||||||
using BluetoothProvisioningCallback = std::function<void(BluetoothProvisioningEvent event, void* data)>;
|
using BluetoothProvisioningCallback = std::function<void(BluetoothProvisioningEvent event, void* data)>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 蓝牙配网封装类
|
* @brief 蓝牙配网封装类(基于自定义 GATT Server)
|
||||||
*
|
*
|
||||||
* 该类封装了ESP-IDF的BLUFI功能,提供简单易用的C++接口
|
* 使用自定义 BLE GATT Server + 原始广播数据替代 BluFi API,
|
||||||
* 用于通过蓝牙进行WiFi配网操作。支持状态管理、事件回调、WiFi凭据接收等功能。
|
* 手机系统蓝牙可直接搜索到设备。保留完整的 WiFi 配网业务逻辑。
|
||||||
*
|
|
||||||
* 典型使用流程:
|
|
||||||
* 1. 创建BluetoothProvisioning实例
|
|
||||||
* 2. 设置事件回调函数
|
|
||||||
* 3. 调用StartProvisioning()开始配网
|
|
||||||
* 4. 处理回调事件
|
|
||||||
* 5. 配网完成后调用StopProvisioning()
|
|
||||||
*/
|
*/
|
||||||
class BluetoothProvisioning {
|
class BluetoothProvisioning {
|
||||||
public:
|
public:
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
*
|
|
||||||
* 初始化蓝牙配网对象,设置默认参数和状态
|
|
||||||
*/
|
|
||||||
BluetoothProvisioning();
|
BluetoothProvisioning();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*
|
|
||||||
* 清理资源,停止配网服务,释放蓝牙相关资源
|
|
||||||
*/
|
|
||||||
~BluetoothProvisioning();
|
~BluetoothProvisioning();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化蓝牙配网功能
|
|
||||||
*
|
|
||||||
* 初始化蓝牙控制器、蓝牙栈和BluFi服务,为配网做准备
|
|
||||||
*
|
|
||||||
* @return true 初始化成功,false 初始化失败
|
|
||||||
*/
|
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 反初始化蓝牙配网功能
|
|
||||||
*
|
|
||||||
* 清理蓝牙资源,释放内存,恢复系统状态
|
|
||||||
*
|
|
||||||
* @return true 反初始化成功,false 反初始化失败
|
|
||||||
*/
|
|
||||||
bool Deinitialize();
|
bool Deinitialize();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 开始蓝牙配网
|
|
||||||
*
|
|
||||||
* 启动BluFi服务,开始广播等待手机客户端连接
|
|
||||||
*
|
|
||||||
* @param device_name 蓝牙设备名称(可选,默认为"BLUFI_Airhub"),手机端会看到此名称
|
|
||||||
* @return true 启动成功,false 启动失败
|
|
||||||
*/
|
|
||||||
bool StartProvisioning(const char* device_name = BLU_NAME);
|
bool StartProvisioning(const char* device_name = BLU_NAME);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 停止蓝牙配网
|
|
||||||
*
|
|
||||||
* 停止BluFi服务,断开客户端连接,停止蓝牙广播
|
|
||||||
*
|
|
||||||
* @return true 停止成功,false 停止失败
|
|
||||||
*/
|
|
||||||
bool StopProvisioning();
|
bool StopProvisioning();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前配网状态
|
|
||||||
* @return 当前状态
|
|
||||||
*/
|
|
||||||
BluetoothProvisioningState GetState() const { return state_; }
|
BluetoothProvisioningState GetState() const { return state_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置事件回调函数
|
|
||||||
*
|
|
||||||
* 设置用于接收配网事件通知的回调函数
|
|
||||||
*
|
|
||||||
* @param callback 事件回调函数,当配网过程中发生事件时会被调用
|
|
||||||
*/
|
|
||||||
void SetCallback(BluetoothProvisioningCallback callback) { callback_ = callback; }
|
void SetCallback(BluetoothProvisioningCallback callback) { callback_ = callback; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取最后收到的WiFi凭据
|
|
||||||
*
|
|
||||||
* 返回从手机客户端接收到的WiFi连接信息
|
|
||||||
*
|
|
||||||
* @return WiFi凭据结构体的常量引用
|
|
||||||
*/
|
|
||||||
const WiFiCredentials& GetWiFiCredentials() const { return wifi_credentials_; }
|
const WiFiCredentials& GetWiFiCredentials() const { return wifi_credentials_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否已连接客户端
|
|
||||||
*
|
|
||||||
* 检查当前是否有手机客户端连接到设备
|
|
||||||
*
|
|
||||||
* @return true 已连接,false 未连接
|
|
||||||
*/
|
|
||||||
bool IsClientConnected() const { return client_connected_; }
|
bool IsClientConnected() const { return client_connected_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前状态的字符串表示
|
|
||||||
*
|
|
||||||
* 将当前配网状态转换为可读的字符串形式,便于调试和日志输出
|
|
||||||
*
|
|
||||||
* @return 状态字符串
|
|
||||||
*/
|
|
||||||
std::string GetStateString() const;
|
std::string GetStateString() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 发送WiFi连接状态报告
|
|
||||||
*
|
|
||||||
* 向手机客户端报告WiFi连接尝试的结果
|
|
||||||
*
|
|
||||||
* @param success 连接是否成功
|
|
||||||
* @param reason 失败原因代码(仅在失败时有效)
|
|
||||||
*/
|
|
||||||
void ReportWiFiStatus(bool success, uint8_t reason = 0);
|
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);
|
void SendWiFiList(const wifi_ap_record_t* ap_list, uint16_t ap_count);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 可靠地发送设备MAC地址给手机客户端
|
|
||||||
*
|
|
||||||
* 该函数实现了增强的MAC地址发送机制,包括:
|
|
||||||
* - 多次重试机制,提高发送成功率
|
|
||||||
* - 连接状态双重检查,避免竞争条件
|
|
||||||
* - 重复发送检测,避免发送相同MAC地址
|
|
||||||
* - 详细的错误处理和日志记录
|
|
||||||
*
|
|
||||||
* @return true 发送成功,false 发送失败
|
|
||||||
*/
|
|
||||||
bool SendMacAddressReliably();
|
bool SendMacAddressReliably();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置MAC地址发送状态
|
|
||||||
*
|
|
||||||
* 在新的配网会话开始时调用,清除之前的发送记录
|
|
||||||
* 允许重新发送MAC地址
|
|
||||||
*/
|
|
||||||
void ResetMacSendingState();
|
void ResetMacSendingState();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BluetoothProvisioningState state_; //< 当前配网状态
|
BluetoothProvisioningState state_;
|
||||||
BluetoothProvisioningCallback callback_; //< 用户设置的事件回调函数
|
BluetoothProvisioningCallback callback_;
|
||||||
WiFiCredentials wifi_credentials_; //< 存储接收到的WiFi凭据信息
|
WiFiCredentials wifi_credentials_;
|
||||||
bool client_connected_; //< 客户端连接状态标志
|
bool client_connected_;
|
||||||
bool initialized_; //< 蓝牙配网模块初始化状态标志
|
bool initialized_;
|
||||||
bool delayed_disconnect_; //< 延迟断开连接标志,用于优雅断开
|
bool delayed_disconnect_;
|
||||||
bool wifi_connecting_; //< WiFi连接进行中标志
|
bool wifi_connecting_;
|
||||||
bool mac_address_sent_; //< MAC地址发送状态标志,避免重复发送
|
bool mac_address_sent_;
|
||||||
|
|
||||||
// 静态实例指针,用于C回调函数访问
|
static BluetoothProvisioning* instance_;
|
||||||
static BluetoothProvisioning* instance_; //< 单例实例指针,用于静态回调函数访问类成员
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置状态并触发回调
|
|
||||||
*
|
|
||||||
* 内部状态管理函数,更新当前状态并通知回调函数
|
|
||||||
*
|
|
||||||
* @param new_state 新的配网状态
|
|
||||||
*/
|
|
||||||
void SetState(BluetoothProvisioningState new_state);
|
void SetState(BluetoothProvisioningState new_state);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 触发事件回调
|
|
||||||
*
|
|
||||||
* 向用户注册的回调函数发送事件通知
|
|
||||||
*
|
|
||||||
* @param event 事件类型
|
|
||||||
* @param data 事件相关数据指针(可选)
|
|
||||||
*/
|
|
||||||
void TriggerCallback(BluetoothProvisioningEvent event, void* data = nullptr);
|
void TriggerCallback(BluetoothProvisioningEvent event, void* data = nullptr);
|
||||||
|
|
||||||
public:
|
#ifdef ESP_PLATFORM
|
||||||
/**
|
// GATT 相关成员
|
||||||
* @brief BluFi事件回调函数(静态函数)
|
esp_gatt_if_t gatts_if_ = ESP_GATT_IF_NONE;
|
||||||
*
|
uint16_t service_handle_ = 0;
|
||||||
* ESP-IDF BluFi库的静态回调函数,处理所有BluFi相关事件
|
uint16_t write_char_handle_ = 0;
|
||||||
*
|
uint16_t notify_char_handle_ = 0;
|
||||||
* @param event BluFi事件类型
|
uint16_t notify_cccd_handle_ = 0;
|
||||||
* @param param 事件参数结构体指针
|
uint16_t conn_id_ = 0;
|
||||||
*/
|
bool notify_enabled_ = false;
|
||||||
static void BlufiEventCallback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t* param);
|
uint16_t mtu_ = 23;
|
||||||
|
|
||||||
private:
|
// BLE 回调
|
||||||
/**
|
static void GattsEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param);
|
||||||
* @brief WiFi事件处理函数
|
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);
|
||||||
* 处理WiFi连接、断开等相关事件
|
|
||||||
*
|
// GATT 辅助方法
|
||||||
* @param arg 用户参数
|
void CreateService(esp_gatt_if_t gatts_if);
|
||||||
* @param event_base 事件基础类型
|
void StartAdvertising();
|
||||||
* @param event_id 事件ID
|
bool SendNotify(const uint8_t* data, uint16_t len);
|
||||||
* @param event_data 事件数据
|
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);
|
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);
|
static void IPEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||||
|
#endif
|
||||||
// 禁用拷贝构造和赋值操作
|
|
||||||
BluetoothProvisioning(const BluetoothProvisioning&) = delete;
|
BluetoothProvisioning(const BluetoothProvisioning&) = delete;
|
||||||
BluetoothProvisioning& operator=(const BluetoothProvisioning&) = delete;
|
BluetoothProvisioning& operator=(const BluetoothProvisioning&) = delete;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -72,22 +72,21 @@ std::string WifiBoard::GetBoardType() {
|
|||||||
void WifiBoard::EnterWifiConfigMode() {
|
void WifiBoard::EnterWifiConfigMode() {
|
||||||
ESP_LOGI(TAG, "🔵 进入配网模式 - 使用BluFi蓝牙配网");
|
ESP_LOGI(TAG, "🔵 进入配网模式 - 使用BluFi蓝牙配网");
|
||||||
|
|
||||||
// 直接启动BluFi配网,不再回退到WiFi AP模式
|
// 使用 BluFi 蓝牙配网
|
||||||
bool blufi_success = StartBluFiProvisioning();
|
bool success = StartBluFiProvisioning();
|
||||||
ESP_LOGI(TAG, "🔍 BluFi配网启动结果: %s", blufi_success ? "成功" : "失败");
|
ESP_LOGI(TAG, "🔍 BluFi配网启动结果: %s", success ? "成功" : "失败");
|
||||||
|
|
||||||
if (blufi_success) {
|
if (success) {
|
||||||
ESP_LOGI(TAG, "✅ BluFi配网启动成功,等待手机连接");
|
ESP_LOGI(TAG, "✅ BluFi配网启动成功,等待手机连接");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGW(TAG, "⚠️ BluFi配网启动失败,将持续重试BluFi配网(不使用WiFi AP模式)");
|
ESP_LOGW(TAG, "⚠️ BluFi配网启动失败,将持续重试");
|
||||||
ESP_LOGI(TAG, "🔄 持续重试BluFi蓝牙配网...");
|
|
||||||
|
|
||||||
// 持续重试BluFi配网
|
// 持续重试
|
||||||
while (true) {
|
while (true) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(5000)); // 等待5秒后重试
|
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||||
ESP_LOGI(TAG, "🔄 重试启动BluFi蓝牙配网...");
|
ESP_LOGI(TAG, "🔄 重试启动BluFi配网...");
|
||||||
if (StartBluFiProvisioning()) {
|
if (StartBluFiProvisioning()) {
|
||||||
ESP_LOGI(TAG, "✅ BluFi配网重试成功,等待手机连接");
|
ESP_LOGI(TAG, "✅ BluFi配网重试成功,等待手机连接");
|
||||||
return;
|
return;
|
||||||
@ -152,17 +151,14 @@ void WifiBoard::StartNetwork() {
|
|||||||
auto& ssid_manager = SsidManager::GetInstance(); // 获取SSID管理器实例
|
auto& ssid_manager = SsidManager::GetInstance(); // 获取SSID管理器实例
|
||||||
auto ssid_list = ssid_manager.GetSsidList(); // 获取SSID列表
|
auto ssid_list = ssid_manager.GetSsidList(); // 获取SSID列表
|
||||||
if (ssid_list.empty()) {
|
if (ssid_list.empty()) {
|
||||||
ESP_LOGI(TAG, "🔍 未找到WiFi凭据,启动BluFi蓝牙配网...");
|
ESP_LOGI(TAG, "🔍 未找到WiFi凭据,启动BluFi配网...");
|
||||||
if (StartBluFiProvisioning()) {
|
if (StartBluFiProvisioning()) {
|
||||||
ESP_LOGI(TAG, "✅ BluFi蓝牙配网启动成功,等待手机连接...");
|
ESP_LOGI(TAG, "✅ BluFi配网启动成功,等待手机连接...");
|
||||||
// BluFi配网启动成功,等待完成或超时
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// BluFi配网启动失败,继续尝试重新启动BluFi配网
|
ESP_LOGW(TAG, "❌ BluFi配网启动失败,将重试");
|
||||||
ESP_LOGW(TAG, "❌ BluFi蓝牙配网启动失败,将持续重试BluFi配网");
|
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||||
// 延迟后重试BluFi配网
|
ESP_LOGI(TAG, "🔄 重试启动BluFi配网...");
|
||||||
vTaskDelay(pdMS_TO_TICKS(5000)); // 等待5秒后重试
|
|
||||||
ESP_LOGI(TAG, "🔄 重试启动BluFi蓝牙配网...");
|
|
||||||
StartBluFiProvisioning();
|
StartBluFiProvisioning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -418,6 +414,63 @@ bool WifiBoard::StartBluFiProvisioning() {
|
|||||||
return true;// 启动成功,返回true
|
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配网状态
|
// 监控BluFi配网状态
|
||||||
void WifiBoard::MonitorBluFiProvisioning() {
|
void WifiBoard::MonitorBluFiProvisioning() {
|
||||||
ESP_LOGI(TAG, "Starting BluFi provisioning monitor...");
|
ESP_LOGI(TAG, "Starting BluFi provisioning monitor...");
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "board.h"
|
#include "board.h"
|
||||||
#include "bluetooth_provisioning.h"
|
#include "bluetooth_provisioning.h"
|
||||||
|
#include "ble_service.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
@ -33,7 +34,8 @@ protected:
|
|||||||
bool blufi_provisioning_success_ = false; ///< BluFi配网成功状态标志
|
bool blufi_provisioning_success_ = false; ///< BluFi配网成功状态标志
|
||||||
TickType_t blufi_start_time_ = 0; ///< BluFi配网开始时间戳
|
TickType_t blufi_start_time_ = 0; ///< BluFi配网开始时间戳
|
||||||
static const TickType_t BLUFI_TIMEOUT_MS = 300000; ///< BluFi配网超时时间(5分钟),避免过快重新进入配网
|
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 构造函数
|
* @brief 构造函数
|
||||||
@ -63,6 +65,9 @@ protected:
|
|||||||
*/
|
*/
|
||||||
bool StartBluFiProvisioning();
|
bool StartBluFiProvisioning();
|
||||||
|
|
||||||
|
// 使用 BLE JSON Service 替代 BluFi 进行配网
|
||||||
|
bool StartBleJsonProvisioning();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 监控BluFi配网进程
|
* @brief 监控BluFi配网进程
|
||||||
* 监控配网状态变化,处理超时和异常情况
|
* 监控配网状态变化,处理超时和异常情况
|
||||||
|
|||||||
@ -1124,11 +1124,14 @@ public:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 电量上报逻辑:每30秒上报一次(启动3秒后)
|
// 电量上报逻辑:每30秒上报一次(启动3秒后),配网模式下跳过
|
||||||
if (battery_report_enabled_) {
|
if (battery_report_enabled_) {
|
||||||
battery_report_ticks_++;
|
battery_report_ticks_++;
|
||||||
if (battery_report_ticks_ % kBatteryReportInterval == 0) {
|
if (battery_report_ticks_ % kBatteryReportInterval == 0) {
|
||||||
ReportBatteryToServer(battery_level_);
|
auto& wifi_station = WifiStation::GetInstance();
|
||||||
|
if (wifi_station.IsConnected()) {
|
||||||
|
ReportBatteryToServer(battery_level_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
356
tests/ble_provision_test.py
Normal file
356
tests/ble_provision_test.py
Normal file
@ -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)
|
||||||
Loading…
x
Reference in New Issue
Block a user