多台 ESP32 BLE 设备使用相同的 UUID(服务+特征),但不同设备名称(bleName)时是否相互干扰
答案是 **会干扰**,因为 BLE 的发现和连接机制不仅依赖设备名,还依赖广播数据和服务 UUID。以下是详细分析和解决方案:---
## **1. 问题原因分析**
### **(1) 相同 UUID 的冲突**
* **所有设备广播相同的 `SERVICE_UUID`**:
当 Python 端扫描设备时,会看到多个设备都广播 `00001101-0000-1000-8000-00805F9B34FB`,客户端可能无法区分它们,导致随机连接或连接失败。
* **特征 UUID 也相同**:
即使连接成功,如果 Python 端同时尝试读写多台设备,数据可能发送到错误的设备(因为特征 UUID 完全一致)。
### **(2) 设备名称(`bleName`)的作用有限**
* `bleName` 仅在扫描时显示,但 **BLE 连接实际依赖的是设备的 MAC 地址和广播数据**。
* 如果 Python 端未明确绑定目标设备的 MAC 地址,可能会错误地连接到同名设备(尤其在广播缓存干扰时)。
### **(3) 经典蓝牙 vs. BLE 的区别**
* 你使用的 UUID `00001101-...` 是 **经典蓝牙 SPP 的标准 UUID**,但在 BLE 模式下,它只是一个普通 UUID,没有特殊作用。
* **经典蓝牙 SPP 的 UUID 不能直接用于 BLE**,因为两者的协议栈完全不同。
---
## **2. 解决方案**
### **(1) 为每台设备生成唯一的服务 UUID**
修改代码,让每台设备使用 **不同的服务 UUID**(特征 UUID 可以相同):
```
// 设备1
#define SERVICE_UUID "00001101-0000-1000-8000-00805F9B34F1"
// 设备2
#define SERVICE_UUID "00001101-0000-1000-8000-00805F9B34F2"
// 设备3
#define SERVICE_UUID "00001101-0000-1000-8000-00805F9B34F3"
```
**优点**:
* Python 端可以明确选择连接特定设备(通过不同的 `SERVICE_UUID`)。
* 完全避免 UUID 冲突。
### **(2) 动态生成 UUID(推荐)**
在设备启动时,基于 MAC 地址生成唯一 UUID:
```
#include "BLEAddress.h"
String generateUniqueUUID() {
String mac = BLEDevice::getAddress().toString().c_str();
mac.replace(":", "");
return "0000" + mac.substring(0, 4) + "-0000-1000-8000-00805F9B34FB";
}
// 在 setup() 中:
String SERVICE_UUID = generateUniqueUUID();
```
**优点**:
* 完全自动化,无需手动配置。
* 保证每台设备的 UUID 唯一。
### **(3) Python 端明确指定目标设备**
在 Python 代码中,先扫描设备,再通过 **设备名或 MAC 地址** 过滤目标设备:
```
import asyncio
from bleak import BleakScanner, BleakClient
async def connect_to_device(device_name):
devices = await BleakScanner.discover()
for d in devices:
if d.name == device_name:
client = BleakClient(d.address)
await client.connect()
return client
raise Exception("Device not found")
# 使用示例
client = await connect_to_device("ESP32_Device1")
```
**优点**:
* 直接绑定物理设备,避免 UUID 冲突问题。
### **(4) 避免使用经典蓝牙的 SPP UUID**
如果确实需要 BLE 通信,建议使用 **随机生成的 UUID**,而非经典蓝牙的固定 UUID。例如:
```
// 随机生成(符合 UUID v4 规范)
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
```
(注:这是 Nordic UART Service 的 UUID,常用于 BLE 串口透传)
---
## **3. 现场问题排查步骤**
如果多台设备仍然异常,按以下步骤检查:
1. **确认 Python 端是否正确过滤设备**:
* 打印扫描到的所有设备,检查 `bleName` 和 MAC 地址是否唯一。
2. **检查 ESP32 的广播数据**:
* 确保 `pAdvertising->addServiceUUID(SERVICE_UUID)` 使用的是动态 UUID。
3. **验证单设备连接稳定性**:
* 如果单台设备偶尔断开,可能是电源或信号干扰问题。
4. **监听 BLE 广播包**:
* 使用手机 App(如 **nRF Connect**)扫描,确认每台设备的 UUID 和名称是否唯一。
---
## **4. 总结**
| 问题根源 | 解决方案 |
| ------------------------ | ------------------------------ |
| 所有设备 UUID 相同 | 为每台设备生成不同的服务 UUID|
| Python 端未绑定具体设备| 通过设备名或 MAC 地址过滤 |
| 使用经典蓝牙 UUID 于 BLE | 改用随机生成的 BLE UUID |
| 广播冲突 | 动态生成 UUID(基于 MAC 地址) |
**最终建议**:
* **优先使用动态 UUID 生成**(方案 2),确保唯一性。
* **Python 端严格绑定设备 MAC 地址**(方案 3),避免误连接。
* 如果仍有问题,提供具体的错误日志(如连接超时、数据错乱等),可以进一步分析。
代码:
#include "protocol.h"
// 全局变量
BLEServer* pServer = nullptr;
BLECharacteristic* pRxCharacteristic = nullptr;
String receivedData;
message_para_t g_message_para;
int g_current_score = 0;
// 回调类实现
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) override {
Serial.println("设备已连接");
}
void onDisconnect(BLEServer* pServer) override {
Serial.println("设备已断开");
BLEDevice::startAdvertising();
Serial.println("重新开始广播...");
}
};
class RxCharacteristicCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* pCharacteristic) override {
String rxValue = pCharacteristic->getValue().c_str(); // 修正类型转换
if (rxValue.length() > 0) {
receivedData += rxValue;
Serial.print("收到数据: ");
Serial.println(rxValue);
}
}
};
void processReceivedData(String data) {
data.trim();
// 使用ArduinoJson解析
StaticJsonDocument<256> doc; // 改用StaticJsonDocument避免动态内存分配
DeserializationError error = deserializeJson(doc, data);
if (error) {
Serial.print("JSON解析错误: ");
Serial.println(error.c_str());
return;
}
// 提取数据
const char* status = doc["status"];
int score = doc["score"];
// 验证数据
if (status == nullptr || score < 0 || score > 10) {
Serial.println("无效数据");
return;
}
// 状态处理
if (g_message_para.status != status) {
g_message_para.status = status;
if (strcmp(status, "start") == 0) {
Serial.println("疗愈开始");
stopAllEffects();
startFastChase();
}
else if (strcmp(status, "listening") == 0) {
stopAllEffects();
startClockwiseChase();
}
else if (strcmp(status, "thinking") == 0) {
stopAllEffects();
startFastBreathing();
}
else if (strcmp(status, "speaking") == 0) {
stopAllEffects();
startAnticlockwiseChase();
}
else if (strcmp(status, "stop") == 0) {
Serial.println("疗愈停止");
stopAllEffects();
startSlowBreathing();
}
}
// 评分处理
if (g_message_para.score != score) {
g_message_para.score = score;
on_new_score();
}
Serial.printf("状态更新: %s, 评分: %d\n", status, score);
}
void blue_setup() {
Serial.begin(115200);
// 生成设备SN
uint64_t chipid = ESP.getEfuseMac();
char sn;
snprintf(sn, sizeof(sn), "%04X", (uint16_t)(chipid >> 32));
Serial.printf("设备SN: %s\n", sn);
// 生成UUID
char service_uuid;
char characteristic_uuid;
snprintf(service_uuid, sizeof(service_uuid), "6E400001-B5A3-F393-E0A9-E50E24DC%s", sn);
snprintf(characteristic_uuid, sizeof(characteristic_uuid), "6E400002-B5A3-F393-E0A9-E50E24DC%s", sn);
// 初始化BLE
BLEDevice::init(String("BW-LED-") + sn);
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// 创建服务
BLEService* pService = pServer->createService(service_uuid);
// 创建特征
pRxCharacteristic = pService->createCharacteristic(
characteristic_uuid,
BLECharacteristic::PROPERTY_WRITE
);
pRxCharacteristic->setCallbacks(new RxCharacteristicCallbacks());
// 启动服务
pService->start();
// 开始广播
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(service_uuid);
pAdvertising->setScanResponse(true);
BLEDevice::startAdvertising();
Serial.println("BLE初始化完成");
Serial.printf("服务UUID: %s\n", service_uuid);
Serial.printf("特征UUID: %s\n", characteristic_uuid);
}
void data_process() {
if (receivedData.length() > 0 && receivedData.endsWith("\r\n")) {
processReceivedData(receivedData);
receivedData = "";
}
}
void update_outer_ring() {
if (g_message_para.status == "start") {
updateFastChase();
}
else if (g_message_para.status == "listening") {
updateClockwiseChase();
}
else if (g_message_para.status == "thinking") {
updateFastBreathing();
}
else if (g_message_para.status == "speaking") {
updateAnticlockwiseChase();
}
else if (g_message_para.status == "stop") {
updateSlowBreathing();
}
}
页:
[1]