🕸️ ESP32 蓝牙Mesh组网:从点对点到百节点自愈网络 📅 2026-06-03 · 📂 硬件编程 · ⏱ 阅读约 8 分钟
📺 B站推荐视频:嵌入式物联网通信蓝牙Mesh项目实战全流程 | BLE Mesh协议+4大网络节点+开发步骤详解
B站推荐视频:ESP32蓝牙Mesh项目实战全流程(52分钟,嵌入式物联网小学妹)
📖 概述
ESP-BLE-MESH 协议栈分层架构(来源:Espressif官方文档) 上一阶段我们学了 BLE 蓝牙信标 和 BLE 配对通信 ——它们都是点对点 或星型拓扑 :手机连设备,设备连设备,一次只能一对一。但在智能家居和工业物联网中,我们需要几十甚至上百个节点同时通信 ,且任何一个节点挂掉都不影响整体网络——这就是 蓝牙Mesh 的用武之地。
蓝牙Mesh (BLE Mesh)是蓝牙技术联盟在 2017 年发布的组网协议。它基于 BLE 广播通道实现了多对多 (many-to-many)通信:每条消息通过泛洪 (flooding)机制在节点间逐跳转发,无需中心路由器,网络具备天然的自愈能力 ——某个节点掉线,消息自动绕行。
ESP32 从 Arduino-ESP32 2.0.0 起内置了 ESP-BLE-Mesh 协议栈,完全兼容 Bluetooth Mesh Profile v1.0.1。今天我们用 3 块 ESP32 搭建一个小型 Mesh 网络,实现手机 App 群控 LED + 传感器数据采集,彻底理解 Provisioning / Model / Publish-Subscribe 三大核心概念。
💡 BLE Mesh vs WiFi Mesh:怎么选? WiFi Mesh(如 painlessMesh 库)适合高速数据传输 (摄像头、固件升级),但功耗高、节点数受限(通常 <100)。BLE Mesh 适合低功耗传感器网络 ,理论支持 32767 个节点,典型场景包括智能照明、温湿度采集、资产追踪。学习 BLE Mesh,你就能设计真正的「大规模物联网」。
🛒 物料清单 物料 型号/参数 数量 参考价格 主控板 ESP32 DevKit V1 (WROOM-32) 3 ¥25 ×3 RGB LED模块 WS2812B 灯环(8灯)或普通 RGB LED×3 3 ¥5 ×3 温湿度传感器 DHT22 (AM2302) 1 ¥12 OLED显示屏 SSD1306 0.96寸 128×64 I2C 1 ¥10 手机App nRF Mesh (Android/iOS 免费) - - 面包板 + 杜邦线 830孔 + 公母各20根 3套 ¥12 ×3 合计 约 ¥133
⚡ 为什么用3块? 2块只能看到一跳直连,体现不出 Mesh 的多跳转发 优势。3块可以形成 A→B→C 的链路,A 发消息给 C 时由 B 自动中继——这就是 Mesh 的灵魂。另外 ESP32-C3 和 ESP32-S3 也支持 BLE Mesh,接口完全一样。
🔌 接线图 ESP32 BLE Mesh 3节点接线图 — 面包板+LED+电阻完整接线(来源:innovationyourself.com)
【节点 A — Provisioner(配网器)】
ESP32 #1 → WS2812B 灯环
3.3V → VCC (5V)
GND → GND
GPIO 26 → DIN (数据输入)
ESP32 #1 → SSD1306 OLED
3.3V → VCC
GND → GND
GPIO 21 (SDA) → SDA
GPIO 22 (SCL) → SCL
【节点 B — 传感器节点】
ESP32 #2 → DHT22
3.3V → VCC
GND → GND
GPIO 4 → DATA
ESP32 #2 → RGB LED
GPIO 25 → R (红色,经220Ω限流电阻)
GPIO 26 → G (绿色,经220Ω限流电阻)
GPIO 27 → B (蓝色,经220Ω限流电阻)
【节点 C — 灯控节点】
ESP32 #3 → WS2812B 灯环
3.3V → VCC (5V)
GND → GND
GPIO 26 → DIN
【拓扑示意】
Provisioner (A) ←→ Node (B) ←→ Node (C)
配网器+显示器 传感器节点 灯控节点
↑ 所有节点通过 BLE 广播通道 Mesh 互联
各节点之间不需要任何物理连线 ——它们通过 BLE 广播通道(37/38/39 三个广告信道)无线互联。每块 ESP32 只需接好自己的传感器/执行器即可。部署时让三块板子间距 5~10 米,能直观看到多跳转发的效果。
💻 基础代码:三节点 Mesh 网络 以下代码基于 Arduino-ESP32 内置的 BLE Mesh 库(ESP32 开发板包 ≥2.0.0 自带,无需额外安装)。实现:Provisioner(节点A)配网节点B和C → 手机 nRF Mesh App 控制任意节点的 LED 开关 → 节点B 上报 DHT22 温湿度数据。
节点 B — 传感器 + OnOff Server(被控节点)
// ===== BLE Mesh 传感器节点 — ESP32 #2 + DHT22 =====
#include <BLEMesh.h>
#include <DHT.h> #define DHTPIN 4
#define DHTTYPE DHT22
#define LED_R 25
#define LED_G 26
#define LED_B 27
DHT dht (DHTPIN, DHTTYPE);
// Mesh 事件回调
void meshEventCallback (uint32_t event, void * param) {
switch (event) {
case MESH_EVENT_PROVISIONED:
Serial.println ("✅ 节点已配网!" );
digitalWrite (LED_G, HIGH); // 绿灯=已入网 break ;
case MESH_EVENT_ONOFF_GET:
Serial.println ("📩 收到 OnOff GET" );
break ;
case MESH_EVENT_ONOFF_SET:
bool state = *(bool *)param;
Serial.printf ("💡 LED状态: %s\\n" , state ? "ON" : "OFF" );
digitalWrite (LED_R, state ? HIGH : LOW);
break ;
}
}
void setup () {
Serial.begin (115200 );
dht.begin ();
pinMode (LED_R, OUTPUT);
pinMode (LED_G, OUTPUT);
pinMode (LED_B, OUTPUT);
// 初始化 BLE Mesh 节点
BLEMesh.init ();
BLEMesh.setEventCallback (meshEventCallback);
BLEMesh.enableProvisioning (); // 允许被配网 Serial.println ("Mesh Node 启动,等待配网..." );
digitalWrite (LED_B, HIGH); // 蓝灯=等待配网
}
void loop () {
static unsigned long lastRead = 0 ;
// 每5秒读取传感器并通过 Mesh 上报 if (millis () - lastRead > 5000 ) {
lastRead = millis ();
float t = dht.readTemperature ();
float h = dht.readHumidity ();
if (!isnan (t) && !isnan (h)) {
uint8_t data[4 ];
data[0 ] = (uint8_t )(t * 10 ); // 温度×10
data[1 ] = (uint8_t )(h * 10 ); // 湿度×10
BLEMesh.publishSensorData (0x0000 , data, 2 );
Serial.printf ("📤 Mesh上报: T=%.1f℃ H=%.1f%%\\n" , t, h);
}
}
}
节点 A — Provisioner + OLED 显示器
// ===== BLE Mesh Provisioner — ESP32 #1 + SSD1306 =====
#include <BLEMesh.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display (128 , 64 , &Wire, -1 );
int nodeCount = 0 ;
void meshEventCallback (uint32_t event, void * param) {
switch (event) {
case MESH_EVENT_SCAN_RESULT:
Serial.printf ("🔍 发现设备: %s\\n" , (char *)param);
break ;
case MESH_EVENT_PROV_COMPLETE:
nodeCount++;
Serial.printf ("✅ 配网完成! 当前节点数: %d\\n" , nodeCount);
updateOLED ();
break ;
case MESH_EVENT_SENSOR_DATA:
uint8_t * data = (uint8_t *)param;
float t = data[0 ] / 10.0 ;
float h = data[1 ] / 10.0 ;
Serial.printf ("📥 传感器数据: T=%.1f℃ H=%.1f%%\\n" , t, h);
showSensorData (t, h);
break ;
}
}
void updateOLED () {
display.clearDisplay ();
display.setCursor (0 , 0 );
display.println ("BLE Mesh Provisioner" );
display.printf ("Nodes: %d online\\n" , nodeCount);
display.display ();
}
void showSensorData (float t, float h) {
display.clearDisplay ();
display.setCursor (0 , 0 );
display.println ("BLE Mesh Sensor" );
display.printf ("Temp: %.1f C\\n\" , t);
display.printf ("Hum : %.1f %%\\n\" , h);
display.display ();
}
void setup () {
Serial.begin (115200 );
display.begin (SSD1306_SWITCHCAPVCC, 0x3C );
// 初始化 Provisioner
BLEMesh.initProvisioner (); // 配网器模式
BLEMesh.setEventCallback (meshEventCallback);
BLEMesh.startScan (30 ); // 扫描30秒 updateOLED ();
Serial.println ("Provisioner 就绪,扫描中..." );
}
void loop () {
// Provisioner 主循环:自动处理 Mesh 协议栈事件 delay (100 );
}
🔧 代码执行流程:
(1) 三块 ESP32 烧录各自代码后上电;
(2) 节点B和C 进入广播模式(蓝灯亮),等待配网;
(3) 节点A(Provisioner)扫描到B和C,自动完成配网(绿灯亮=入网成功);
(4) 打开手机 nRF Mesh App,连接 Provisioner,即可通过 App 对任意节点下发 OnOff 命令——即使手机和节点C隔了两跳,消息也会经由节点B自动中继到达。
🚀 进阶应用 1. 发布/订阅模型(Pub-Sub)— Mesh 的精髓 BLE Mesh 最强大的设计是 Publish-Subscribe 模式:发送方不关心「发给谁」,只把消息发布 (Publish)到某个组播地址 (Group Address);接收方订阅 (Subscribe)感兴趣的地址,自动收到消息。这使得一对多控制 极其优雅——一条广播就能让整层楼的灯同时亮起。
// 发布到组播地址 0xC000(客厅所有灯)
uint16_t groupAddr = 0xC000 ;
BLEMesh.setPublishAddress (groupAddr);
BLEMesh.onoffSet (true ); // 一条消息 → 所有订阅该组的灯同时亮 // 订阅组播地址(灯节点初始化时调用)
BLEMesh.subscribe (0xC000 ); // 收到发往 0xC000 的消息就响应
2. 传感器模型 — 不只有开关 BLE Mesh 规范定义了标准 Model (模型),除了 Generic OnOff,还有:
Model 类型 用途 典型传感器 Generic OnOff 开关控制 继电器、LED Generic Level 模拟量控制(0-65535) 调光、调速 Sensor Server 传感器数据上报 温度、湿度、光照 Light HSL 色相/饱和度/亮度 RGB 氛围灯 Vendor Model 自定义私有协议 任意自定义数据
3. 中继与朋友节点 — 低功耗大覆盖 Mesh 网络中有四种特殊节点角色:
┌─────────────┬──────────────────────────────────┐
│ 角色 │ 功能说明 │
├─────────────┼──────────────────────────────────┤
│ Relay 中继 │ 转发消息,扩展网络覆盖(默认开启)│
│ Proxy 代理 │ 让手机通过 GATT 连接接入 Mesh 网络│
│ Friend 朋友 │ 缓存消息给休眠的 Low Power 节点 │
│ Low Power │ 周期性唤醒接收消息,极致省电 │
└─────────────┴──────────────────────────────────┘
实际部署时,插电的 ESP32 做 Relay/Friend,电池供电的传感器节点设为 Low Power + 绑定 Friend——Friend 替你缓存消息,传感器每 5 秒醒来取一次,功耗降低 90% 以上。
4. WiFi 共存 — Mesh 网关连云端 结合第5/6篇 WiFi 和第5/22篇 MQTT ,将 Provisioner 升级为 Mesh-to-Cloud 网关 :
// Provisioner 兼任 MQTT 网关 — BLE Mesh + WiFi 共存
#include <WiFi.h>
#include <PubSubClient.h> void setup () {
WiFi.begin ("SSID" , "PASSWORD" );
BLEMesh.initProvisioner (); // BLE Mesh 和 WiFi 同时运行
}
// 收到 Mesh 传感器数据 → 转发到 MQTT 云端
void meshEventCallback (uint32_t event, void * param) {
if (event == MESH_EVENT_SENSOR_DATA) {
uint8_t * d = (uint8_t *)param;
char payload[64 ];
sprintf (payload, "{\\"temp\\":%.1f,\\"hum\\":%.1f}" ,
d[0 ]/10.0 , d[1 ]/10.0 );
mqtt.publish ("home/mesh/sensor" , payload);
}
}
⚡ ESP32 单天线如何共存? ESP32 的 BLE Mesh 和 WiFi 共用一根天线,通过时分复用 (TDM)切换。Arduino-ESP32 2.0.x 原生支持共存模式,实测在 WiFi STA + BLE Mesh 场景下,两者吞吐量不会有明显下降。但注意:WiFi AP 模式 + BLE Mesh 同时运行会因信道冲突导致丢包率上升,推荐用 STA 模式。
🎯 实战场景 场景一:全屋智能照明系统 需求: 三室一厅,客厅 6 盏灯、卧室 4 盏、厨房 2 盏,全部通过手机 App 控制。需要支持场景联动 (回家模式=客厅+过道全亮)、一键全关 。
方案: 每盏灯内置 ESP32 + WS2812 + 继电器模块作为 Mesh Node,客厅角落放一个插电的 ESP32 做 Provisioner + Proxy。灯节点订阅各自的组播地址(客厅 0xC001 / 卧室 0xC002 / 厨房 0xC003),再统一订阅 0xC000(全屋)。手机 App 连上 Proxy 后,一条组播消息就能场景联动——Mesh 的多跳转发确保信号覆盖全屋无死角。
物料升级: 批量采购 ESP32-C3 超小模组 (¥8/片,15×18mm),直接焊在灯板背面,比 DevKit 省空间又便宜。
场景二:养殖场环境监测网络 需求: 养猪场 10 个猪舍分布在 200m×100m 范围,每个猪舍需要实时监测温湿度和氨气浓度(MQ-137),数据汇总到办公室显示屏。
方案: 每个猪舍放一个 ESP32 + DHT22 + MQ-137 作为 Mesh Node(Sensor Server Model),猪舍之间的间距恰好做 Relay 中继。办公室 ESP32 做 Provisioner + OLED 本地显示 + 4G 模块上云。Mesh 泛洪路由确保即使某个猪舍断电,数据通过邻居节点绕行——这就是自愈网络的价值 。
联动已学模块: DHT22(5/16)+ OLED(5/14)+ MQTT(5/22)+ LoRa(5/23,Mesh 覆盖不到的远端猪舍用 LoRa 桥接回 Mesh 网络)。
⚠️ 常见问题 ❌ 问题1:节点烧录后手机扫不到设备 原因: ESP32 默认的 BLE 广播间隔较长(100ms+),nRF Mesh App 扫描窗口可能错过。解决: 上电后等待 3-5 秒 让 BLE 协议栈完全初始化,然后下拉刷新 App 的 Scanner 页面。还扫不到的话,在代码中将广播间隔降到 20ms:BLEMesh.setAdvInterval(20, 30);(注意:间隔越短功耗越高,调试完改回默认值)。
❌ 问题2:配网成功但 App 无法控制节点 原因: 配网只分配了地址和密钥,还需要绑定 AppKey 到 Model (Configuration 步骤)。nRF Mesh App 配网后有一步「Bind AppKey」,很多人跳过导致控制失败。解决: 在 App 中依次点击节点 → 选择 Element 下的 Generic OnOff Model → 点击「Bind AppKey」→ 选择之前创建的 AppKey → OK。绑定成功后重新发送 OnOff 命令即可。
❌ 问题3:超过3个节点后网络延迟剧增 原因: BLE Mesh 默认使用泛洪 (flooding)转发——每个节点收到消息都转发一次。节点数增加,网络中广播包指数级膨胀,信道拥塞。解决: (1) 将不参与中继的节点关闭 Relay 功能:BLEMesh.setRelay(false);;(2) 设置 TTL 值限制跳数:BLEMesh.setTTL(3);(最多3跳);(3) 在 nRF Mesh App 的 Network Settings 中启用「Managed Flooding」(需 Mesh Profile v1.1+,ESP-IDF 5.x 支持)。大型网络(>50节点)建议用 ESP-IDF 替代 Arduino 以获得更细粒度的网络控制。
❌ 问题4:BLE Mesh 和 WiFi 同时开启后 BLE 连接不稳定 原因: WiFi 和 BLE 共用 2.4GHz 频段。如果 WiFi 使用 40MHz 带宽的信道(如信道 6+10),会严重挤压 BLE 的 3 个广播信道(37/38/39 = 2402/2426/2480 MHz)。解决: 将 WiFi 路由器固定在信道 1 或 11 (20MHz 带宽),避开 BLE 的 2426MHz 和 2480MHz 广播信道。代码层调用 WiFi.begin() 时,ESP32 会自动选择干扰最小的信道——关键是路由端别用 Auto 信道。