零知IDE—— ESP8266(ESP-12F)MESH 组网实现多设备智能家居控制系统(灯光 / 传感器 / 人体感应)

✔零知开源(零知IDE)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 "配置环境" 转移到 "创意实现",极大降低了技术门槛。零知IDE编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知开源(零知IDE)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!

✔访问零知实验室,获取更多实战项目和教程资源吧!

www.lingzhilab.com

目录

一、系统接线部分

[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网络通信流程)

六、常见问题解答(FAQ)

[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

相关推荐
小y要自律2 小时前
11 string容器 - 子串获取
c++·算法·stl
DevangLic2 小时前
【确认是否安装了 C++ 工具】
android·java·c++
承渊政道2 小时前
C++学习之旅【C++拓展学习之反向迭代器实现、计算器实现以及逆波兰表达式】
c语言·开发语言·c++·学习·visual studio
科技AI训练师2 小时前
CAXA CAD兼容性实测,老图纸与多格式文件的适配方案
人工智能·智能家居·vr
王老师青少年编程3 小时前
信奥赛C++提高组csp-s知识详解及案例实践(汇总版)
c++·知识·csp·高频考点·信奥赛·csp-s·提高组
2301_788662403 小时前
C++中的代理模式高级应用
开发语言·c++·算法
啊阿狸不会拉杆3 小时前
《计算机操作系统》第十二章 - 保护和安全
开发语言·网络·c++·算法·安全·计算机组成原理·计算机操作系统
月挽清风3 小时前
代码随想录第十一天
c++·算法·leetcode
郝学胜-神的一滴3 小时前
Linux网络字节序详解:从理论到实践
linux·服务器·c语言·开发语言·c++·网络协议·程序人生