🕸️ ESP32 蓝牙Mesh组网:从点对点到百节点自愈网络

📅 2026-06-03 · 📂 硬件编程 · ⏱ 阅读约 8 分钟

📺 B站推荐视频:嵌入式物联网通信蓝牙Mesh项目实战全流程 | BLE Mesh协议+4大网络节点+开发步骤详解

B站推荐视频:ESP32蓝牙Mesh项目实战全流程(52分钟,嵌入式物联网小学妹)

📖 概述

ESP-BLE-MESH 协议栈架构
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×33¥5 ×3
温湿度传感器DHT22 (AM2302)1¥12
OLED显示屏SSD1306 0.96寸 128×64 I2C1¥10
手机AppnRF 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节点面包板接线图
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 信道。