基于 ESP32-WROOM-32 实践 BLE 客户端 – PlatformIO

最近更新于 2026-08-09 23:12

测试环境

  • ESP32-WROOM-32
  • PlatformIO IDE 3.3.4
  • PlatformIO Core 6.1.19
  • pioarduino platform-espressif32 55.03.311(Arduino Release v3.3.11 based on ESP-IDF v5.5.5)

platformio.ini

[env:esp32dev]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip
board = esp32dev
framework = arduino
monitor_speed = 115200

服务端

工具见:https://blog.iyatt.com/?p=25020
我这里是 Android 手机,使用 nRF Connect 开蓝牙服务器来供 ESP32 连接。

点开左上角的三杠,点开Configure GATT server
file

下拉展开,点击加号新建 GATT 配置
file

点击 ADD SERVICE 添加服务,
名字这里设置的my service,可以自定义
UUID必须填(和代码里写死的连接目标一致):4fafc201-1fb5-459e-8fcc-c5c9c331914b
file

然后点击 ADD CHARACTERISTIC 添加特征
名字这里设置的my chara,可以自定义
UUID必须填(和代码里写死的特征目标一致):beb5483e-36e1-4688-b7f5-ea07361b26a8
属性要勾读Read、写Write、通知Notufy,权限要勾Read、Write
下面的初始值可以选Empty value(空值),也可以填文本或写十六进制
file

返回主页,切换到 ADVERTISER 选项卡,点击加号创建广播
显示名字我设置为:server test,可以自定义
点击ADD RECORD,选Service UUDI,然后填上面配置 GATT 服务的 UUID:4fafc201-1fb5-459e-8fcc-c5c9c331914b
勾选可连接Connectable和可被发现Discoverable
file

创建好广播点击开关打开,弹出的页面直接点 OK,现在就开启广播了
file

实践

源码参考来源:https://github.com/espressif/arduino-esp32/blob/3.3.11/libraries/BLE/examples/Client/Client.ino

/**
 * 一个功能丰富的 ESP32 BLE 客户端示例
 * 用于扫描、连接指定的 BLE 服务端,并进行数据读写和通知订阅
 * 作者:未知
 * 更新:chegewara
 */

#include <Arduino.h>
#include "BLEDevice.h" // ESP32 BLE 核心库
// #include "BLEScan.h"

// 目标 BLE 服务(Service)的 UUID,需与服务端保持一致
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// 目标 BLE 特征(Characteristic)的 UUID
static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");

// 控制逻辑与状态标志位
static boolean doConnect = false; // 标志:是否触发连接操作
static boolean connected = false; // 标志:当前是否已成功连接服务端
static boolean doScan = false;    // 标志:断开连接后是否重新开启扫描

// 远程特征指针与扫描到的设备对象指针
static BLERemoteCharacteristic *pRemoteCharacteristic;
static BLEAdvertisedDevice *myDevice;

/**
 * 接收服务端 Notify(通知)数据的回调函数
 * 当服务端修改特征值并主动推送到客户端时,该函数会被自动触发
 */
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)
{
    Serial.print("收到特征通知 (UUID: ");
    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print("),数据长度: ");
    Serial.println(length);
    Serial.print("接收到的数据内容: ");
    Serial.write(pData, length); // 按照字节流打印接收到的数据
    Serial.println();
}

/**
 * 客户端连接状态改变时的回调类
 */
class MyClientCallback : public BLEClientCallbacks
{
    // 当与服务端建立连接成功时触发
    void onConnect(BLEClient *pclient) {}

    // 当与服务端断开连接时触发
    void onDisconnect(BLEClient *pclient)
    {
        connected = false;
        Serial.println("与 BLE 服务端断开连接 (onDisconnect)");
    }
};

/**
 * 连接至远程 BLE 服务端的主逻辑函数
 */
bool connectToServer()
{
    Serial.print("正在尝试连接设备,MAC 地址: ");
    Serial.println(myDevice->getAddress().toString().c_str());

    // 1. 创建 BLE 客户端实例
    BLEClient *pClient = BLEDevice::createClient();
    Serial.println(" - 客户端创建成功");

    // 设置客户端状态回调(监听连接/断开事件)
    pClient->setClientCallbacks(new MyClientCallback());

    // 2. 发起连接
    // 直接传入 BLEAdvertisedDevice 对象能自动识别目标设备地址类型(Public 或 Private)
    pClient->connect(myDevice);
    Serial.println(" - 成功连接至服务端");

    // 3. 请求协商最大传输单元 (MTU),默认是 23 字节,此处请求设为 517 字节
    pClient->setMTU(517);

    // 4. 获取远程服务端对应的 Service(服务)
    BLERemoteService *pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr)
    {
        Serial.print("未能在目标设备上找到匹配的服务 UUID: ");
        Serial.println(serviceUUID.toString().c_str());
        pClient->disconnect();
        return false;
    }
    Serial.println(" - 找到目标服务");

    // 5. 从服务中获取对应的 Characteristic(特征)
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr)
    {
        Serial.print("未能在目标服务中找到匹配的特征 UUID: ");
        Serial.println(charUUID.toString().c_str());
        pClient->disconnect();
        return false;
    }
    Serial.println(" - 找到目标特征");

    // 6. 检查特征属性并进行【读取】操作
    if (pRemoteCharacteristic->canRead())
    {
        String value = pRemoteCharacteristic->readValue();
        Serial.print("成功读取特征当前值: ");
        Serial.println(value.c_str());
    }

    // 7. 检查特征属性并【订阅通知】
    if (pRemoteCharacteristic->canNotify())
    {
        // 注册回调函数以接收实时数据推送
        pRemoteCharacteristic->registerForNotify(notifyCallback);
    }

    connected = true; // 标记已成功连接并准备就绪
    return true;
}

/**
 * 扫描回调类:在后台扫描周边广播设备,寻找符合目标服务 UUID 的设备
 */
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
{
    /**
     * 每次扫描到周围有 BLE 设备广播时,系统都会调用此函数
     */
    void onResult(BLEAdvertisedDevice advertisedDevice)
    {
        Serial.print("发现 BLE 广播设备: ");
        Serial.println(advertisedDevice.toString().c_str());

        // 检查设备广播的数据中是否包含我们正在寻找的目标服务 UUID
        if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID))
        {

            // 找到目标设备后,立即停止后台扫描以节省资源
            BLEDevice::getScan()->stop();

            // 保存目标设备对象的副本
            myDevice = new BLEAdvertisedDevice(advertisedDevice);

            doConnect = true; // 触发 loop 中的连接逻辑
            doScan = true;    // 标记断开后需要重新扫描

        } // 找到目标服务端
    } // onResult
}; // MyAdvertisedDeviceCallbacks

void setup()
{
    Serial.begin(115200);
    Serial.println("启动 Arduino BLE 客户端程序...");

    // 初始化 ESP32 的 BLE 设备,参数可留空
    BLEDevice::init("");

    // 获取扫描器单例,并配置参数
    BLEScan *pBLEScan = BLEDevice::getScan();
    pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); // 设置广播回调
    pBLEScan->setInterval(1349);                                               // 扫描间隔 (ms)
    pBLEScan->setWindow(449);                                                  // 扫描窗口大小 (ms),需小于或等于间隔
    pBLEScan->setActiveScan(true);                                             // 开启主动扫描(可索要更多广播数据)

    // 启动第一次扫描,持续 5 秒,第 2 个参数为 false 表示重新扫描
    pBLEScan->start(5, false);
}

void loop()
{
    // 1. 如果扫描回调找到了目标设备,执行连接逻辑
    if (doConnect == true)
    {
        if (connectToServer())
        {
            Serial.println("我们现在已成功连接到 BLE 服务端。");
        }
        else
        {
            Serial.println("连接服务端失败,未采取进一步操作。");
        }
        doConnect = false; // 消费连接请求标志
    }

    // 2. 连接建立后的持续交互逻辑:写入数据 ➔ 重新读取验证
    if (connected)
    {
        // 构造写入数据(开机时长秒数)
        String newValue = "Time since boot: " + String(millis() / 1000);
        Serial.println("==========================================");
        Serial.println("【发送数据】: \"" + newValue + "\"");

        // 写入数据
        pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());

        // 延迟 100ms 留给服务端处理和更新缓存
        delay(100);

        // 重新读取特征值并打印
        if (pRemoteCharacteristic->canRead())
        {
            String actualValue = pRemoteCharacteristic->readValue();
            Serial.print("【回读验证】: ");
            Serial.println(actualValue.c_str());
        }
        else
        {
            Serial.println("【警告】该特征不支持读取 (canRead == false)");
        }
        Serial.println("==========================================");
    }
    // 3. 如果连接断开且需要恢复扫描,重新启动扫描
    else if (doScan)
    {
        BLEDevice::getScan()->start(0);
    }

    delay(2000); // 调整为每 2 秒循环一次
}

ESP32 连接到手机蓝牙,读取到初始值,并周期性修改特征值并读取修改后结果
file

手机端查看
可以看到当前特征值为:Time since boot: 113
file

基于 ESP32-WROOM-32 实践 BLE 客户端 – PlatformIO
Scroll to top
打开目录