✔零知开源(零知IDE)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 "配置环境" 转移到 "创意实现",极大降低了技术门槛。零知IDE编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知开源(零知IDE)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
目录
[1.1 硬件清单](#1.1 硬件清单)
[1.2 接线方案表](#1.2 接线方案表)
[1.3 接线图](#1.3 接线图)
[1.4 连接实物图](#1.4 连接实物图)
[3.1 Mesh网络核心管理](#3.1 Mesh网络核心管理)
[3.2 网页控制界面](#3.2 网页控制界面)
[3.3 串口命令解析器](#3.3 串口命令解析器)
[3.4 设备通信接收回调](#3.4 设备通信接收回调)
[3.5 主程序框架](#3.5 主程序框架)
[4.1 操作流程](#4.1 操作流程)
[4.2 视频演示](#4.2 视频演示)
[五、ESP MESH组网技术讲解](#五、ESP MESH组网技术讲解)
[5.1 ESP-MESH 核心概念](#5.1 ESP-MESH 核心概念)
[5.2 Mesh网络通信流程](#5.2 Mesh网络通信流程)
[Q1:智能灯做 Bridge 节点,必须先连路由器再 Mesh?](#Q1:智能灯做 Bridge 节点,必须先连路由器再 Mesh?)
[Q2: 设备无法连接到Mesh网络怎么办?](#Q2: 设备无法连接到Mesh网络怎么办?)
[Q3: 网页控制界面无法访问?](#Q3: 网页控制界面无法访问?)
项目概述
本项目基于零知 ESP8266(ESP-12F) 主控芯片,依托
painlessMesh库实现 ESP-MESH 无线自组网,构建去中心化的智能家居控制系统。核心围绕 MESH 组网技术,打通多设备间的无线通信链路,支持三种设备类型的灵活部署,兼顾实用性与扩展性
项目难点及解决方案
问题描述:在Mesh网络中,设备动态加入和退出,需要实时维护设备状态,确保控制命令能够准确送达
**解决方案:**每个设备每5秒发送心跳包,包含设备类型和状态;30秒未收到心跳的设备标记为离线;设备状态变化时立即广播,保证网络同步
一、系统接线部分
1.1 硬件清单
| 组件 | 型号 | 数量 | 备注 |
|---|---|---|---|
| 主控板 | 零知ESP8266(ESP-12F) | 3块 | 智能家居节点 |
| DHT11温湿度传感器 | DHT11 | 1个 | 温湿度监测 |
| HC-SR505人体感应器 | PIR传感器 | 1个 | 运动检测 |
| LED灯 | 直插LED灯 | 1个 | 被控设备 |
| 杜邦线 | 公对公 | 若干 | 连接用 |
| 电源 | 5V/2A USB电源 | 3个 | 设备供电 |
1.2 接线方案表
严格按照代码config.h中引脚定义配置:
| ESP8266引脚 | 功能定义 | 连接设备 | 备注 |
|---|---|---|---|
| D4 (GPIO2) | LED_PIN | 板载LED | 状态指示灯 |
| D1 (GPIO5) | CONTROL_PIN | 继电器IN引脚 | 仅TYPE_LIGHT设备 |
| D2 (GPIO4) | MOTION_PIN | HC-SR501 OUT引脚 | 仅TYPE_MOTION设备 |
| D5 (GPIO14) | DHT_PIN | DHT11 OUT引脚 | 仅TYPE_SENSOR设备 |
| 3.3V | 电源正极 | 所有传感器VCC | 注意电压匹配 |
| GND | 电源负极 | 所有传感器GND | 共地连接 |
1.3 接线图

1.4 连接实物图

二、安装与使用部分
2.1 开源平台-输入"ESP8266 MESH组网"并搜索-代码下载自动打开

2.2 连接-验证-上传

2.3 调试-串口监视器

三、代码讲解部分
代码整体架构
项目代码核心分为 4 个模块:Mesh 网络初始化、设备驱动模块、指令交互模块、自动化控制
3.1 Mesh网络核心管理
cpp
// ==================== 设备索引查找 ====================
int getDeviceIndex(uint32_t nodeId) {
for (int i = 0; i < deviceCount; i++) {
if (deviceList[i].nodeId == nodeId) return i;
}
return -1;
}
// ==================== 自动联动:运动检测控制灯光 ====================
void autoControlLightByMotion(bool hasMotion) {
uint32_t lightId = findOnlineLight();
if (lightId == 0) {
Serial.println("[联动] 未找到可用的智能灯设备");
return;
}
String action = hasMotion ? "on" : "off";
// 如果目标是本机
if (lightId == mesh.getNodeId()) {
#if MY_DEVICE_TYPE == TYPE_LIGHT
Serial.printf("[联动] 本地灯光自动%s\n", hasMotion ? "开启" : "关闭");
handleLightControl(action);
#endif
} else {
// 如果是远程设备
Serial.printf("[联动] 自动控制灯 %u %s\n", lightId, hasMotion ? "开启" : "关闭");
sendMeshCommand(lightId, action);
}
}
每个ESP8266的唯一标识符nodeId,由Mesh网络自动分配;自动添加新设备,定期清理离线设备
3.2 网页控制界面
cpp
server.on("/api/system", HTTP_GET, [this](AsyncWebServerRequest *request){
DynamicJsonDocument doc(512);
doc["myNodeId"] = mesh.getNodeId();
doc["freeHeap"] = ESP.getFreeHeap();
doc["uptime"] = millis();
doc["deviceType"] = MY_DEVICE_TYPE;
JsonObject localDevice = doc.createNestedObject("localDevice");
localDevice["deviceType"] = MY_DEVICE_TYPE;
#if MY_DEVICE_TYPE == TYPE_LIGHT
localDevice["state"] = ledState ? "on" : "off";
#elif MY_DEVICE_TYPE == TYPE_SENSOR
localDevice["temperature"] = String(temperature, 1);
localDevice["humidity"] = String(humidity, 1);
#elif MY_DEVICE_TYPE == TYPE_MOTION
localDevice["motion"] = motionDetected;
#endif
String response;
serializeJson(doc, response);
request->send(200, "application/json", response);
});
// API: 控制命令
server.on("/api/control", HTTP_POST,
[](AsyncWebServerRequest *request){},
NULL,
[this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
if (len > 128) {
request->send(400, "application/json", "{\"error\":\"请求过大\"}");
return;
}
String body = "";
for (size_t i = 0; i < len; i++) {
body += (char)data[i];
}
DynamicJsonDocument doc(128);
DeserializationError error = deserializeJson(doc, body);
if (error) {
request->send(400, "application/json", "{\"error\":\"JSON解析失败\"}");
return;
}
uint32_t nodeId = doc["nodeId"];
String action = doc["action"].as<String>();
if (nodeId == mesh.getNodeId()) {
executeLocalCommand(action.c_str());
request->send(200, "application/json", "{\"success\":true,\"message\":\"本地命令执行成功\"}");
} else {
sendMeshCommand(nodeId, action);
request->send(200, "application/json", "{\"success\":true,\"message\":\"远程命令已发送\"}");
}
}
);
// API: 发现设备
server.on("/api/discover", HTTP_GET, [](AsyncWebServerRequest *request){
sendMeshBroadcast("status");
request->send(200, "application/json", "{\"success\":true}");
});
server.begin();
Serial.println("[Web] ✓ 服务器已启动");
HTML页面存储在Flash中,减少RAM使用;大HTML页面分两部分发送,避免内存溢出;使用AsyncWebServer,支持并发请求
3.3 串口命令解析器
cpp
// 解析命令并执行
void parseAndExecute(String cmd) {
cmd.trim();
cmd.toUpperCase();
if (cmd.length() == 0) return;
Serial.printf("[串口] 收到命令: %-25s \n", cmd.c_str());
// ==================== 帮助命令 ====================
if (cmd == "HELP" || cmd == "?") {
printHelp();
}
// ==================== 本地LED控制 ====================
else if (cmd == "LED:ON" || cmd == "ON") {
#if MY_DEVICE_TYPE == TYPE_LIGHT
Serial.println("[✓] 执行本地开灯命令");
handleLightControl("on");
#else
Serial.println("[✗] 本设备不是智能灯");
#endif
}
else if (cmd == "LED:OFF" || cmd == "OFF") {
#if MY_DEVICE_TYPE == TYPE_LIGHT
Serial.println("[✓] 执行本地关灯命令");
handleLightControl("off");
#else
Serial.println("[✗] 本设备不是智能灯");
#endif
}
else if (cmd == "LED:TOGGLE" || cmd == "TOGGLE") {
#if MY_DEVICE_TYPE == TYPE_LIGHT
Serial.println("[✓] 执行本地切换命令");
handleLightControl("toggle");
#else
Serial.println("[✗] 本设备不是智能灯");
#endif
}
// ==================== 远程控制命令 ====================
else if (cmd.startsWith("REMOTE:")) {
// 格式: REMOTE:<NodeID>:<ACTION>
// 例如: REMOTE:3237134842:ON
int firstColon = cmd.indexOf(':');
int secondColon = cmd.indexOf(':', firstColon + 1);
if (secondColon > 0) {
String nodeIdStr = cmd.substring(firstColon + 1, secondColon);
String action = cmd.substring(secondColon + 1);
uint32_t nodeId = nodeIdStr.toInt();
action.toLowerCase();
Serial.printf("[✓] 发送远程命令到节点 %u: %s\n", nodeId, action.c_str());
sendMeshCommand(nodeId, action);
} else {
Serial.println("[✗] 命令格式错误!");
Serial.println(" [正确格式] REMOTE:<NodeID>:<ACTION>");
Serial.println(" [示例] REMOTE:3237134842:ON");
}
}
// ==================== 查询命令 ====================
else if (cmd == "LIST") {
printDeviceList();
}
else if (cmd == "STATE" || cmd == "STATUS") {
printSystemState();
}
else if (cmd == "DISCOVER") {
Serial.println("[✓] 触发设备发现");
sendMeshBroadcast("status");
}
// ==================== 传感器命令 ====================
else if (cmd == "READ" || cmd == "SENSOR") {
#if MY_DEVICE_TYPE == TYPE_SENSOR
Serial.println("[✓] 触发传感器读取");
// 触发立即读取(在主循环中处理)
#else
Serial.println("[✗] 本设备不是传感器");
#endif
}
// ==================== 系统命令 ====================
else if (cmd == "RESTART" || cmd == "RESET") {
Serial.println("[串口] 系统重启中...");
delay(1000);
ESP.restart();
}
else if (cmd == "MEMORY" || cmd == "MEM") {
Serial.println("\n[内存信息]");
Serial.printf(" 空闲堆内存: %d 字节\n", ESP.getFreeHeap());
// Serial.printf(" 最小空闲: %d 字节\n", ESP.getMinFreeHeap());
Serial.printf(" 堆碎片率: %d%%\n", ESP.getHeapFragmentation());
}
else if (cmd == "INFO") {
printSystemInfo();
}
// ==================== 未知命令 ====================
else {
Serial.println("[✗] 未知命令");
Serial.println(" 输入 HELP 查看可用命令");
}
Serial.println();
}
100ms无输入自动清空缓冲区进行超时处理;每个操作都有明确的实时反馈
3.4 设备通信接收回调
cpp
void receivedCallback(uint32_t from, String &msg) {
Serial.printf("[接收] 来自 %u: %s\n", from, msg.c_str());
DynamicJsonDocument doc(384);
DeserializationError error = deserializeJson(doc, msg);
if (error) {
Serial.println("[错误] JSON解析失败");
return;
}
String type = doc["type"].as<String>();
// ==================== 处理心跳和状态更新 ====================
if (type == "status" || type == "heartbeat") {
int devType = doc["devType"];
updateDevice(from, devType);
int idx = getDeviceIndex(from);
if (idx != -1) {
// 更新灯光状态
if (devType == TYPE_LIGHT && doc.containsKey("state")) {
String stateStr = doc["state"].as<String>();
bool newLightState = (stateStr == "on");
// 只有状态真正改变时才打印
if (deviceList[idx].lightState != newLightState) {
deviceList[idx].lightState = newLightState;
Serial.printf("[状态] 灯 %u 更新为: %s\n", from, newLightState ? "开" : "关");
}
}
// 更新传感器数据
else if (devType == TYPE_SENSOR) {
if(doc.containsKey("temp")) {
deviceList[idx].temp = doc["temp"];
}
if(doc.containsKey("hum")) {
deviceList[idx].hum = doc["hum"];
}
}
// 更新运动传感器状态
else if (devType == TYPE_MOTION && doc.containsKey("motion")) {
bool newMotionState = doc["motion"];
// *** 关键:检测到运动状态变化时触发自动控制 ***
if (deviceList[idx].motionDetected != newMotionState) {
deviceList[idx].motionDetected = newMotionState;
Serial.printf("[状态] 运动传感器 %u: %s\n", from, newMotionState ? "检测到运动" : "运动停止");
// *** 自动联动控制灯光 ***
#ifndef DISABLE_AUTO_LIGHT_CONTROL
autoControlLightByMotion(newMotionState);
#endif
}
}
}
}
// ==================== 处理控制命令 ====================
else if (type == "cmd") {
String action = doc["action"].as<String>();
Serial.printf("[命令] 收到控制指令: %s\n", action.c_str());
#if MY_DEVICE_TYPE == TYPE_LIGHT
handleLightControl(action);
#elif MY_DEVICE_TYPE == TYPE_SENSOR
if (action == "read") {
Serial.println("[命令] 触发传感器读取");
// 主程序会在下一个循环中读取
}
#elif MY_DEVICE_TYPE == TYPE_MOTION
if (action == "status") {
Serial.println("[命令] 触发状态广播");
sendMeshBroadcast("status");
}
#endif
}
}
从其他设备接收 JSON 格式的消息,设备状态变化时广播到整个网络
3.5 主程序框架
cpp
/**************************************************************************************
* 文件: ESP8266_SmartHome_Mesh.ino
* 作者:零知实验室(深圳市在芯间科技有限公司)
* -^^- 零知实验室,让电子制作变得更简单! -^^-
* 时间: 2026-1-22
* 功能:ESP8266 Mesh 网络节点,支持 painlessMesh 和 网页控制
***************************************************************************************/
#include "config.h"
#include <painlessMesh.h>
#include "MeshNetwork.h"
#include "WebController.h"
#include "SerialParser.h"
#if MY_DEVICE_TYPE == TYPE_SENSOR
#include <DHT.h>
DHT dht(DHT_PIN, DHT_TYPE);
#endif
// ==================== 全局变量 ====================
Scheduler userScheduler;
painlessMesh mesh;
DeviceNode deviceList[MAX_DEVICES];
int deviceCount = 0;
bool ledState = false;
bool motionDetected = false;
float temperature = 0;
float humidity = 0;
SerialParser serialParser;
// ==================== 任务声明 ====================
void sendMessage();
Task taskSendMessage(TASK_SECOND * 5, TASK_FOREVER, &sendMessage);
// ==================== 灯光控制 ====================
void handleLightControl(String action) {
#if MY_DEVICE_TYPE == TYPE_LIGHT
bool oldState = ledState;
if (action == "on") {
digitalWrite(CONTROL_PIN, HIGH);
ledState = true;
Serial.println("[执行] 开灯");
} else if (action == "off") {
digitalWrite(CONTROL_PIN, LOW);
ledState = false;
Serial.println("[执行] 关灯");
} else if (action == "toggle") {
ledState = !ledState;
digitalWrite(CONTROL_PIN, ledState ? HIGH : LOW);
Serial.printf("[执行] 切换 -> %s\n", ledState ? "开" : "关");
}
// 状态改变时广播
if (oldState != ledState) {
sendMeshBroadcast("status");
}
#else
Serial.println("[警告] 本设备不是智能灯");
#endif
}
// ==================== 本地控制接口 ====================
void executeLocalCommand(const char* action) {
Serial.printf("[本地控制] 执行: %s\n", action);
#if MY_DEVICE_TYPE == TYPE_LIGHT
handleLightControl(String(action));
#elif MY_DEVICE_TYPE == TYPE_SENSOR
if (strcmp(action, "read") == 0) {
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
temperature = t;
humidity = h;
Serial.printf("[传感器] 温度: %.1f°C, 湿度: %.1f%%\n", t, h);
sendMeshBroadcast("status");
}
}
#elif MY_DEVICE_TYPE == TYPE_MOTION
if (strcmp(action, "status") == 0) {
Serial.printf("[本地控制] 运动: %s\n", motionDetected ? "有" : "无");
sendMeshBroadcast("status");
}
#endif
}
// ==================== 获取本地状态 ====================
String getLocalDeviceState() {
String state = "{";
#if MY_DEVICE_TYPE == TYPE_LIGHT
state += "\"state\":\"";
state += ledState ? "on" : "off";
state += "\",\"hasLight\":true";
#elif MY_DEVICE_TYPE == TYPE_SENSOR
state += "\"temperature\":";
state += String(temperature, 1);
state += ",\"humidity\":";
state += String(humidity, 1);
state += ",\"hasSensor\":true";
#elif MY_DEVICE_TYPE == TYPE_MOTION
state += "\"motion\":";
state += motionDetected ? "true" : "false";
state += ",\"hasMotion\":true";
#endif
state += "}";
return state;
}
// ==================== 发送心跳 ====================
void sendMessage() {
sendMeshBroadcast("heartbeat");
// 清理超时设备
unsigned long now = millis();
for(int i=0; i<deviceCount; i++){
if(deviceList[i].online && (now - deviceList[i].lastSeen > DEVICE_TIMEOUT)){
deviceList[i].online = false;
Serial.printf("[离线] 设备 %u\n", deviceList[i].nodeId);
}
}
}
// ==================== 硬件逻辑 ====================
void loopSensorLogic() {
#if MY_DEVICE_TYPE == TYPE_SENSOR
static unsigned long lastRead = 0;
if (millis() - lastRead > 10000) {
lastRead = millis();
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
temperature = t;
humidity = h;
Serial.printf("[传感器] 温度:%.2f°C 湿度:%.1f%%\n", t, h);
sendMeshBroadcast("status");
}
}
#elif MY_DEVICE_TYPE == TYPE_MOTION
static bool lastPinState = false;
static unsigned long lastChangeTime = 0;
static bool stableMotionState = false;
bool currentPinState = digitalRead(MOTION_PIN);
unsigned long now = millis();
// LED指示
digitalWrite(LED_PIN, currentPinState ? LOW : HIGH);
// 状态变化
if (currentPinState != lastPinState) {
lastPinState = currentPinState;
lastChangeTime = now;
}
// 防抖确认
if ((now - lastChangeTime) >= 3000) {
if (currentPinState != stableMotionState) {
stableMotionState = currentPinState;
motionDetected = stableMotionState;
Serial.printf("\n[运动] %s\n", stableMotionState ? " 检测到!" : "停止");
sendMeshBroadcast("status");
}
}
#endif
}
// ==================== 打印设备列表 ====================
void printDeviceList() {
Serial.println("设备列表");
Serial.printf(" 本机ID: %-33u \n", mesh.getNodeId());
#if MY_DEVICE_TYPE == TYPE_LIGHT
Serial.println(" 类型: 智能灯 ");
#elif MY_DEVICE_TYPE == TYPE_SENSOR
Serial.println(" 类型: 温湿度传感器 ");
#elif MY_DEVICE_TYPE == TYPE_MOTION
Serial.println(" 类型: 人体感应器 ");
#endif
Serial.printf(" 在线设备数: %-29d \n", deviceCount);
for (int i = 0; i < deviceCount; i++) {
Serial.println("───────────────────────────────────────────");
Serial.printf(" [%d] ID: %-33u \n", i+1, deviceList[i].nodeId);
Serial.printf(" 类型: %-2d 状态: %-20s \n",
deviceList[i].type,
deviceList[i].online ? "在线" : "离线");
}
}
// ==================== 打印系统状态 ====================
void printSystemState() {
Serial.println("系统状态");
Serial.printf(" 节点ID: %-30u \n", mesh.getNodeId());
Serial.printf(" 运行时间: %-27lu 秒 \n", millis()/1000);
Serial.printf(" 空闲内存: %-26d 字节 \n", ESP.getFreeHeap());
#if MY_DEVICE_TYPE == TYPE_LIGHT
Serial.printf(" LED状态: %-30s \n", ledState ? "开启" : "关闭");
#elif MY_DEVICE_TYPE == TYPE_SENSOR
Serial.printf(" 温度: %-27.1f °C \n", temperature);
Serial.printf(" 湿度: %-28.1f %% \n", humidity);
#elif MY_DEVICE_TYPE == TYPE_MOTION
Serial.printf(" 运动检测: %-30s \n", motionDetected ? "有运动" : "无运动");
#endif
}
// ==================== 初始化 ====================
void setup() {
Serial.begin(SERIAL_BAUD_RATE);
delay(1000);
Serial.println("\n");
Serial.println("[初始化]ESP8266智能家居Mesh网络节点 v2.0 ");
Serial.println();
// 1. 硬件初始化
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH); // 初始熄灭
#if MY_DEVICE_TYPE == TYPE_LIGHT
pinMode(CONTROL_PIN, OUTPUT);
digitalWrite(CONTROL_PIN, LOW);
ledState = false;
Serial.println("[硬件] ✓ 智能灯已初始化 (GPIO" + String(CONTROL_PIN) + ")");
#elif MY_DEVICE_TYPE == TYPE_MOTION
pinMode(MOTION_PIN, INPUT);
Serial.println("[硬件] ✓ 人体感应器已初始化 (GPIO" + String(MOTION_PIN) + ")");
Serial.println("[联动] ✓ 自动灯光控制已启用");
#elif MY_DEVICE_TYPE == TYPE_SENSOR
dht.begin();
Serial.println("[硬件] ✓ DHT11传感器已初始化 (GPIO" + String(DHT_PIN) + ")");
delay(2000);
// 初始读取
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
temperature = t;
humidity = h;
Serial.printf("[传感器] 初始值 - 温度: %.2f°C, 湿度: %.1f%%\n", t, h);
}
#endif
// 2. Mesh初始化
#ifdef ENABLE_BRIDGE_MODE
Serial.println("\n[网络] Bridge模式启动...");
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
// 连接到路由器
mesh.stationManual(STATION_SSID, STATION_PASSWORD);
mesh.setHostname("SmartHomeBridge");
// 设置为根节点
mesh.setRoot(true);
mesh.setContainsRoot(true);
Serial.println("[网络] ✓ Bridge节点配置完成");
#else
Serial.println("\n[网络] 纯Mesh模式启动...");
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
Serial.println("[网络] ✓ Mesh节点配置完成");
#endif
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
Serial.printf("[Mesh] 节点ID: %u\n", mesh.getNodeId());
Serial.printf("[Mesh] 网络: %s\n", MESH_PREFIX);
// 3. 启动Web服务
webController.begin();
webController.setDeviceData(deviceList, &deviceCount);
// 4. 串口解析器
serialParser.begin();
// 5. 启动任务
userScheduler.addTask(taskSendMessage);
taskSendMessage.enable();
Serial.println("[✓] 系统初始化完成! ");
#ifdef ENABLE_BRIDGE_MODE
Serial.println("\n[提示] Bridge模式:");
Serial.println(" - 等待连接路由器...");
Serial.println(" - 连接成功后可通过路由器IP访问");
Serial.println("[Bridge] IP: " + WiFi.localIP().toString());
#else
Serial.println("\n[提示] 访问: http://" + WiFi.softAPIP().toString());
#endif
Serial.println("\n[串口] 输入 HELP 查看命令\n");
}
// ==================== 主循环 ====================
void loop() {
mesh.update();
loopSensorLogic();
serialParser.update();
// 显示Bridge连接状态
#ifdef ENABLE_BRIDGE_MODE
static bool lastConnected = false;
static unsigned long lastCheck = 0;
if (millis() - lastCheck > 5000) {
lastCheck = millis();
bool connected = (WiFi.status() == WL_CONNECTED);
if (connected != lastConnected) {
lastConnected = connected;
if (connected) {
Serial.println("\n[Bridge] ✓ 已连接到路由器");
Serial.println("[Bridge] IP: " + WiFi.localIP().toString());
Serial.println("[Bridge] 网关: " + WiFi.gatewayIP().toString());
Serial.println();
} else {
Serial.println("\n[Bridge] ✗ 路由器连接断开\n");
}
}
}
#endif
}
/******************************************************************************
* 深圳市在芯间科技有限公司
* 淘宝店铺:在芯间科技零知板
* 店铺网址:https://shop533070398.taobao.com
* 版权说明:
* 1.本代码的版权归【深圳市在芯间科技有限公司】所有,仅限个人非商业性学习使用。
* 2.严禁将本代码或其衍生版本用于任何商业用途(包括但不限于产品开发、付费服务、企业内部使用等)。
* 3.任何商业用途均需事先获得【深圳市在芯间科技有限公司】的书面授权,未经授权的商业使用行为将被视为侵权。
******************************************************************************/
根据配置选择Bridge模式或纯Mesh模式、启动Web服务器和串口解析器、启动心跳任务,开始Mesh通信
系统流程图

PainlessMesh库解析
cpp
void onReceive(receivedCallback_t onReceive) {
using namespace painlessmesh;
this->callbackList.onPackage(
protocol::SINGLE,
[onReceive](protocol::Variant variant, std::shared_ptr<T>, uint32_t) {
auto pkg = variant.to<protocol::Single>();
onReceive(pkg.from, pkg.msg);
return false;
});
this->callbackList.onPackage(
protocol::BROADCAST,
[onReceive](protocol::Variant variant, std::shared_ptr<T>, uint32_t) {
auto pkg = variant.to<protocol::Broadcast>();
onReceive(pkg.from, pkg.msg);
return false;
});
}
注册消息接收逻辑,远程指令交互的核心入口
四、项目结果演示
准备3个零知ESP8266开发板,选择 TYPE_LIGHT 节点作为 Bridge,烧录开启ENABLE_BRIDGE_MODE的代码,其他节点(TYPE_SENSOR/TYPE_MOTION)烧录对应的代码
4.1 操作流程
①组网验证
Bridge 节点串口打印路由器 IP,其他节点打印 Mesh NodeID、串口发送LIST指令,可查看所有在线节点

串口打印出Bridge的IP地址为192.168.3.246
②网页访问
访问 http://<bridge-ip>、等待在线设备加入、点击按钮控制灯光开关、查看传感器实时数据

③串口命令测试
发送LED:TOGGLE控制本机灯光,发送REMOTE:1:ON控制 NodeID=1 的灯光
> LIST # 查看设备列表
> LED:TOGGLE # 切换本地灯光
> STATE # 查看系统状态
> REMOTE:12345678:ON # 远程控制
4.2 视频演示
零知ESP8266智能家居Mesh组网系统完整演示
本视频完整演示基于零知 ESP8266(ESP-12F)的 MESH 组网智能家居系统,包含 Bridge 节点配置、Mesh 自动组网、串口 / Web 双端控制、传感器数据采集、人体感应自动化等核心功能,直观展示 ESP-MESH 自组网的稳定性与实用性
五、ESP MESH组网技术讲解
ESP-MESH 允许分布在大范围区域内的大量设备节点 在同一个 WLAN(无线局域网)中相互连接,MESH 网络可以自主地构建和维护
ESP-MESH 网络架构示意图
网络中的节点不需要连接到中心节点,可以与相邻节点连接;无需受限于距离中心节点的位置,所有节点仍可互连
5.1 ESP-MESH 核心概念
ESP-MESH是基于ESP-NOW的自组织、自修复网络协议,构建多层树状拓扑
网络拓扑结构
ESP-MESH 树型拓扑
节点角色
根节点 连接到路由器的Bridge节点、中间节点 既连接父节点又连接子节点、叶子节点只连接父节点,无子节点
路由算法
painlessMesh使用基于洪泛的路由协议:
ESP-MESH 路由表 每个节点维护邻居列表、广播消息时,邻居节点继续转发
网络形成过程
具有最强的路由器 RSSI 值作为信标帧将在整个网络中传播,经过多次传输和扫描迭代成为根节点

节点扫描周围网络、选择最佳父节点建立连接、同步网络时间和配置、开始正常数据传输
5.2 Mesh网络通信流程

心跳检测机制
节点每隔
HEARTBEAT_INTERVAL=5000ms广播状态包,Bridge 节点维护 "在线节点列表",超过DEVICE_TIMEOUT=30000ms未收到心跳则标记离线
两种组网模式对比
| 维度 | Bridge 模式 | 纯 Mesh 模式 |
|---|---|---|
| 网络架构 | 路由器 + Mesh 网络 | 仅 Mesh 网络 |
| 访问方式 | 路由器 IP(局域网任意设备) | 连接 Mesh AP 后访问指定 IP |
| 覆盖范围 | 路由器 + Mesh 多跳,范围广 | 仅 Mesh 多跳,范围有限 |
| 实用性 | 高(适配家庭场景) | 低(仅无路由器场景可用) |
| 稳定性 | 高(路由器提供稳定外网) | 中(依赖 Mesh 节点稳定性) |
六、常见问题解答(FAQ)
Q1:智能灯做 Bridge 节点,必须先连路由器再 Mesh?
*A:必须优先连路由器再启动 Mesh:*先连 Mesh AP 后,路由器 STA 连接会失败,Bridge 节点无法获取局域网 IP,Web 界面无法访问
Q2: 设备无法连接到Mesh网络怎么办?
*A:请检查:*所有设备的MESH_PREFIX和MESH_PASSWORD一致、重启所有设备,重新尝试组网、检查是否有Wi-Fi信号干扰
Q3: 网页控制界面无法访问?
A:检查设备是否在同一网络段:确保Bridge设备已成功连接路由器,查看串口输出的IP地址、尝试重启Web服务,先断开MESH 节点避免STA模式导致Mesh 拓扑重构
项目资源整合
ESP-MESH组网文档: ESP-MESH
Mesh网络核心库: gmag11/painlessMesh
JSON处理库: bblanchon/ArduinoJson
异步TCP库: ESP32Async/ESPAsyncTCP
异步Web服务器: lacamera/ESPAsyncWebServer
ESP-MESH 网络架构示意图