《ESP32 物联网全栈实战-07》BLE 蓝牙

第 7 篇:BLE 蓝牙------近场通信与智能配网

WiFi 让 ESP32 连上了互联网,但有个鸡生蛋的问题:设备第一次怎么知道 WiFi 密码? BLE 蓝牙解决了这个问题------用手机通过蓝牙把 WiFi 凭据传给设备。同时,BLE 本身也是一个近场通信方案。


1. BLE 核心概念速览

在写代码之前,先搞清楚 BLE 的几个关键概念:

复制代码
BLE 角色:
├── Central(中心设备)→ 手机(主动扫描、发起连接)
└── Peripheral(外设) → ESP32(广播、等待连接)

GATT 数据层次:
Profile(配置文件)
  └── Service(服务)          ← UUID 标识,如 0x1809 = 健康温度计
        └── Characteristic(特征值)← 实际数据载体
              ├── Value(值)
              └── Descriptor(描述符)

Characteristics 的操作类型:

属性 含义 方向
Read 手机主动读 外设→手机
Write 手机主动写 手机→外设
Notify 外设主动推 外设→手机(无需手机轮询)
Indicate 带确认的推送 外设→手机(手机必须确认)

2. ESP32 作为 BLE 外设------完整代码

c 复制代码
// ble_sensor.c ------ BLE GATT 服务器,暴露温湿度服务

#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_main.h"
#include "esp_bt.h"

#define GATTS_SERVICE_UUID       0x00FF
#define GATTS_CHAR_TEMP_UUID     0xFF01
#define GATTS_CHAR_HUMI_UUID     0xFF02
#define GATTS_NUM_HANDLE         8

static uint8_t adv_config_done = 0;

// 广播数据:设备名 + 16-bit Service UUID
static esp_ble_adv_data_t adv_data = {
    .set_scan_rsp = false,
    .include_name = true,
    .include_txpower = true,
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

static esp_ble_adv_params_t adv_params = {
    .adv_int_min = 0x20,   // 32 × 0.625ms = 20ms
    .adv_int_max = 0x40,   // 64 × 0.625ms = 40ms
    .adv_type = ADV_TYPE_IND,
    .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
    .channel_map = ADV_CHNL_ALL,
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};

// GATT 事件回调
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
                                 esp_ble_gatts_cb_param_t *param) {
    switch (event) {
    case ESP_GATTS_REG_EVT:
        // 注册成功 → 设置广播数据
        esp_ble_gap_set_device_name("ESP32-Sensor");
        esp_ble_gap_config_adv_data(&adv_data);
        break;

    case ESP_GATTS_READ_EVT:
        // 手机读取温湿度
        if (param->read.handle == temp_handle) {
            float temp = 25.6f;  // 实际从传感器读
            esp_ble_gatts_send_response(gatts_if, param->read.conn_id,
                param->read.trans_id, ESP_GATT_OK, NULL);
        }
        break;

    case ESP_GATTS_CONNECT_EVT:
        // 手机连上了,可以降低广播间隔省电
        esp_ble_gap_stop_advertising();
        break;

    case ESP_GATTS_DISCONNECT_EVT:
        // 手机断开了,重新开始广播
        esp_ble_gap_start_advertising(&adv_params);
        break;

    default:
        break;
    }
}

// Gap 事件回调
static void gap_event_handler(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:
        adv_config_done = 1;
        esp_ble_gap_start_advertising(&adv_params);
        break;
    default:
        break;
    }
}

// 初始化 BLE
void ble_sensor_init(void) {
    esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);  // 只开 BLE,释放经典蓝牙内存

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    esp_bt_controller_init(&bt_cfg);
    esp_bt_controller_enable(ESP_BT_MODE_BLE);

    esp_bluedroid_init();
    esp_bluedroid_enable();

    esp_ble_gatts_register_callback(gatts_event_handler);
    esp_ble_gap_register_callback(gap_event_handler);
    esp_ble_gatts_app_register(GATTS_SERVICE_UUID);
}

3. BLE 配网------解决"第一次联网"问题

市面上几乎所有智能家居设备都用 BLE 配网。流程如下:

复制代码
┌──────┐                      ┌────────┐
│ 手机  │                      │ ESP32  │
│ App  │                      │        │
└──┬───┘                      └───┬────┘
   │  1. 扫描 BLE 设备             │
   │←─────────────────────────────│ 广播 "ESP32-Sensor"
   │  2. 连接                     │
   │─────────────────────────────→│
   │  3. 写入 WiFi SSID+Password  │
   │  (Write Characteristic)      │
   │─────────────────────────────→│ 收到凭据 → 存 NVS
   │  4. 断开连接                 │
   │←─────────────────────────────│ 重启,用新凭据连 WiFi
   │                              │
   │  5. 设备 via WiFi 上报状态   │
   │←═════════════════════════════│ "配网成功,IP=192.168.1.100"

ESP32 端的关键代码:

c 复制代码
// 定义配网专用的 Characteristic
// UUID: 0xFF03, 属性: Write, 最大长度: 64 字节

// 手机写入的 JSON: {"ssid":"MyWiFi","password":"12345678"}

static void gatts_event_handler(esp_gatts_cb_event_t event, ...) {
    case ESP_GATTS_WRITE_EVT: {
        // 收到手机发来的 WiFi 凭据
        char ssid[33] = {0}, pass[65] = {0};
        // 解析 JSON → 写入 NVS
        nvs_set_str(nvs_handle, "ssid", ssid);
        nvs_set_str(nvs_handle, "pass", pass);
        nvs_commit(nvs_handle);

        // 告诉手机"收到"
        esp_ble_gatts_send_response(gatts_if, param->write.conn_id, ...);

        // 延迟 1 秒后重启
        vTaskDelay(pdMS_TO_TICKS(1000));
        esp_restart();
    }
}

重启后 app_main 里:

c 复制代码
// 从 NVS 读凭据
nvs_get_str(nvs_handle, "ssid", ssid, &len);
nvs_get_str(nvs_handle, "pass", pass, &len2);

if (strlen(ssid) > 0) {
    wifi_connect(ssid, pass);  // 正常连 WiFi
} else {
    ble_provisioning_start();  // 没有凭据 → 进入配网模式
}

4. BLE 配网 vs SmartConfig vs 网页配网

方式 优点 缺点
BLE 配网 稳定、成功率高、支持回传状态 需要蓝牙
SmartConfig 不需要蓝牙 部分路由器不兼容、成功率约 70%
网页配网(AP模式) 不需要 App 用户操作繁琐(连热点→填表单)
声波配网 酷炫 环境噪声影响大

推荐方案:BLE 为主,AP 模式为备份。


下一篇:OTA 远程升级------设备装到天花板上了,怎么更新固件?

相关推荐
艾络迅Acceleronix1 小时前
环境监测终端:STM32+EG800Z AT+ 艾络迅平台实战|多传感器采集联动全流程
c语言·stm32·单片机·嵌入式硬件·物联网·1024程序员节
武雄(小星Ai)2 小时前
智能家居云依赖排坑:从 Nest 温控器被远程“变砖“到 Home Assistant 自托管救赎
物联网·智能家居·架构设计·homeassistant
星恒讯工业路由器7 小时前
Modbus透传和Modbus网关有什么区别?从字节转发到协议翻译的选型思路
网络·物联网·modbus·工业物联网·工业网关·rs485·协议转换
李永奉7 小时前
杰理可视化SDK开发-音频录制功能使用教程
笔记·单片机·嵌入式硬件·mcu·物联网·音视频
钱工的门窗技术笔记8 小时前
铝木复合门窗中木材的角色分类与界面可分解性:结构判定、失效路径与维护设计
网络·物联网·数据分析
艾络迅Acceleronix8 小时前
气象监测:STM32+EG800Z AT + 艾络迅平台实战|气压/光照/雨量采集全流程
stm32·单片机·嵌入式硬件·mcu·物联网·1024程序员节
注塑机数据采集联网8 小时前
中科晟达注塑机 MES 系统:从车间到数字化的智能制造实践
大数据·数据仓库·人工智能·物联网·制造·数据库架构
李永奉9 小时前
杰理可视化SDK开发-【BUG】AC7006F8芯片开启一拖二和双麦DNS通话算法后,大概率通话时蓝牙自动断连,RAM空间爆了问题处理
单片机·嵌入式硬件·mcu·物联网·电脑·bug
vortex59 小时前
IoT 安全剖析:从消费级摄像头到车联网的攻击面全景
物联网·struts·安全
IT规划师9 小时前
MQTT实战 - MQTT Broker选型,EMQX vs Mosquitto,我用过的真实对比(八)
物联网·mqtt·.net core·mqttnet