ESP32 实战教程:用 BLE GATT 做手机温湿度查看与 LED 控制

本文由 AI 协助整理。示例以 Arduino-ESP32 BLE API 为主,芯片型号、蓝牙栈和库版本可能影响接口。BLE 连接不等于安全认证,涉及门锁、继电器和隐私数据时必须增加配对、加密和权限控制。

ESP32 BLE GATT 传感器架构

先把 BLE 说成人话

经典蓝牙像一条可以持续通话的串口线,BLE 更像一套有目录的公告栏。Service 是目录,Characteristic 是目录下的条目,每个条目可以具备读取、写入或通知属性。

本项目创建一个环境服务:手机可以读取温湿度,也可以订阅温度和湿度通知;另有一个 LED 控制特征值,手机写入 ON 或 OFF 后,ESP32 执行并更新状态。

这个项目的重点不是把 UUID 抄到能编译,而是理解四个边界:服务负责组织功能,特征值负责数据,通知要先订阅,写入命令必须校验。手机工具能发现设备,只能说明广告包出现了,不能证明 GATT 业务已经正确。

硬件与工具

  • 支持 BLE 的 ESP32 芯片。
  • 一个温湿度传感器,可先使用固定模拟值。
  • 板载 LED 或低功率 LED。
  • 手机 BLE 调试工具,例如 nRF Connect 一类工具。

ESP32 经典型号支持 BLE,但 ESP32-C3、S3 等芯片的蓝牙能力与经典 ESP32 不完全相同。本文使用 BLE,不依赖蓝牙经典串口;选板时先确认 Arduino-ESP32 的 BLE 支持矩阵。

设计 UUID 和数据格式

学习阶段可以使用自定义 UUID:

#define SERVICE_UUID        "7d2a0001-3b7a-4c18-9f42-123456789001"
#define TEMP_UUID           "7d2a0002-3b7a-4c18-9f42-123456789001"
#define HUM_UUID            "7d2a0003-3b7a-4c18-9f42-123456789001"
#define LED_UUID            "7d2a0004-3b7a-4c18-9f42-123456789001"

温湿度通知使用文本,例如 24.6 和 53.1,便于手机工具直接查看。量产设备可以改用定长二进制,减少无线开销,但要在文档中明确字节序、缩放系数和版本。

UUID 稳定后不要频繁改。手机端可能缓存旧服务,调试时遇到特征值不更新,可以关闭蓝牙、重新扫描或清除工具缓存,而不是立刻怀疑 UUID 又被某个小数点改变了。

创建 BLE 服务和特征值

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLECharacteristic* tempChar = nullptr;
BLECharacteristic* humChar = nullptr;
BLECharacteristic* ledChar = nullptr;
bool deviceConnected = false;
bool ledOn = false;

class ServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer*) override {
    deviceConnected = true;
    Serial.println("BLE connected");
  }

  void onDisconnect(BLEServer* server) override {
    deviceConnected = false;
    Serial.println("BLE disconnected");
    delay(100);
    server->startAdvertising();
  }
};

初始化:

void setupBle() {
  BLEDevice::init("ESP32-Env-01");

  BLEServer* server = BLEDevice::createServer();
  server->setCallbacks(new ServerCallbacks());

  BLEService* service = server->createService(SERVICE_UUID);

  tempChar = service->createCharacteristic(
      TEMP_UUID, BLECharacteristic::PROPERTY_READ |
                 BLECharacteristic::PROPERTY_NOTIFY);
  humChar = service->createCharacteristic(
      HUM_UUID, BLECharacteristic::PROPERTY_READ |
                BLECharacteristic::PROPERTY_NOTIFY);
  ledChar = service->createCharacteristic(
      LED_UUID, BLECharacteristic::PROPERTY_READ |
                BLECharacteristic::PROPERTY_WRITE);

  tempChar->addDescriptor(new BLE2902());
  humChar->addDescriptor(new BLE2902());
  tempChar->setValue("--");
  humChar->setValue("--");
  ledChar->setValue("OFF");

  service->start();
  BLEAdvertising* advertising = BLEDevice::getAdvertising();
  advertising->addServiceUUID(SERVICE_UUID);
  advertising->setScanResponse(true);
  BLEDevice::startAdvertising();
}

BLE2902 是客户端配置描述符,手机工具通过它打开或关闭通知。只设置 PROPERTY_NOTIFY 而没有描述符,部分工具能看到特征值,却找不到可操作的通知开关。

处理写入命令

class LedCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic* characteristic) override {
    std::string value = characteristic->getValue();

    if (value == "ON") {
      ledOn = true;
      digitalWrite(LED_BUILTIN, HIGH);
    } else if (value == "OFF") {
      ledOn = false;
      digitalWrite(LED_BUILTIN, LOW);
    } else {
      Serial.println("Unknown LED command");
      return;
    }

    characteristic->setValue(ledOn ? "ON" : "OFF");
    Serial.printf("LED=%s\n", ledOn ? "ON" : "OFF");
  }
};

在 setupBle() 中为 LED 特征值注册回调:

ledChar->setCallbacks(new LedCallbacks());

不要直接把任意收到的字符串当成 GPIO 编号或 PWM 数值执行。写入值要有白名单、范围和失败响应。实际控制继电器时,必须在上电时设为安全状态,并在断开连接后决定是否自动关闭。

发送读取值和通知

void publishEnvironment() {
  float temperature = 24.6f; // 替换为真实传感器读数
  float humidity = 53.2f;

  if (!isfinite(temperature) || !isfinite(humidity)) return;

  char tempText[16];
  char humText[16];
  snprintf(tempText, sizeof(tempText), "%.1f", temperature);
  snprintf(humText, sizeof(humText), "%.1f", humidity);

  tempChar->setValue(tempText);
  humChar->setValue(humText);

  if (deviceConnected) {
    tempChar->notify();
    humChar->notify();
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  setupBle();
}

void loop() {
  static unsigned long lastPublish = 0;
  if (millis() - lastPublish >= 2000) {
    lastPublish = millis();
    publishEnvironment();
  }
  delay(10);
}

读取属性让手机主动取值,通知则由设备主动推送。通知频率不宜高于传感器真正更新的频率,否则只是把同一数字反复塞进无线链路。

手机端验证步骤

  1. 烧录程序,打开串口确认设备开始广播。
  2. 手机打开 BLE 工具,扫描 ESP32-Env-01。
  3. 找到自定义 Service UUID。
  4. 读取温度和湿度特征值,确认文本格式。
  5. 打开通知,等待数值周期变化。
  6. 向 LED 特征值写入 UTF-8 文本 ON,观察 LED。
  7. 写入 OFF,确认状态恢复。
  8. 关闭手机蓝牙,再打开,确认设备重新广告。

注意 BLE 工具的写入模式:有的工具默认按十六进制发送,输入 ON 可能变成错误字节。先确认是文本模式,再验证设备日志。

连接状态和功耗

BLE 连接时设备会保持更多无线活动,电流通常高于只做周期广播。若项目只需要单向上报,可以让设备周期广播制造商数据,手机扫描后读取,不必一直维持连接。

连接断开后重新开始广播是最基本的恢复动作。若传感器需要长期运行,应把采样周期、通知周期和连接超时分开,不要让手机是否在线决定传感器是否工作。

常见问题

现象 优先检查
手机搜不到设备 BLE 是否初始化、是否开始广播、芯片能力
能看到设备但看不到服务 Service UUID、服务是否 start()
特征值能读但无通知 BLE2902、手机是否开启 notify
写入没有效果 写入类型、大小写、回调是否注册
断开后不再出现 onDisconnect() 是否重启广告
BLE 初始化崩溃 与 Matter 或底层 BLE API 内存配置冲突

安全与扩展

BLE 特征值默认不是“只有你能写”。附近的人可能扫描到设备并尝试写控制特征。需要安全控制时,应启用配对和加密,限制可写特征,增加设备身份和命令确认。不要把门锁、加热器等高风险输出接到一个裸写特征上。

后续可以把温湿度特征改成标准 GATT UUID,让通用健康或环境工具识别;也可以增加电池电量、设备版本、采样周期配置,以及 OTA 前的 BLE 配网。功能越多,越要保持 UUID、数据格式和权限表的文档。

总结

BLE GATT 项目的核心是把功能拆成服务与特征值:读用于查询,通知用于推送,写用于命令。手机能发现设备只是第一步,真正可用还要处理通知订阅、重连、数据格式和写入校验。

先用文本值把协议跑通,再接入真实传感器和安全控制。这样既能快速看到结果,也不会把蓝牙协议、传感器错误和执行器风险一次揉成一团。

参考资料

打赏

如果这篇学习记录对你有帮助,欢迎扫码支持后续整理。

打赏码

打赏支持:王庆岩

posted on 2026-09-28 23:43  潇洒石头  阅读(14)  评论(0)    收藏  举报

导航