零知IDE——基于ESP-NOW协议的ESP32 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 ESP-NOW初始化](#3.1 ESP-NOW初始化)

[3.2 设备发现与列表更新](#3.2 设备发现与列表更新)

[3.3 人体感应算法](#3.3 人体感应算法)

[3.4 Web 服务器核心](#3.4 Web 服务器核心)

[3.5 主函数接口](#3.5 主函数接口)

四、项目结果演示

[4.1 操作流程](#4.1 操作流程)

[4.2 视频演示](#4.2 视频演示)

五、ESP32组网技术讲解

[5.1 ESP-NOW协议解析](#5.1 ESP-NOW协议解析)

[5.2 ESP-MESH组网技术](#5.2 ESP-MESH组网技术)

六、常见问题解答(FAQ)

Q1:设备无法相互通信?

Q2:状态数据未返回到网页?


项目概述

本项目基于零知ESP32 主控板,实现了一个去中心化的智能家居MESH 组网系统。系统采用ESP-NOW协议作为通信基础,无需路由器即可建立设备间直接通信,支持智能灯控、人体感应、温湿度监测等多种设备类型。每个节点既是终端设备又是中继节点,形成自组织、自修复的Mesh网络拓扑结构

项目难点及解决方案

问题描述:ESP32在STA模式连接WiFi时,ESP-NOW通信不稳定

**解决方案:**采用WiFi信道固定策略,所有设备工作在相同信道(信道1),避免信道切换导致的通信中断

一、系统接线部分

1.1 硬件清单

组件 型号 数量 备注
主控板 零知ESP32 3 核心处理器
人体感应模块 HC-SR501 1 运动检测
温湿度传感器 DHT11 1 环境监测
LED 任意 1 灯光控制
杜邦线 公对公 若干 连接线
USB数据线 Type-A 3 供电/串口调试

1.2 接线方案表

根据代码中引脚定义配置:

功能模块 ESP32 GPIO 外设引脚 说明
灯光控制 GPIO27 LED+ 输出高电平控制
人体感应 GPIO5 HC-SR501 OUT 高电平触发模式
温湿度传感器 GPIO18 DHT11 OUT 单总线通信
电源 3.3V/5V 外设VCC 根据外设需求
地线 GND 外设GND 共地连接

1.3 具体接线图

HC-SR501人体感应模块接+5V供电,确保工作条件稳定

1.4 接线实物图

二、安装与使用部分

2.1 开源平台-输入"ESP32 MESH组网"并搜索-代码下载自动打开

2.2 连接-验证-上传

2.3 调试-串口监视器

三、核心代码解析

代码核心逻辑聚焦MESH 初始化、ESP-NOW 配置、数据发送回调、心跳包检测四大函数

3.1 ESP-NOW初始化

cpp 复制代码
// esp_now_network.cpp - initESPNow()
void initESPNow() {
    // 设置WiFi模式为STA,但不断开连接
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    
    // 关键:固定WiFi信道,确保所有设备在同一信道
    esp_wifi_set_channel(WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);
    
    // 初始化ESP-NOW协议栈
    if (esp_now_init() != ESP_OK) {
        Serial.println("[错误] ESP-NOW初始化失败");
        return;
    }
    
    // 注册数据接收回调函数
    esp_now_register_recv_cb(onDataReceived);
    
    // 添加广播地址对等体
    esp_now_peer_info_t peerInfo = {};
    memcpy(peerInfo.peer_addr, broadcastAddress, 6);
    peerInfo.channel = WIFI_CHANNEL;
    peerInfo.encrypt = false;
    
    if (esp_now_add_peer(&peerInfo) != ESP_OK) {
        Serial.println("[错误] 添加广播地址失败");
    }
}

WIFI_CHANNEL固定为信道1,确保所有设备信道一致;broadcastAddress广播地址{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}

3.2 设备发现与列表更新

cpp 复制代码
void updateDeviceList(const uint8_t *mac, Message *msg) {
    int index = findDevice(msg->deviceId);
    
    if (index == -1) {
        // 新设备
        if (deviceCount < MAX_DEVICES) {
            index = deviceCount++;
            memcpy(deviceList[index].mac, mac, 6);
            deviceList[index].deviceId = msg->deviceId;
            deviceList[index].deviceType = msg->deviceType;
            strncpy(deviceList[index].name, msg->deviceName, 31);
            deviceList[index].name[31] = '\0';
            deviceList[index].online = true;
            deviceList[index].lastSeen = millis();

            switch(msg->deviceType) {
                case TYPE_LIGHT:
                    deviceList[index].state.light.lightState = false;
                    break;
                case TYPE_SENSOR:
                    deviceList[index].state.sensor.temperature = 0.0f;
                    deviceList[index].state.sensor.humidity = 0.0f;
                    break;
                case TYPE_MOTION:
                    deviceList[index].state.motion.motionDetected = false;
                    deviceList[index].state.motion.lastMotionTime = 0;
                    break;
            }
            
            const char* typeStr;
            switch(msg->deviceType) {
                case TYPE_LIGHT: typeStr = "智能灯"; break;
                case TYPE_MOTION: typeStr = "人体感应器"; break;
                case TYPE_SENSOR: typeStr = "传感器"; break;
                default: typeStr = "未知";
            }
            
            Serial.printf("[新设备] %s (ID:%08X, MAC:%s, 类型:%s)\n", 
                          deviceList[index].name, 
                          deviceList[index].deviceId,
                          macToString(mac).c_str(),
                          typeStr);
            
            // 添加到对等体列表(避免重复添加)
            if (!esp_now_is_peer_exist(mac)) {
                esp_now_peer_info_t peerInfo = {};
                memcpy(peerInfo.peer_addr, mac, 6);
                peerInfo.channel = WIFI_CHANNEL;
                peerInfo.encrypt = false;
                
                esp_err_t result = esp_now_add_peer(&peerInfo);
                if (result != ESP_OK) {
                    Serial.printf("[警告] 添加对等体失败: %d\n", result);
                }
            }
        } else {
            Serial.println("[警告] 设备列表已满,无法添加新设备");
            return;
        }
    }
    
    // 更新设备信息
    deviceList[index].online = true;
    deviceList[index].lastSeen = millis();
}

通过心跳包或发现请求识别新设备、维护统一的设备状态表、自动管理ESP-NOW通信对等体

3.3 人体感应算法

cpp 复制代码
void checkMotionSensorOptimized() {
  static unsigned long lastMotionTime = 0;
  static unsigned long lastReportTime = 0;
  static bool stableMotionState = false;
  
  // HC-SR501检测到人体时输出高电平
  bool currentReading = digitalRead(MOTION_PIN);  // HIGH = 检测到人体
  unsigned long now = millis();
  
  // 更新LED指示(高电平时点亮)
  digitalWrite(LED_PIN, currentReading ? HIGH : LOW);
  
  // 简单的防抖处理:状态改变后等待MOTION_DEBOUNCE_MS确认
  if (currentReading != lastMotionState) {
    lastMotionState = currentReading;
    lastMotionTime = now;
    
  }
  
  // 防抖确认:状态稳定超过MOTION_DEBOUNCE_MS后才认为有效
  if ((now - lastMotionTime) >= MOTION_DEBOUNCE_MS) {
    if (currentReading != stableMotionState) {
      stableMotionState = currentReading;
      
      if (stableMotionState) {
        Serial.println("[运动] 检测到人体运动!");
        
        motionDetected = true;
        
        // 限制广播频率:至少间隔2秒
        if ((now - lastReportTime) >= 2000) {
          Message msg = {0};
          msg.msgType = MSG_STATUS;
          snprintf(msg.payload, sizeof(msg.payload), 
                   "{\"motion\":true,\"time\":%lu}", now);
          sendBroadcast(&msg);
          lastReportTime = now;
        }
        
      } else {
        // *** 运动停止 ***
        Serial.println("[运动] ✗ 运动停止,恢复静止状态");
        motionDetected = false;
        
        // 广播运动停止
        Message msg = {0};
        msg.msgType = MSG_STATUS;
        strcpy(msg.payload, "{\"motion\":false}");
        sendBroadcast(&msg);
        lastReportTime = now;
      }
    }
  }
}

2秒最小间隔,减少网络拥堵;记录稳定状态,避免频繁切换;板载LED实时反映传感器状态

3.4 Web 服务器核心

cpp 复制代码
void WebController::handleControlBody(AsyncWebServerRequest *request, uint8_t *data, size_t len) {
    if (len > 256) {
        request->send(400, "application/json", "{\"error\":\"请求体过大\"}");
        return;
    }
    
    String body = "";
    for (size_t i = 0; i < len; i++) {
        body += (char)data[i];
    }
    
    Serial.printf("[网页控制] 收到请求: %s\n", body.c_str());
    
    DynamicJsonDocument doc(256);
    DeserializationError error = deserializeJson(doc, body);
    
    if (error) {
        Serial.printf("[错误] JSON解析失败: %s\n", error.c_str());
        request->send(400, "application/json", "{\"error\":\"JSON解析失败\"}");
        return;
    }
    
    String deviceIdStr = doc["deviceId"].as<String>();
    String action = doc["action"].as<String>();
    
    if (deviceIdStr.length() == 0 || action.length() == 0) {
        request->send(400, "application/json", "{\"error\":\"缺少参数\"}");
        return;
    }
    
    uint32_t deviceId = strtoul(deviceIdStr.c_str(), NULL, 16);
    
    // *** 关键修改:判断是否控制本机 ***
    if (myDevice && deviceId == myDevice->deviceId) {
        Serial.printf("[网页控制] 本地控制: %s\n", action.c_str());
        executeLocalCommand(action.c_str());
        request->send(200, "application/json", "{\"success\":true,\"message\":\"本地命令执行成功\"}");
    } else {
        Serial.printf("[网页控制] 远程控制设备 0x%08X: %s\n", deviceId, action.c_str());
        sendCommand(deviceId, action.c_str(), "");
        request->send(200, "application/json", "{\"success\":true,\"message\":\"远程命令发送成功\"}");
    }
}

void WebController::handleAPIBody(AsyncWebServerRequest *request, uint8_t *data, size_t len) {
    if (len > 128) {
        request->send(400, "application/json", "{\"error\":\"请求体过大\"}");
        return;
    }
    
    // 将数据转换为字符串
    String body = "";
    for (size_t i = 0; i < len; i++) {
        body += (char)data[i];
    }
    
    Serial.printf("[API] 收到请求: %s\n", body.c_str());
    
    DynamicJsonDocument doc(128);
    DeserializationError error = deserializeJson(doc, body);
    
    if (error) {
        Serial.printf("[错误] JSON解析失败: %s\n", error.c_str());
        request->send(400, "application/json", "{\"error\":\"JSON解析失败\"}");
        return;
    }
    
    String action = doc["action"].as<String>();
    
    Serial.printf("[API] 执行操作: %s\n", action.c_str());
    
    if (action == "discover") {
        sendDiscovery();
        request->send(200, "application/json", "{\"success\":true,\"message\":\"设备发现已触发\"}");
    } else if (action == "heartbeat") {
        sendHeartbeat();
        request->send(200, "application/json", "{\"success\":true,\"message\":\"心跳已发送\"}");
    } else {
        request->send(400, "application/json", "{\"error\":\"未知命令\"}");
    }
}

处理 设备控制类请求支持 "本地设备控制" 和 "远程设备控制"、通过 JSON 请求体指定目标设备 ID 和控制指令

3.5 主函数接口

cpp 复制代码
/**************************************************************************************
 * 文件: ESP32_SmartHome_Mesh_Node.ino
 * 作者:零知实验室(深圳市在芯间科技有限公司)
 * -^^- 零知实验室,让电子制作变得更简单!-^^-
 * 时间: 2026-1-19
 * 功能:ESP32智能家居Mesh网络节点,支持ESP-NOW协议和网页控制
 *       自动识别设备类型(灯控/人体感应/温湿度传感器)
 *       无需路由器直接组网,支持设备发现和远程控制
 ***************************************************************************************/

#include "WebController.h"
#include "esp_now_network.h"

// ==================== 设备类型配置 ====================
// 在此处指定当前设备的物理类型
// #define MY_DEVICE_TYPE TYPE_LIGHT    // 智能灯
// #define MY_DEVICE_TYPE TYPE_SENSOR    // 温湿度传感器
#define MY_DEVICE_TYPE TYPE_MOTION   // 人体感应器

// ==================== 引脚配置 ====================
// 统一管理引脚,不再嵌套宏。如果对应硬件不存在,这些引脚定义将不被使用。
int CONTROL_PIN = 27;
int MOTION_PIN = 5;
int LED_PIN = 2;
int DHT_PIN = 18;

// ==================== 库引用与全局变量 ====================
#include "DHT.h"
DHT dht(DHT_PIN, DHT11);

// 全局变量全定义,增加代码通用性
bool ledState = false;
bool motionDetected = false;
bool lastMotionState = false;
unsigned long motionLastTime = 0;
const unsigned long MOTION_DEBOUNCE_MS = 3000;  // 1秒去抖时间
unsigned long lastActionTime = 0;
float temperature = 0;
float humidity = 0;

// WiFi配置
const char* WIFI_SSID = "zaixinjian";
const char* WIFI_PASSWORD = "2020zaixinjian";

// ==================== 灯光控制函数 ====================
void handleLightControl(const char* action) {
    if (strcmp(action, "on") == 0) {
        Serial.println("[执行] 开灯");
        digitalWrite(CONTROL_PIN, HIGH);
        ledState = true;
        
        // 发送状态回复
        Message reply = {0};
        reply.msgType = MSG_STATUS;
        strcpy(reply.payload, "{\"state\":\"on\"}");
        sendBroadcast(&reply);
        
    } else if (strcmp(action, "off") == 0) {
        Serial.println("[执行] 关灯");
        digitalWrite(CONTROL_PIN, LOW);
        ledState = false;
        
        // 发送状态回复
        Message reply = {0};
        reply.msgType = MSG_STATUS;
        strcpy(reply.payload, "{\"state\":\"off\"}");
        sendBroadcast(&reply);
        
    } else if (strcmp(action, "toggle") == 0) {
        Serial.println("[执行] 切换灯状态");
        ledState = !ledState;
        digitalWrite(CONTROL_PIN, ledState ? HIGH : LOW);
        Serial.printf("[状态] LED已%s\n", ledState ? "打开" : "关闭");
        
        // 发送状态回复
        Message reply = {0};
        reply.msgType = MSG_STATUS;
        snprintf(reply.payload, sizeof(reply.payload), 
                "{\"state\":\"%s\"}", ledState ? "on" : "off");
        sendBroadcast(&reply);
    }
}

// ==================== 本地控制接口(供Web调用)====================
void executeLocalCommand(const char* action) {
    Serial.printf("[本地控制] 执行命令: %s\n", action);
    
    #if MY_DEVICE_TYPE == TYPE_LIGHT
        handleLightControl(action);
    #elif MY_DEVICE_TYPE == TYPE_SENSOR
        if (strcmp(action, "read") == 0) {
            Serial.println("[本地控制] 立即读取传感器");
            readSensorData();
        }
    #elif MY_DEVICE_TYPE == TYPE_MOTION
        if (strcmp(action, "status") == 0) {
            Serial.printf("[本地控制] 当前运动状态: %s\n", 
                         motionDetected ? "检测到运动" : "无运动");
        }
    #endif
}

// ==================== 获取本地设备状态JSON ====================
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, 2);
        state += ",\"humidity\":";
        state += String(humidity, 2);
        state += ",\"hasSensor\":true";
    #elif MY_DEVICE_TYPE == TYPE_MOTION
        state += "\"motion\":";
        state += motionDetected ? "true" : "false";
        state += ",\"hasMotion\":true";
    #endif
    
    state += "}";
    return state;
}

// ==================== 初始化硬件 ====================
void initHardware() {
  Serial.println("\n[硬件] 初始化硬件...");
  
  pinMode(CONTROL_PIN, OUTPUT);
  digitalWrite(CONTROL_PIN, LOW);
  ledState = false;
  Serial.println("[硬件] 智能灯已初始化,控制引脚: GPIO" + String(CONTROL_PIN));
    
  pinMode(MOTION_PIN, INPUT);  // 不使用上拉电阻 
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  motionDetected = false;
  lastMotionState = false;
  Serial.println("[硬件] 人体感应器已初始化");
  Serial.println("[硬件] HC-SR501模式: 低电平触发,输入引脚: GPIO" + String(MOTION_PIN));
    
  dht.begin();
  Serial.println("[硬件] DHT11传感器已初始化,数据引脚: GPIO" + String(DHT_PIN));
  delay(2000);  // 等待传感器稳定
}

// ==================== 人体检测优化函数 ====================
void checkMotionSensorOptimized() {
  static unsigned long lastMotionTime = 0;
  static unsigned long lastReportTime = 0;
  static bool stableMotionState = false;
  
  // HC-SR501检测到人体时输出高电平
  bool currentReading = digitalRead(MOTION_PIN);  // HIGH = 检测到人体
  unsigned long now = millis();
  
  // 更新LED指示(高电平时点亮)
  digitalWrite(LED_PIN, currentReading ? HIGH : LOW);
  
  // 简单的防抖处理:状态改变后等待MOTION_DEBOUNCE_MS确认
  if (currentReading != lastMotionState) {
    lastMotionState = currentReading;
    lastMotionTime = now;
    
  }
  
  // 防抖确认:状态稳定超过MOTION_DEBOUNCE_MS后才认为有效
  if ((now - lastMotionTime) >= MOTION_DEBOUNCE_MS) {
    if (currentReading != stableMotionState) {
      stableMotionState = currentReading;
      
      if (stableMotionState) {
        Serial.println("[运动] 检测到人体运动!");
        
        motionDetected = true;
        
        // 限制广播频率:至少间隔2秒
        if ((now - lastReportTime) >= 2000) {
          Message msg = {0};
          msg.msgType = MSG_STATUS;
          snprintf(msg.payload, sizeof(msg.payload), 
                   "{\"motion\":true,\"time\":%lu}", now);
          sendBroadcast(&msg);
          lastReportTime = now;
        }
        
      } else {
        // *** 运动停止 ***
        Serial.println("[运动] ✗ 运动停止,恢复静止状态");
        motionDetected = false;
        
        // 广播运动停止
        Message msg = {0};
        msg.msgType = MSG_STATUS;
        strcpy(msg.payload, "{\"motion\":false}");
        sendBroadcast(&msg);
        lastReportTime = now;
      }
    }
  }
}

// ==================== SETUP ====================
void setup() {
  Serial.begin(115200);
  delay(1000);
  
  Serial.println("\n==========================================");
  Serial.println("   ESP32智能家居Mesh网络节点");
  Serial.println("==========================================\n");
  
  // 初始化硬件
  initHardware();

   // 添加:初始化传感器数据
  #if MY_DEVICE_TYPE == TYPE_SENSOR
    delay(2000); // 等待DHT11稳定
    float t = dht.readTemperature();
    float h = dht.readHumidity();
    if (!isnan(t) && !isnan(h)) {
      temperature = t;
      humidity = h;
      Serial.printf("[传感器] 初始值 - 温度: %.2f°C, 湿度: %.2f%%\n", temperature, humidity);
    }
  #endif
  
  // 初始化ESP-NOW网络
  initESPNow();
  
  // 初始化本设备信息
  initMyDevice(MY_DEVICE_TYPE);
  
  // 初始化网页控制器(STA模式)
  Serial.println("[网页] 初始化网页控制器...");
  webController.begin(WIFI_SSID, WIFI_PASSWORD);
  webController.setDeviceData(&myDevice, deviceList, &deviceCount);
  
  // 发送设备发现广播
  sendDiscovery();
  
  Serial.println("\n系统初始化完成!");
  Serial.println("网页控制地址: " + WiFi.localIP().toString());
}

// ==================== LOOP ====================
void loop() {
  unsigned long now = millis();
  
  // 定时发送心跳
  static unsigned long lastHeartbeat = 0;
  if (now - lastHeartbeat >= HEARTBEAT_INTERVAL) {
    lastHeartbeat = now;
    sendHeartbeat();
  }
  
  // 设备特定操作
  if (MY_DEVICE_TYPE == TYPE_SENSOR) {
    static unsigned long lastSensorRead = 0;
    if (now - lastSensorRead >= SENSOR_READ_INTERVAL) {
      lastSensorRead = now;
      readSensorData();
    }
  }else if(MY_DEVICE_TYPE == TYPE_MOTION) {
    checkMotionSensorOptimized();
  }
  
  // 检查设备超时
  checkDeviceTimeout();
  
  // 处理串口命令(测试用)
  if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');
    cmd.trim();
    
    if (cmd == "list") {
      printDeviceList();
    } else if (cmd == "discover") {
      sendDiscovery();
    } else if (cmd == "help") {
      Serial.println("\n可用命令:");
      Serial.println("  list - 显示设备列表");
      Serial.println("  discover - 重新发现设备");
      Serial.println("  web - 显示网页控制信息");
      Serial.println("  test <deviceId> <action> - 发送测试命令");
      Serial.println("  on/off/toggle - 本地灯光控制");
    } else if (cmd == "web") {
      Serial.println("网页控制地址: " + WiFi.localIP().toString());
    } else if ((cmd == "on" || cmd == "off" || cmd == "toggle") && MY_DEVICE_TYPE == TYPE_LIGHT) {
      handleLightControl(cmd.c_str());
    } else if (cmd == "state") {
      #if MY_DEVICE_TYPE == TYPE_LIGHT
        Serial.printf("LED状态: %s\n", ledState ? "ON" : "OFF");
      #elif MY_DEVICE_TYPE == TYPE_MOTION
        Serial.printf("运动状态: %s\n", motionDetected ? "检测到运动" : "无运动");
      #elif MY_DEVICE_TYPE == TYPE_SENSOR
        Serial.printf("温度: %.2f°C, 湿度: %.2f%%\n", temperature, humidity);
      #endif
    }else if (cmd.startsWith("test ")) {
      // 解析命令: test <deviceId> <action>
      int firstSpace = cmd.indexOf(' ');
      int secondSpace = cmd.indexOf(' ', firstSpace + 1);
      
      if (secondSpace > 0) {
        String deviceIdStr = cmd.substring(firstSpace + 1, secondSpace);
        String action = cmd.substring(secondSpace + 1);
        
        uint32_t targetId = strtoul(deviceIdStr.c_str(), NULL, 16);
        Serial.printf("发送命令到设备 %08X: %s\n", targetId, action.c_str());
        sendCommand(targetId, action.c_str(), "");
      } else {
        Serial.println("命令格式错误,应为: test <deviceId> <action>");
        Serial.println("示例: test 12345678 on");
      }
    }
  }
  delay(10);
}

// ==================== 传感器数据读取 ====================
void readSensorData() {
  float newTemp = dht.readTemperature();
  float newHum = dht.readHumidity();
  
  if (!isnan(newTemp) && !isnan(newHum)) {
    temperature = newTemp;
    humidity = newHum;
    
    Serial.printf("[传感器] 温度: %.2f°C, 湿度: %.2f%%\n", temperature, humidity);
    
    // 广播传感器数据
    Message msg = {0};
    msg.msgType = MSG_SENSOR_DATA;
    
    char payload[100];
    snprintf(payload, sizeof(payload), 
             "{\"temperature\":%.2f,\"humidity\":%.2f,\"unit\":\"C\"}", 
             temperature, humidity);
    strcpy(msg.payload, payload);
    
    sendBroadcast(&msg);
  } else {
    Serial.println("[传感器] 读取失败,检查连接");
  }
}

/******************************************************************************
 * 深圳市在芯间科技有限公司
 * 淘宝店铺:在芯间科技零知板
 * 店铺网址:https://shop533070398.taobao.com
 * 版权说明:
 *  1.本代码的版权归【深圳市在芯间科技有限公司】所有,仅限个人非商业性学习使用。
 *  2.严禁将本代码或其衍生版本用于任何商业用途(包括但不限于产品开发、付费服务、企业内部使用等)。
 *  3.任何商业用途均需事先获得【深圳市在芯间科技有限公司】的书面授权,未经授权的商业使用行为将被视为侵权。
******************************************************************************/

系统流程图

四、项目结果演示

不同ESP32组网按照接线表连接各模块,修改主函数MY_DEVICE_TYPE定义设备类型烧录到对应功能的ESP32模组

4.1 操作流程

①网络建立

主设备上电,创建WiFi热点、其他设备上电时,自动加入Mesh网络、所有设备指示灯正常闪烁

②网页访问

ESP32连接局域网WIFI,浏览器打印串口调试器的IP地址、查看设备列表,进行控制操作

③功能测试

点击网页按钮控制灯光开关、在传感器前移动测试人体感应、查看温湿度数据实时更新

4.2 视频演示

零知ESP32 Mesh智能家居组网实战演示

展示基于ESP-NOW协议的ESP32 Mesh智能家居系统的完整工作流程,硬件连接与设备上电,Mesh网络自动建立与网页控制界面操作演示,多设备联动效果展示,网络稳定性与响应速度测试

五、ESP32组网技术讲解

ESP-MESH 允许分布在大范围区域内的大量设备节点 在同一个 WLAN(无线局域网)中相互连接,MESH 网络可以自主地构建和维护
ESP-MESH 网络架构示意图

网络中的节点不需要连接到中心节点,可以与相邻节点连接;无需受限于距离中心节点的位置,所有节点仍可互连

5.1 ESP-NOW协议解析

ESP-NOW 基于数据链路层的无线通信协议,可与 Wi-Fi 和 Bluetooth LE 共存,既能实现稳定的设备连接和控制

技术特性

无连接通信,无需握手过程直接发送数据;低延迟3-10ms典型,适合实时控制;支持广播和单播模式

发送端流程

接收端流程

5.2 ESP-MESH组网技术

ESP-MESH是基于ESP-NOW的自组织、自修复网络协议,构建多层树状拓扑
ESP-MESH 树型拓扑

设备发现

通过广播实现自动邻居发现,广播功能允许将单个 ESP-MESH 数据包同时发送给网络中的所有节点

每个节点可以将一个广播包转发至其所有上行和下行连接,使得数据包尽可能快地在整个网络中传播

关键技术对比

特性 ESP-NOW 传统WiFi 蓝牙Mesh
连接方式 无连接 需要连接 需要配对
功耗 极低
传输距离 中等
最大节点数 20+ 有限 32+
数据传输 点对点 通过AP 广播/中继
适用场景 传感器网络 互联网接入 个人区域网

六、常见问题解答(FAQ)

Q1:设备无法相互通信?

*A:可能原因:*确保所有设备WIFI_CHANNEL设置相同、检查广播地址是否正确、ESP-NOW必须在WiFi初始化后调用

Q2:状态数据未返回到网页?

*A:可能原因:*IP地址错误,连接到稳定的WIFI网络、减少MAX_DEVICES或DynamicJsonDocument大小避免内存不足

项目资源整合

ESP-MESH组网文档: ESP-MESH

异步Web服务器: ESP32Async ESPAsyncWebServer

ESP32Async AsyncTCP

JSON数据处理: bblanchon ArduinoJson

DHT11传感器驱动: adafruit DHT-sensor-library

相关推荐
康康的AI博客11 小时前
腾讯王炸:CodeMoment - 全球首个产设研一体 AI IDE
ide·人工智能
码道功成11 小时前
Pycham及IntelliJ Idea常用插件
java·ide·intellij-idea
文 丰21 小时前
【Android Studio】gradle下载慢解决方案(替换配置-非手工下载安装包)
android·ide·android studio
Jackson@ML1 天前
2026最新版Visual Studio Code安装使用指南
ide·vscode·编辑器
New_Horizons6661 天前
VScode 无法使用shift + F12查看引用
ide·vscode·编辑器
tc&1 天前
VSCode远程连接AlmaLinux虚拟机问题总结
ide·vscode·编辑器
web守墓人1 天前
【编辑器】简单了解下vscode的go语言插件原理
ide·vscode·编辑器
颖风船2 天前
vscode连接vmware中的deepin虚拟机,deepin端配置
linux·ide·vscode
小胖红3 天前
Xcode 打包失败 处理
ide·macos·xcode