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

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

www.lingzhilab.com

目录

一、系统接线部分

1.1 硬件清单

1.2 接线方案表

1.3 接线图

1.4 连接实物图

二、安装与使用部分

三、代码讲解部分

3.1 Mesh网络核心管理

3.2 网页控制界面

3.3 串口命令解析器

3.4 设备通信接收回调

3.5 主程序框架

四、项目结果演示

4.1 操作流程

4.2 视频演示

五、ESP MESH组网技术讲解

5.1 ESP-MESH 核心概念

5.2 Mesh网络通信流程

六、常见问题解答(FAQ)

Q1:智能灯做 Bridge 节点,必须先连路由器再 Mesh?

Q2: 设备无法连接到Mesh网络怎么办?

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网络核心管理

// ==================== 设备索引查找 ====================
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 网页控制界面

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 串口命令解析器

// 解析命令并执行
  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 设备通信接收回调

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 主程序框架

/**************************************************************************************
 * 文件: 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库解析

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

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐