最近更新于 2026-08-18 21:18
测试环境
- 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://github.com/espressif/arduino-esp32/blob/3.3.11/libraries/BLE/examples/Client_Server/Client_Server.ino
手机 BLE 调试参考:https://blog.iyatt.com/?p=25142
/*
* BLE 客户端与服务端共存示例(ESP32)
*
* 功能说明:
* 演示如何在同一台 ESP32 设备上同时运行 BLE 服务端 (Server) 与客户端 (Client) 功能。
* - 作为服务端:广播自定义服务,监听并响应外部设备的读写请求,定期发送通知 (Notify)。
* - 作为客户端:扫描周围设备,发现匹配的服务 UUID 后自动发起连接,并读写远程特征值。
*/
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEClient.h>
#include <BLEScan.h>
// ==================== 1. 全局配置与 UUID 定义 ====================
// 服务端使用的自定义服务与特征值 UUID
#define SERVER_SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define SERVER_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// 客户端搜寻的目标 UUID(此处设置为与服务端相同的 UUID,方便两台 ESP32 互联)
static BLEUUID clientServiceUUID(SERVER_SERVICE_UUID);
static BLEUUID clientCharUUID(SERVER_CHARACTERISTIC_UUID);
// 服务端对象指针
BLEServer *pServer = nullptr; // BLE 服务端实例
BLECharacteristic *pServerCharacteristic = nullptr; // BLE 服务端特征值实例
// 客户端对象指针及状态标志
static boolean doConnect = false; // 是否准备开始连接标志位
static boolean clientConnected = false; // 客户端连接状态标志位
static BLERemoteCharacteristic *pRemoteCharacteristic; // 远程服务器特征值对象指针
static BLEAdvertisedDevice *targetDevice; // 扫描到的目标设备指针
static BLEClient *pClient = nullptr; // BLE 客户端实例
BLEScan *pBLEScan = nullptr; // BLE 扫描器实例
// ==================== 2. 回调函数类定义 ====================
/**
* @brief 服务端连接/断开事件回调类
*/
class ServerCallbacks : public BLEServerCallbacks
{
// 当有客户端连接到本 ESP32 服务端时触发
void onConnect(BLEServer *pServer)
{
Serial.println("服务端:有客户端成功连接");
}
// 当连接到本服务端的客户端断开时触发
void onDisconnect(BLEServer *pServer)
{
Serial.println("服务端:客户端已断开连接");
// 客户端断开后重新开启广播,以便其他设备再次连接
BLEDevice::startAdvertising();
}
};
/**
* @brief 服务端特征值读写事件回调类
*/
class CharacteristicCallbacks : public BLECharacteristicCallbacks
{
// 当外部客户端向本特征值写入数据时触发
void onWrite(BLECharacteristic *pCharacteristic)
{
String value = pCharacteristic->getValue();
Serial.print("服务端:收到写入数据,内容为: ");
Serial.println(value.c_str());
}
// 当外部客户端读取本特征值时触发
void onRead(BLECharacteristic *pCharacteristic)
{
Serial.println("服务端:特征值被外部设备读取");
}
};
/**
* @brief 客户端连接/断开事件回调类
*/
class ClientCallbacks : public BLEClientCallbacks
{
// 当本客户端成功连接到远程服务器时触发
void onConnect(BLEClient *pClient)
{
Serial.println("客户端:成功连接到远程服务器");
clientConnected = true;
}
// 当与远程服务器断开连接时触发
void onDisconnect(BLEClient *pClient)
{
Serial.println("客户端:与远程服务器断开连接");
clientConnected = false;
}
};
/**
* @brief 客户端接收 Notify(通知)数据的回调函数
* @param pBLERemoteCharacteristic 触发通知的特征值指针
* @param pData 数据内容指针
* @param length 数据字节长度
* @param isNotify 是否为 Notify(通知)模式
*/
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)
{
Serial.print("客户端:收到特征值 ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" 的通知,数据长度: ");
Serial.println(length);
Serial.print("客户端:收到数据: ");
Serial.write(pData, length);
Serial.println();
}
/**
* @brief 客户端扫描发现新设备时的回调类
*/
class AdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
{
// 每扫描到一个广播设备时都会调用此方法
void onResult(BLEAdvertisedDevice advertisedDevice)
{
Serial.print("客户端:扫描到设备: ");
Serial.println(advertisedDevice.toString().c_str());
// 判断扫描到的设备是否包含我们正在寻找的目标服务 UUID,且当前未处于连接状态
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(clientServiceUUID) && !clientConnected)
{
Serial.println("客户端:找到包含目标服务的设备,停止扫描并准备建立连接...");
BLEDevice::getScan()->stop(); // 停止扫描以释放 BLE 资源
targetDevice = new BLEAdvertisedDevice(advertisedDevice); // 暂存设备信息
doConnect = true; // 设置标志位,告知 loop() 发起连接
}
}
};
// ==================== 3. 核心功能函数 ====================
/**
* @brief 客户端发起连接与服务搜寻的函数
* @return true 连接并成功获取特征值;false 连接或搜寻失败
*/
bool connectToServer()
{
Serial.print("客户端:正在尝试连接到设备 ");
Serial.println(targetDevice->getAddress().toString().c_str());
// 如果客户端未创建则实例化,已创建则复用
if (pClient == nullptr)
{
pClient = BLEDevice::createClient();
pClient->setClientCallbacks(new ClientCallbacks());
Serial.println("客户端:已创建新的 BLEClient 实例");
}
else
{
Serial.println("客户端:复用现有的 BLEClient 实例");
}
// 发起对目标的物理连接
if (!pClient->connect(targetDevice))
{
Serial.println("客户端:建立连接失败");
return false;
}
Serial.println("客户端:物理连接建立成功");
pClient->setMTU(517); // 请求最大 MTU (传输单元) 提升数据传输效率
// 在远程服务器上搜寻目标服务
BLERemoteService *pRemoteService = pClient->getService(clientServiceUUID);
if (pRemoteService == nullptr)
{
Serial.print("客户端:未能找到指定服务 UUID: ");
Serial.println(clientServiceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println("客户端:已找到目标服务");
// 在目标服务下获取对应的特征值
pRemoteCharacteristic = pRemoteService->getCharacteristic(clientCharUUID);
if (pRemoteCharacteristic == nullptr)
{
Serial.print("客户端:未能找到指定特征值 UUID: ");
Serial.println(clientCharUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println("客户端:已找到目标特征值");
// 如果特征值支持“读”属性,尝试读取一次初始值
if (pRemoteCharacteristic->canRead())
{
String value = pRemoteCharacteristic->readValue();
Serial.print("客户端:读取到的初始特征值: ");
Serial.println(value.c_str());
}
// 如果特征值支持“通知”属性,注册 Notify 回调函数
if (pRemoteCharacteristic->canNotify())
{
pRemoteCharacteristic->registerForNotify(notifyCallback);
Serial.println("客户端:已成功订阅 Notify 通知");
}
return true;
}
/**
* @brief 初始化配置 BLE 服务端
*/
void setupServer()
{
Serial.println("正在配置 BLE 服务端...");
// 创建服务端实例并设置回调
pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
// 创建服务
BLEService *pService = pServer->createService(SERVER_SERVICE_UUID);
// 创建特征值(授予 读、写、通知 属性)
pServerCharacteristic = pService->createCharacteristic(
SERVER_CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY);
// 设置特征值回调与初始值
pServerCharacteristic->setCallbacks(new CharacteristicCallbacks());
pServerCharacteristic->setValue("Hello from Coexistence Server");
// 启动服务
pService->start();
// 配置并启动 BLE 广播
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVER_SERVICE_UUID); // 将服务端 UUID 添加到广播数据中
pAdvertising->setScanResponse(true); // 开启扫描响应
pAdvertising->setMinPreferred(0x06); // 设置连接间隔偏好值(优化 iOS 连接兼容性)
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("服务端:广播已启动");
}
/**
* @brief 初始化配置 BLE 客户端扫描器
*/
void setupClient()
{
Serial.println("正在配置 BLE 客户端...");
// 配置扫描器属性
pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true); // 主动扫描模式(消耗更多功率,但可获得扫描响应)
pBLEScan->setInterval(100); // 扫描间隔 (ms)
pBLEScan->setWindow(99); // 扫描窗口 (ms)
Serial.println("客户端:扫描器配置完成");
}
// ==================== 4. Arduino 主入口 ====================
void setup()
{
Serial.begin(115200);
Serial.println("启动 BLE 客户端-服务端共存示例...");
// 全局初始化 BLE 设备并命名
BLEDevice::init("ESP32-Coexistence");
// 分别初始化服务端和客户端
setupServer();
setupClient();
// 启动首次扫描(扫描 10 秒,非持续重复模式)
pBLEScan->start(10, false);
Serial.println("初始化完成!设备正在作为服务端广播,同时作为客户端扫描...");
}
void loop()
{
static unsigned long lastServerUpdate = 0;
static unsigned long lastClientWrite = 0;
static unsigned long lastScanStart = 0;
unsigned long currentTime = millis();
// --- 逻辑 1:处理客户端连接请求 ---
if (doConnect && !clientConnected)
{
if (connectToServer())
{
Serial.println("客户端:已成功连接至远程服务端");
}
else
{
Serial.println("客户端:连接失败,准备重新启动扫描");
pBLEScan->start(10, false); // 连接失败时重新开启扫描
}
doConnect = false; // 重置触发标志
}
// --- 逻辑 2:服务端定期更新并推送数据(每 5 秒)---
if (currentTime - lastServerUpdate > 5000)
{
String value = "Server time: " + String(millis() / 1000);
pServerCharacteristic->setValue(value.c_str());
pServerCharacteristic->notify(); // 向所有已连接本服务端的设备发送数据通知
Serial.print("服务端:更新特征值并发送 Notify: ");
Serial.println(value);
lastServerUpdate = currentTime;
}
// --- 逻辑 3:客户端定期向远程服务器写入数据(每 3 秒)---
if (clientConnected && pRemoteCharacteristic && currentTime - lastClientWrite > 3000)
{
if (pRemoteCharacteristic->canWrite())
{
String clientValue = "Client msg: " + String(millis() / 1000);
pRemoteCharacteristic->writeValue(clientValue.c_str(), clientValue.length());
Serial.print("客户端:向远程特征值写入数据: ");
Serial.println(clientValue);
lastClientWrite = currentTime;
}
}
// --- 逻辑 4:未连接时定期重新触发扫描(每 15 秒)---
if (!clientConnected && currentTime - lastScanStart > 15000)
{
Serial.println("客户端:重新开始扫描周围设备...");
pBLEScan->start(10, false);
lastScanStart = currentTime;
}
delay(100); // 适当主循环延迟,避免主线程过载
}
在作为服务器的同时也可以作为客户端连接别的设备

基于 ESP32-WROOM-32 实践 BLE 同时作为服务器和客户端 – PlatformIO
