【ESP-NOW Web 服务器传感器仪表板 (ESP-NOW + Wi-Fi)】

news2024/11/20 6:27:30

【ESP-NOW Web 服务器传感器仪表板 (ESP-NOW + Wi-Fi)】

  • 1. 前言
  • 2. 同时使用 ESP-NOW 和 Wi-Fi
  • 3. 项目概况
  • 4. 先决条件
    • 4.1 环境配置
    • 4.2 DHT 库
    • 4.3 ESPAsyncWebSrv服务器库
    • 4.4 Arduino_JSON
    • 4.5 所需零件
    • 4.6 获取接收板 MAC 地址
    • 4.7 ESP32 发送电路
  • 5. ESP32 接收器 (ESP-NOW + Web Server)
    • 5.1 代码的工作原理
    • 5.2 数据结构
    • 5.3 创建事件源
    • 5.4 OnDataRecv() 函数
    • 5.5 构建网页
    • 5.6 处理事件
    • 5.7 setup()
    • 5.8 处理请求
    • 5.9 服务器事件源
    • 5.10 loop()
  • 6. ESP32 发送码 (ESP-NOW)
    • 6.1 代码的工作原理
    • 6.2 设置板 ID
    • 6.3 DHT传感器
    • 6.4 接收方的MAC地址
    • 6.5 数据结构
    • 6.6 定时器间隔
    • 6.7 更改 Wi-Fi 信道
    • 6.8 读数温度
    • 6.9 读数湿度
    • 6.10 OnDataSent 回调函数
    • 6.11 setup()
    • 6.12 WiFi.mode(WIFI_STA);
    • 6.13 添加对等节点
    • 6.14 loop()
    • 6.14 发送 ESP-NOW 消息
  • 7. 示范
  • 8. 总结

1. 前言

在本项目中,您将学习如何托管 ESP32 Web 服务器并同时使用 ESP-NOW 通信协议。您可以让多个 ESP32 开发板通过 ESP-NOW 将传感器读数发送到一个 ESP32 接收器,该接收器在 Web 服务器上显示所有读数。这些板将使用Arduino IDE进行编程。
在这里插入图片描述

ESP32:使用 Arduino IDE 的 ESP-NOW Web 服务器传感器仪表板(同时使用 ESP-NOW 和 Wi-Fi)
我们还有其他与 ESP-NOW 相关的指南,您可能对此感兴趣:

  • 【ESP-NOW ESP32 开发板之间的双向通信】
  • 【ESP-NOW 入门(ESP32 with Arduino IDE)】
  • 【ESP-NOW with ESP32:向多个开发板发送数据(一对多)】
  • 【ESP-NOW with ESP32:从多个开发板接收数据(多对一)】

观看视频教程

注意:我们更新了本教程,增加了同时使用 ESP-NOW 和 Wi-Fi 的更好方法。视频不使用此当前方法。您仍然可以观看视频以了解一切是如何工作的,但我们建议您查看书面文章。

2. 同时使用 ESP-NOW 和 Wi-Fi

同时使用 ESP-NOW 和 Wi-Fi:ESP-NOW Receiver Web Server 和 ESP-NOW 发送板

如果您想使用 Wi-Fi 托管 Web 服务器并同时使用 ESP-NOW 接收来自其他开发板的传感器读数,您需要考虑以下几点:

  • ESP32 发送板必须使用与接收板相同的 Wi-Fi 通道。
  • 接收器板的 Wi-Fi 通道由您的 Wi-Fi 路由器自动分配。
  • 接收板的Wi-Fi 模式必须是接入点和站点 (WIFI_AP_STA).
  • 您可以手动设置相同的 Wi-Fi 信道,也可以在发送器上添加一个简单的代码,以将其 Wi-Fi 信道设置为与接收板相同的信道。

3. 项目概况

下图显示了我们将要生成的项目的高级概述。

在这里插入图片描述

  • ESP-NOW Receiver Web Server 和 ESP32 开发板使用 ESP-NOW 发送温湿度读数 有两个 ESP32发送板,通过 ESP-NOW 将 DHT22 温湿度读数发送到一个 ESP32 接收器板(ESP-NOW 多对一配置);
  • ESP32接收板接收数据包并在 Web 服务器上显示读数;
  • 每次使用服务器发送事件 (SSE) 接收新读数时,Web 服务器都会自动更新。

4. 先决条件

在继续此项目之前,请确保检查以下先决条件。

我们将使用 Arduino IDE 对 ESP32/ESP8266 开发板进行编程,因此在继续本教程之前,请确保已在 Arduino IDE 中安装这些开发板。

4.1 环境配置

  1. Arduino IDE:下载并安装 Arduino IDE;
  2. ESP32 开发板库:在 Arduino IDE 中添加 ESP32 支持;
    参考博客:【esp32c3配置arduino IDE教程】
    为安装过程留出一些时间,具体时间可能因您的互联网连接而异。

4.2 DHT 库

ESP32 发送板将发送来自 DHT22 传感器的温度和湿度读数。

要从 DHT 传感器读取数据,我们将使用 Adafruit 的 DHT 库。要使用此库,您还需要安装 Adafruit Unified Sensor 库。按照以下步骤安装这些库。

  1. 打开Arduino IDE,然后转到“Sketch”>“包含库”>“管理库”。库管理器应打开。

  2. 在搜索框中搜索“DHT”,然后从 Adafruit 安装 DHT 库。

在这里插入图片描述
点击全部安装会自动“Adafruit Unified Sensor”。

要了解有关 DHT11 或 DHT22 温度传感器的更多信息,请阅读我们的指南:使用 Arduino IDE 的 ESP32 和DHT11/DHT22 温湿度传感器。

4.3 ESPAsyncWebSrv服务器库

要构建 Web 服务器,您需要安装以下库:

  • ESPAsyncWebServer
  • AsyncTCP

您需要安装 ESPAsyncWebSrv库。您可以在 Arduino IDE 库管理器中安装此库。只需转到“草图”>“包含库”>“管理库”,然后搜索库名称,如下所示:
在这里插入图片描述

4.4 Arduino_JSON

您需要安装 Arduino_JSON库。您可以在 Arduino IDE 库管理器中安装此库。只需转到“草图”>“包含库”>“管理库”,然后搜索库名称,如下所示:
在这里插入图片描述

4.5 所需零件

要学习本教程,您需要多个 ESP32 开发板。我们将使用三个 ESP32 开发板。您还需要:

  • 3 x ESP32(阅读最佳 ESP32开发板)
  • 2 x DHT22 温湿度传感器 – ESP32 的 DHT指南
  • 6 x 母对母杜邦线

您可以使用上述链接或直接去淘宝找到适合您项目的所有零件!

4.6 获取接收板 MAC 地址

要通过 ESP-NOW 发送消息,您需要知道接收板的 MAC 地址。每个开发板都有一个唯一的 MAC 地址(了解如何获取和更改 ESP32 MAC 地址)。

将以下代码上传到 ESP32 接收器板以获取其 MAC 地址。

// Complete Instructions to Get and Change ESP MAC Address: https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/

#ifdef ESP32
  #include <WiFi.h>
#else
  #include <ESP8266WiFi.h>
#endif

void setup(){
  Serial.begin(115200);
  Serial.println();
  Serial.print("ESP Board MAC Address:  ");
  Serial.println(WiFi.macAddress());
}
 
void loop(){

}

上传代码后,按 RST/EN 按钮,MAC 地址应显示在串行监视器上。

ESP-NOW ESP32 获取开发板 MAC 地址

4.7 ESP32 发送电路

ESP32 发送板连接到 DHT22 温湿度传感器。数据引脚连接到通用IO 4(D4端口).您可以选择任何其他合适的 GPIO(阅读 ESP32 引脚分配指南)。按照下图对电路进行接线。

ESP32 到 DHT22 温湿度传感器接线

5. ESP32 接收器 (ESP-NOW + Web Server)

ESP32 接收板接收来自发送板的数据包,并托管一个 Web 服务器以显示最新接收到的读数。

将以下代码上传到您的接收器板 - 该代码已准备好接收来自两个不同板的读数。

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-esp-now-wi-fi-web-server/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <esp_now.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <Arduino_JSON.h>
#include <ESPAsyncWebSrv.h>

// Replace with your network credentials (STATION)
const char* ssid = "J09 502";
const char* password = "qwertyuiop111";

// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
  int id;
  float temp;
  float hum;
  unsigned int readingId;
} struct_message;

struct_message incomingReadings;

JSONVar board;

AsyncWebServer server(80);
AsyncEventSource events("/events");

// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) { 
  // Copies the sender mac address to a string
  char macStr[18];
  Serial.print("Packet received from: ");
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  Serial.println(macStr);
  memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
  
  board["id"] = incomingReadings.id;
  board["temperature"] = incomingReadings.temp;
  board["humidity"] = incomingReadings.hum;
  board["readingId"] = String(incomingReadings.readingId);
  String jsonString = JSON.stringify(board);
  events.send(jsonString.c_str(), "new_readings", millis());
  
  Serial.printf("Board ID %u: %u bytes\n", incomingReadings.id, len);
  Serial.printf("t value: %4.2f \n", incomingReadings.temp);
  Serial.printf("h value: %4.2f \n", incomingReadings.hum);
  Serial.printf("readingID value: %d \n", incomingReadings.readingId);
  Serial.println();
}

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP-NOW DASHBOARD</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  <link rel="icon" href="data:,">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    p {  font-size: 1.2rem;}
    body {  margin: 0;}
    .topnav { overflow: hidden; background-color: #2f4468; color: white; font-size: 1.7rem; }
    .content { padding: 20px; }
    .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
    .cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
    .reading { font-size: 2.8rem; }
    .packet { color: #bebebe; }
    .card.temperature { color: #fd7e14; }
    .card.humidity { color: #1b78e2; }
  </style>
</head>
<body>
  <div class="topnav">
    <h3>ESP-NOW DASHBOARD</h3>
  </div>
  <div class="content">
    <div class="cards">
      <div class="card temperature">
        <h4><i class="fas fa-thermometer-half"></i> BOARD #1 - TEMPERATURE</h4><p><span class="reading"><span id="t1"></span> &deg;C</span></p><p class="packet">Reading ID: <span id="rt1"></span></p>
      </div>
      <div class="card humidity">
        <h4><i class="fas fa-tint"></i> BOARD #1 - HUMIDITY</h4><p><span class="reading"><span id="h1"></span> &percnt;</span></p><p class="packet">Reading ID: <span id="rh1"></span></p>
      </div>
      <div class="card temperature">
        <h4><i class="fas fa-thermometer-half"></i> BOARD #2 - TEMPERATURE</h4><p><span class="reading"><span id="t2"></span> &deg;C</span></p><p class="packet">Reading ID: <span id="rt2"></span></p>
      </div>
      <div class="card humidity">
        <h4><i class="fas fa-tint"></i> BOARD #2 - HUMIDITY</h4><p><span class="reading"><span id="h2"></span> &percnt;</span></p><p class="packet">Reading ID: <span id="rh2"></span></p>
      </div>
    </div>
  </div>
<script>
if (!!window.EventSource) {
 var source = new EventSource('/events');
 
 source.addEventListener('open', function(e) {
  console.log("Events Connected");
 }, false);
 source.addEventListener('error', function(e) {
  if (e.target.readyState != EventSource.OPEN) {
    console.log("Events Disconnected");
  }
 }, false);
 
 source.addEventListener('message', function(e) {
  console.log("message", e.data);
 }, false);
 
 source.addEventListener('new_readings', function(e) {
  console.log("new_readings", e.data);
  var obj = JSON.parse(e.data);
  document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2);
  document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2);
  document.getElementById("rt"+obj.id).innerHTML = obj.readingId;
  document.getElementById("rh"+obj.id).innerHTML = obj.readingId;
 }, false);
}
</script>
</body>
</html>)rawliteral";

void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);

  // Set the device as a Station and Soft Access Point simultaneously
  WiFi.mode(WIFI_AP_STA);
  Serial.println(WiFi.macAddress());
  // WiFi.softAPsetHostname("ESP_vor");
  
  // Set device as a Wi-Fi Station
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Setting as a Wi-Fi Station..");
  }
  Serial.print("Station IP Address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Wi-Fi Channel: ");
  Serial.println(WiFi.channel());

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  
  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info
  esp_now_register_recv_cb(OnDataRecv);

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html);
  });
   
  events.onConnect([](AsyncEventSourceClient *client){
    if(client->lastId()){
      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    // send event with message "hello!", id current millis
    // and set reconnect delay to 1 second
    client->send("hello!", NULL, millis(), 10000);
  });
  server.addHandler(&events);
  server.begin();
}
 
void loop() {
  static unsigned long lastEventTime = millis();
  static const unsigned long EVENT_INTERVAL_MS = 5000;
  if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
    events.send("ping",NULL,millis());
    lastEventTime = millis();
  }
}

5.1 代码的工作原理

首先,包括必要的库。

#include <esp_now.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <Arduino_JSON.h>
#include <ESPAsyncWebSrv.h>

这Arduino_JSON图书馆是必需的,因为我们将创建一个 JSON 变量,其中包含从每个板接收的数据。此 JSON 变量将用于将所有需要的信息发送到网页,稍后将在本项目中看到。

在以下行中插入您的网络凭据,以便 ESP32 可以连接到您的本地网络。

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

5.2 数据结构

然后,创建一个包含我们将接收的数据的结构。我们称这种结构为struct_message它包含电路板 ID、温度和湿度读数以及读数 ID。

typedef struct struct_message {
    int id;
    float temp;
    float hum;
    int readingId;
} struct_message;

创建一个类型的新变量struct_message这被称为incoming读数这将存储变量的值。

struct_message incomingReadings;

创建一个名为板.

JSONVar board;

在端口 80 上创建异步 Web 服务器。

AsyncWebServer server(80);

5.3 创建事件源

为了在收到新读数时自动在 Web 服务器上显示信息,我们将使用服务器发送事件 (SSE)。

以下行在 上创建一个新的事件源/事件.

AsyncEventSource events("/events");

服务器发送事件允许网页(客户端)从服务器获取更新。当新的 ESP-NOW 数据包到达时,我们将使用它在 Web 服务器页面上自动显示新的读数。

5.4 OnDataRecv() 函数

这OnDataRecv()函数将在收到新的 ESP-NOW 数据包时执行。

void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) 

在该函数中,打印发件人的MAC地址:

// Copies the sender mac address to a string
char macStr[18];
Serial.print("Packet received from: ");
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
         mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.println(macStr);

复制传入数据变量添加到incoming读数结构变量。

memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));

然后,使用接收到的信息创建一个 JSON 字符串变量 (json字符串变量):

board["id"] = incomingReadings.id;
board["temperature"] = incomingReadings.temp;
board["humidity"] = incomingReadings.hum;
board["readingId"] = String(incomingReadings.readingId);
String jsonString = JSON.stringify(board);

下面是一个示例,说明如何json字符串变量在收到读数后可能看起来像:

board = {
  "id": "1",
  "temperature": "24.32",
  "humidity" = "65.85",
  "readingId" = "2"
}

在收集了所有接收到的数据后json字符串变量,则该信息作为事件(“new_readings”).

events.send(jsonString.c_str(), "new_readings", millis());

稍后,我们将看到如何在客户端处理这些事件。

最后,将接收到的信息打印到Arduino IDE串行监视器上,以便进行调试:

Serial.printf("Board ID %u: %u bytes\n", incomingReadings.id, len);
Serial.printf("t value: %4.2f \n", incomingReadings.temp);
Serial.printf("h value: %4.2f \n", incomingReadings.hum);
Serial.printf("readingID value: %d \n", incomingReadings.readingId);
Serial.println();

5.5 构建网页

这index_html变量包含用于构建网页的所有 HTML、CSS 和 JavaScript。我们不会详细介绍 HTML 和 CSS 的工作原理。我们只看一下如何处理服务器发送的事件。

5.6 处理事件

创建一个新的事件源对象,并指定发送更新的页面的 URL。在我们的例子中,它是/事件.

if (!!window.EventSource) {
 var source = new EventSource('/events');

实例化事件源后,可以使用以下命令开始侦听来自服务器的消息addEventListener().

这些是默认事件侦听器,如 AsyncWebServer 文档中所示。

source.addEventListener('open', function(e) {
  console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
 if (e.target.readyState != EventSource.OPEN) {
   console.log("Events Disconnected");
 }
}, false);

source.addEventListener('message', function(e) {
 console.log("message", e.data);
}, false);

然后,为“new_readings”.

source.addEventListener('new_readings', function(e) 

当 ESP32 收到新数据包时,它会发送一个 JSON 字符串,其中包含作为事件 (“new_readings”) 发送给客户端。以下行处理浏览器收到该事件时发生的情况。

console.log("new_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2);
document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2);
document.getElementById("rt"+obj.id).innerHTML = obj.readingId;
document.getElementById("rh"+obj.id).innerHTML = obj.readingId;

基本上,在浏览器控制台上打印新的读数,并将接收到的数据放入网页上具有相应 id 的元素中。

5.7 setup()

在setup(),将 ESP32 接收器设置为接入点和 Wi-Fi 站:

WiFi.mode(WIFI_AP_STA);

以下几行将 ESP32 连接到本地网络并打印 IP 地址和 Wi-Fi 通道:

// Set device as a Wi-Fi Station
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Setting as a Wi-Fi Station..");
}
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());

初始化 ESP-NOW。

if (esp_now_init() != ESP_OK) {
  Serial.println("Error initializing ESP-NOW");
  return;
}

注册OnDataRecvcallback 函数,以便在新的 ESP-NOW 数据包到达时执行。

esp_now_register_recv_cb(OnDataRecv);

5.8 处理请求

当您访问根 URL 上的 ESP32 IP 地址时,发送存储在/index_html变量来构建网页。

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html);
});

5.9 服务器事件源

在服务器上设置事件源。

events.onConnect([](AsyncEventSourceClient *client){
  if(client->lastId()){
    Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
  }
  // send event with message "hello!", id current millis
  // and set reconnect delay to 1 second
  client->send("hello!", NULL, millis(), 10000);
 );
  server.addHandler(&events);

最后,启动服务器。

server.begin();

5.10 loop()

在loop(),每 5 秒发送一次 ping。这用于在客户端检查服务器是否仍在运行。

static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 5000;
if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
  events.send("ping",NULL,millis());
  lastEventTime = millis();
}

下图总结了服务器发送的事件如何在此项目上工作,以及它如何在不刷新网页的情况下更新值。

ESP32 ESP-NOW Web 服务器传感器 Dashboard 项目概述

将代码上传到接收器板后,按板载 EN/RST 按钮。ESP32 IP 地址应打印在串行监视器和 Wi-Fi 通道上。

ESP-NOW 获取 ESP32 IP 地址和 Wi-Fi 通道

6. ESP32 发送码 (ESP-NOW)

每个发送板都会通过 ESP-NOW 发送一个结构,其中包含板 ID(以便您可以识别哪个板发送了读数)、温度、湿度和读数 ID。读取 ID 是一个 int 数字,用于了解发送了多少条消息。

ESP32 发送器接收器板,采用 ESP-NOW,使用 Arduino IDE

将以下代码上传到每个发送方板。别忘了增加编号每个发送器板的编号,然后将您的 SSID 插入WIFI_SSID变量。

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-esp-now-wi-fi-web-server/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <esp_now.h>
#include <esp_wifi.h>
#include <WiFi.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>

// Set your Board ID (ESP32 Sender #1 = BOARD_ID 1, ESP32 Sender #2 = BOARD_ID 2, etc)
#define BOARD_ID 1

// Digital pin connected to the DHT sensor
#define DHTPIN 4  

// Uncomment the type of sensor in use:
#define DHTTYPE    DHT11     // DHT 11
// #define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

DHT dht(DHTPIN, DHTTYPE);

//MAC Address of the receiver 
uint8_t broadcastAddress[] = {0xE0, 0x5A, 0x1B, 0x5F, 0x12, 0xF0};

//Structure example to send data
//Must match the receiver structure
typedef struct struct_message {
    int id;
    float temp;
    float hum;
    int readingId;
} struct_message;

//Create a struct_message called myData
struct_message myData;

unsigned long previousMillis = 0;   // Stores last time temperature was published
const long interval = 10000;        // Interval at which to publish sensor readings

unsigned int readingId = 0;

// Insert your SSID
constexpr char WIFI_SSID[] = "vor";

int32_t getWiFiChannel(const char *ssid) {
  if (int32_t n = WiFi.scanNetworks()) {
      for (uint8_t i=0; i<n; i++) {
          if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
              return WiFi.channel(i);
          }
      }
  }
  return 0;
}

float readDHTTemperature() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float t = dht.readTemperature(true);
  // Check if any reads failed and exit early (to try again).
  if (isnan(t)) {    
    Serial.println("Failed to read from DHT sensor!");
    return 0;
  }
  else {
    Serial.println(t);
    return t;
  }
}

float readDHTHumidity() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  if (isnan(h)) {
    Serial.println("Failed to read from DHT sensor!");
    return 0;
  }
  else {
    Serial.println(h);
    return h;
  }
}

// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.print("\r\nLast Packet Send Status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
 
void setup() {
  //Init Serial Monitor
  Serial.begin(115200);

  dht.begin();
 
  // Set device as a Wi-Fi Station and set channel
  WiFi.mode(WIFI_STA);

  int32_t channel = getWiFiChannel(WIFI_SSID);

  WiFi.printDiag(Serial); // Uncomment to verify channel number before
  esp_wifi_set_promiscuous(true);
  esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
  esp_wifi_set_promiscuous(false);
  WiFi.printDiag(Serial); // Uncomment to verify channel change after

  //Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  // Once ESPNow is successfully Init, we will register for Send CB to
  // get the status of Trasnmitted packet
  esp_now_register_send_cb(OnDataSent);
  
  //Register peer
  esp_now_peer_info_t peerInfo;
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.encrypt = false;
  
  //Add peer        
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }
}
 
void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // Save the last time a new reading was published
    previousMillis = currentMillis;
    //Set values to send
    myData.id = BOARD_ID;
    myData.temp = readDHTTemperature();
    myData.hum = readDHTHumidity();
    myData.readingId = readingId++;
     
    //Send message via ESP-NOW
    esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
    if (result == ESP_OK) {
      Serial.println("Sent with success");
    }
    else {
      Serial.println("Error sending the data");
    }
  }
}

6.1 代码的工作原理

首先导入所需的库:

#include <esp_now.h>
#include <esp_wifi.h>
#include <WiFi.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>

6.2 设置板 ID

定义 ESP32 发送板 ID,例如为 ESP1 发送板 #32 设置 BOARD_ID 1,以此类推…

// Set your Board ID (ESP32 Sender #1 = BOARD_ID 1, ESP32 Sender #2 = BOARD_ID 2, etc)
#define BOARD_ID 1

6.3 DHT传感器

定义 DHT 传感器连接到的引脚。在我们的示例中,它连接到通用IO 4.

#define DHTPIN 4

选择您正在使用的 DHT 传感器类型。我们正在使用 DHT11。

// Uncomment the type of sensor in use:
#define DHTTYPE    DHT11     // DHT 11
//#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

创建一个DHT(DHT)引脚上的对象和前面定义的类型。

DHT dht(DHTPIN, DHTTYPE);

6.4 接收方的MAC地址

在下一行插入接收方的 MAC 地址(例如):

uint8_t broadcastAddress[] = {0xE0, 0x5A, 0x1B, 0x5F, 0x12, 0xF0};

6.5 数据结构

然后,创建一个包含我们要发送的数据的结构。这struct_message包含电路板 ID、温度读数、湿度读数和读数 ID。

typedef struct struct_message {
    int id;
    float temp;
    float hum;
    int readingId;
} struct_message;

创建一个类型的新变量struct_message这被称为myData(我的数据)存储变量的值。

struct_message myData;

6.6 定时器间隔

创建一些辅助计时器变量,每 10 秒发布一次读数。您可以在间隔变量。

unsigned long previousMillis = 0;  // Stores last time temperature was published
const long interval = 10000;       // Interval at which to publish sensor readings

初始化readingId变量 – 它跟踪发送的读数数量。

unsigned int readingId = 0;

6.7 更改 Wi-Fi 信道

现在,我们将获得接收器的 Wi-Fi 频道。这很有用,因为它允许我们自动将相同的 Wi-Fi 信道分配给发送板。

为此,您必须在以下行中插入SSID:

constexpr char WIFI_SSID[] = "vor";

然后,getWiFiChannel()函数扫描您的网络并获取其频道。

int32_t getWiFiChannel(const char *ssid) {
  if (int32_t n = WiFi.scanNetworks()) {
    for (uint8_t i=0; i<n; i++) {
      if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
        return WiFi.channel(i);
      }
    }
  }
  return 0;
}

这段代码是由 Stephane(我们的读者之一)提出的。你可以在这里看到他的完整例子。

6.8 读数温度

这readDHTTemperature()函数从 DHT 传感器读取并返回温度。如果它无法获得温度读数,它会返回0.

float readDHTTemperature() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  //float t = dht.readTemperature(true);
  // Check if any reads failed and exit early (to try again).
  if (isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
    return 0;
  }
  else {
    Serial.println(t);
    return t;
  }
}

6.9 读数湿度

这readDHTHumidity()函数读取并返回来自 DHT 传感器的湿度。如果它无法获得湿度读数,它会返回0.

float readDHTHumidity() {
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  if (isnan(h)) {
    Serial.println("Failed to read from DHT sensor!");
    return 0;
  }
  else {
    Serial.println(h);
    return h;
  }
}

注意:要了解有关从 DHT22 或 DHT11 传感器获取温度和湿度的更多信息,请阅读:使用 Arduino IDE 的 ESP32 和 DHT11/DHT22 温湿度传感器。

6.10 OnDataSent 回调函数

这OnDataSent()发送消息时将执行回调函数。在这种情况下,此函数将打印消息是否成功传递。

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.print("\r\nLast Packet Send Status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}

6.11 setup()

初始化串行监视器。

Serial.begin(115200);

将 ESP32 设置为 Wi-Fi 站。

6.12 WiFi.mode(WIFI_STA);

设置其频道以匹配接收器的 Wi-Fi 频道:

int32_t channel = getWiFiChannel(WIFI_SSID);

WiFi.printDiag(Serial); // Uncomment to verify channel number before
esp_wifi_set_promiscuous(true);
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
esp_wifi_set_promiscuous(false);
WiFi.printDiag(Serial); // Uncomment to verify channel change after

初始化 ESP-NOW。

if (esp_now_init() != ESP_OK) {
  Serial.println("Error initializing ESP-NOW");
  return;
}

成功初始化 ESP-NOW 后,注册发送消息时将调用的回调函数。在这种情况下,请注册OnDataSent()之前创建的函数。

esp_now_register_send_cb(OnDataSent);

6.13 添加对等节点

要将数据发送到另一块板(接收器),您需要将其配对为对等体。以下行注册接收方并将其添加为对等体。

// Register peer
esp_now_peer_info_t peerInfo;
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.encrypt = false;

// Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
  Serial.println("Failed to add peer");
  return;
}

6.14 loop()

在loop(),检查是否是时候获取和发送新读数了。

unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
  // Save the last time a new reading was published
  previousMillis = currentMillis;

6.14 发送 ESP-NOW 消息

最后,通过 ESP-NOW 发送消息结构。

// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
if (result == ESP_OK) {
  Serial.println("Sent with success");
}
else {
  Serial.println("Error sending the data");
}

将代码上传到发件人看板。您应该注意到,这些板将其 Wi-Fi 通道更改为接收器板的通道。在我们的例子中,电路板将其 Wi-Fi 信道号更改为 6。
在这里插入图片描述

7. 示范

将代码上传到所有开发板后,如果一切按预期进行,ESP32 接收器开发板应开始接收来自其他开发板的传感器读数。
在这里插入图片描述

ESP32 ESP-NOW Web 服务器传感器仪表板 ESP-NOW 和 Wi-Fi 演示传感器读数
在本地网络上打开浏览器并输入 ESP32 IP 地址。

ESP32 ESP-NOW Web 服务器传感器 Dashboard Web 浏览器

它应该加载每个板的温度、湿度和读数 ID。收到新数据包后,您的网页会自动更新,而无需刷新网页。

ESP32 ESP-NOW Web 服务器传感器仪表板移动响应

8. 总结

🥳🥳🥳现在,我们在本教程中,您学习了如何使用 ESP-NOW 和 Wi-Fi 设置 Web 服务器以接收来自多个开发板的 ESP-NOW 数据包(多对一配置)。🛹🛹🛹从而实现对外部世界进行感知,充分认识这个有机与无机的环境,后期会持续分享esp32跑freertos实用案列🥳🥳🥳科学地合理地进行创作和发挥效益,然后为人类社会发展贡献一点微薄之力。🤣🤣🤣

参考文献:

  • 【ESP-NOW ESP32 开发板之间的双向通信】
  • 【ESP-NOW 入门(ESP32 with Arduino IDE)】
  • 【ESP-NOW with ESP32:向多个开发板发送数据(一对多)】
  • 【ESP-NOW with ESP32:从多个开发板接收数据(多对一)】
  • ESP32: ESP-NOW Web Server Sensor Dashboard (ESP-NOW + Wi-Fi)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1351445.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

常见安全概念澄清,Java小白入门(八)

认证 认证 (Identification) 是验证当前用户的身份。 常见的认证技术&#xff1a; 身份证用户名和密码用户手机&#xff1a;手机短信、手机二维码扫描、手势密码用户的电子邮箱用户的生物学特征&#xff1a;指纹、语音、眼睛虹膜 授权 授权 (Authorization) 指赋予用户系统…

js常用事件演示

目录 JS事件的具体方法 窗口事件 表单事件 键盘事件 鼠标事件 知识小拓展 JS事件的具体方法 我们用到JavaScript的时候js的事件就显得特别重要了 事件名说明onsubmit当表单提交时触发该事件onclick鼠标单击事件ondblclick鼠标双击事件onblur元素失去焦点onfocus元素获得…

了解深度学习优化器:Momentum、AdaGrad、RMSProp 和 Adam

slavahead 一、介绍 DEEP学习在人工智能领域迈出了一大步。目前&#xff0c;神经网络在非表格数据&#xff08;图像、视频、音频等&#xff09;上的表现优于其他类型的算法。深度学习模型通常具有很强的复杂性&#xff0c;并提出数百万甚至数十亿个可训练的参数。这就是为什么在…

玩转贝启科技BQ3588C开源鸿蒙系统开发板 —— 编译构建及此过程中的踩坑填坑(1)

接前一篇文章&#xff1a;玩转贝启科技BQ3588C开源鸿蒙系统开发板 —— 代码下载&#xff08;2&#xff09; 本文主要参考&#xff1a; BQ3588C_代码下载 上一回完成了代码下载&#xff0c;本回开始进行编译构建。 1. 编译构建 &#xff08;1&#xff09;执行prebuilts 在源…

SpringBoot 集成支付宝支付

网页操作步骤 1.进入支付宝开发平台—沙箱环境 使用开发者账号登录开放平台控制平台 2.点击沙箱进入沙箱环境 说明&#xff1a;沙箱环境支持的产品&#xff0c;可以在沙箱控制台 沙箱应用 > 产品列表 中查看。 3.进入沙箱&#xff0c;配置接口加签方式 在沙箱进行调试前…

扫地机器人地图与用户终端的同步

以下内容为本人的学习笔记&#xff0c;如需要转载&#xff0c;请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/APaJheSbgTW3jNssWsp5Ng 地图数据来源于机器人算法模块&#xff0c;一般通过SLAM算法完成建图的过程。 建图过程中&#xff0c;基础数据涉及到各…

Android 串口协议

前言 本协议是 Android 应用端与主控板之间的通信协议&#xff0c;是串行通信协议。 协议要求同一时间只能有两个通讯端点在相互通讯&#xff0c;采用小端传输数据。 硬件层基于RS485协议&#xff0c;采取半双工&#xff0c;一主多从的通讯模式。Android定义为主机&#xff0c…

一、初识Redis与分布式系统

目录 一、Redis应用 二、实现方式 三、Redis应用 四、分布式系统 五、分布式系统实现 1、应用服务和数据库服务分离 2、引入负载均衡&#xff0c;应用服务器集群&#xff08;解决高并发&#xff09; 3、引入读写分离&#xff0c;数据库主从结构&#xff08;解决高并发&a…

如何在iPhone设备中查看崩溃日志

​ 目录 如何在iPhone设备中查看崩溃日志 摘要 引言 导致iPhone设备崩溃的主要原因是什么&#xff1f; 使用克魔助手查看iPhone设备中的崩溃日志 奔溃日志分析 总结 摘要 本文介绍了如何在iPhone设备中查看崩溃日志&#xff0c;以便调查崩溃的原因。我们将展示三种不同的…

流媒体学习之路(WebRTC)——GCC分析(4)

流媒体学习之路(WebRTC)——GCC分析&#xff08;4&#xff09; —— 我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost目标&#xff1a;可以让大家熟悉各类Qos能力、带宽估计能力&#xff0c;提供每个环节关键参数调节接口并实现一个json全配置…

【Java并发】深入浅出 synchronized关键词原理-上

一个问题的思考 建设我们有两个线程&#xff0c;一个进行5000次的相加操作&#xff0c;另一个进行5000次的减操作。那么最终结果是多少 package com.jia.syn;import java.util.concurrent.TimeUnit;/*** author qxlx* date 2024/1/2 10:08 PM*/ public class SynTest {privat…

使用Vue3开发学生管理系统模板1

环境搭建 通过解压之前《Vue3开发后台管理系统模板》的代码&#xff0c;我们能够得到用户增删改查的页面&#xff0c;我们基于用户增删改查的页面做进一步的优化。 创建学生增删改查页面 第一步&#xff1a;复制用户增删改查页面&#xff0c;重命名为StudentCRUD.vue <…

新闻稿发布:媒体重要还是价格重要

在当今信息爆炸的数字时代&#xff0c;企业推广与品牌塑造不可或缺的一环就是新闻稿发布。新闻稿是一种通过媒体渠道传递企业信息、宣传品牌、事件或产品新闻的文本形式。发布新闻稿的过程旨在将企业的声音传递给更广泛的受众&#xff0c;借助媒体平台实现品牌故事的广泛传播。…

exec、execFile、fork、spawn的区别与使用场景

在Node.js中&#xff0c;通过子进程可以实现并行执行任务&#xff0c;处理复杂的操作&#xff0c;以及与外部命令或文件进行交互。Node.js提供了多种子进程创建方法&#xff0c;包括exec、execFile、fork和spawn。本文将对这些方法进行比较&#xff0c;并介绍它们的适用场景和示…

【深度学习-基础学习】Transformer 笔记

本篇文章学习总结 李宏毅 2021 Spring 课程中关于 Transformer 相关的内容。课程链接以及PPT&#xff1a;李宏毅Spring2021ML这篇Blog需要Self-Attention为前置知识。 Transfomer 简介 Transfomer 架构主要是用来解决 Seq2Seq 问题的&#xff0c;也就是 Sequence to Sequence…

大数据StarRocks(一) StarRocks概述

1 StarRocks介绍 StarRocks是新一代极速全场景MPP(Massively Parallel Processing)数据库&#xff0c;它充分吸收关系型OLAP数据库和分布式存储系统在大数据时代的优秀研究成果&#xff0c;在业界实践的基础上&#xff0c;进一步改进优化、升级架构&#xff0c;并增添了众多全…

NSSCTF 1zjs

开启环境: 搞就完事了,别玩魔法! 源码打开 点击访问:./dist/index.umd.js" 搜索php,找到23条相关的,注意到有一个特别的信息: PERFORMANCE OF THIS SOFTWARE.Your gift just take it : /fk3f1ag.php 访问: node4.anna.nssctf.cn:28325/fk3f1ag.php 得到这样: ([![]…

【hyperledger-fabric】部署和安装

简介 对hyperledger-fabric进行安装&#xff0c;话不多说&#xff0c;直接开干。但是需要申明一点&#xff0c;也就是本文章全程是开着加速器进行的资源操作&#xff0c;所以对于没有开加速器的情况可能会由于网络原因导致下载资源失败。 资料提供 1.官方部署文档在此&#…

IPC之十二:使用libdbus在D-Bus上异步发送/接收信号的实例

IPC 是 Linux 编程中一个重要的概念&#xff0c;IPC 有多种方式&#xff0c;本 IPC 系列文章的前十篇介绍了几乎所有的常用的 IPC 方法&#xff0c;每种方法都给出了具体实例&#xff0c;前面的文章里介绍了 D-Bus 的基本概念以及调用远程方法的实例&#xff0c;本文介绍 D-Bus…

mysql 条件位运算实现多值存储

一、多值存储 mysql 条件位运算位运算实现多值存储&#xff0c;方法适合数据范围有限&#xff0c;且不会变更在业务上往往会出现多选的情况&#xff0c;例&#xff1a;选择 周一 至 周日 随意组合&#xff1b; 数据在设计时就会如何去储存&#xff1f; 一种是一般是在储存是以…