ESP32教程(基于Arduino IDE)9 - 异步网页服务器 Asynchronous Web Server(DHT11)

ESP32教程系列
1 - 认识ESP32
2 - Arduino IDE安装&配置
3 - 基本操作
4 - WiFi配网
5 - WebServer①
6 - Web Server②控制输出
7 - Web Server③ HTML&CSS基础
8 - Web Server④ 在Arduinio中添加HTML&CSS
9 - 异步网页服务器 Asynchronous Web Server(DHT11)
10 - OTA ESP32无线升级,告别数据线

ESP32实战系列
WiFi遥控小车

ESP32 Homekit系列
Homekit & Homespan介绍
点亮一颗LED
调节LED亮度

一、 是异步网页服务器(Asynchronous Web Server)

  1. 定义

    • Asynchronous Web Server(异步Web服务器)是一种网络服务器架构。与传统的同步Web服务器不同,它采用异步的方式来处理客户端的请求。在异步模型中,服务器在处理一个请求时,不会阻塞其他请求的处理。例如,当一个请求需要等待某个耗时的操作(如从数据库读取数据、等待外部设备响应等)完成时,服务器可以先去处理其他的请求,而不是一直等待这个操作结束。
  2. 工作原理

    • 事件驱动机制
      • 异步Web服务器基于事件驱动来工作。它会监听各种事件,如客户端连接请求、数据发送请求、数据接收请求等。当一个事件发生时,例如有新的客户端尝试连接到服务器,服务器会触发相应的事件处理程序来处理这个连接。
      • 以ESP32的Asynchronous Web Server为例,当有HTTP请求到达时,它会触发一个“请求接收”事件。这个事件会被添加到事件队列中,服务器会根据事件队列中的任务优先级等因素来决定何时处理这个请求。
    • 非阻塞I/O操作
      • 在处理请求的过程中,异步Web服务器使用非阻塞I/O操作。比如,当需要从文件系统读取网页文件发送给客户端时,它不会像同步服务器那样一直等待文件读取完成。而是发出读取请求后,继续处理其他事务。当文件读取完成时,会产生一个“文件读取完成”的事件,服务器再回过头来处理这个事件,将读取到的文件内容发送给客户端。
      • 假设服务器要从一个传感器读取数据并返回给客户端。在异步模式下,它会发送读取传感器数据的指令,然后可以去处理其他请求。当传感器数据读取完成后,会触发一个事件,告知服务器数据已准备好,此时服务器就可以将数据发送给等待的客户端。
  3. 优势

    • 高并发处理能力
      • 能够同时处理多个客户端请求,提高服务器的吞吐量。在物联网环境中,可能有多个客户端(如多个用户通过浏览器访问设备状态,或者多个设备向服务器发送数据)同时与服务器交互。异步Web服务器可以有效地处理这些并发请求,而不会因为某个请求的长时间等待而影响其他请求的处理。
    • 资源高效利用
      • 由于不会阻塞在等待操作上,服务器的资源(如CPU时间、内存等)可以得到更充分的利用。例如,在等待外部设备响应的过程中,服务器可以处理其他的任务,减少了资源闲置的时间。
    • 实时响应性好
      • 可以更快地对客户端请求做出响应,特别是对于一些实时性要求高的应用场景。比如实时数据推送,当有新的数据产生(如传感器数据变化),服务器可以及时将数据发送给客户端,而不需要客户端频繁地询问是否有新数据。

二、 ESP32 异步网页服务器 - 温湿度计Demo

上面的文字描述,不如动手实践来理解。做一个常见的DHT11的温湿度传感器的Demo。

目标
如下图,在网页端显示温湿度数据,并能自主实时更新。
在这里插入图片描述

需要使用到的库:DHT_sensor,ESPAsyncWebServer
当通过自带的IDE安装ESPAsyncWebServer库时,会议有下述提示,直接点击全部安装即可。在这里插入图片描述

先上代码

// 导入所需的库
#include "WiFi.h"
#include "ESPAsyncWebServer.h"
#include <Adafruit_Sensor.h>
#include <DHT.h>

const char* ssid = "";		//你的WiFi ssid
const char* password = "";	//你的WiFi密码

#define DHTPIN 13    // DHT传感器连接的数字引脚

// 根据你的DHT传感器注释掉无关的型号代码
#define DHTTYPE    DHT11     // DHT 11
//#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

DHT dht(DHTPIN, DHTTYPE);

// 在端口80上创建AsyncWebServer对象
AsyncWebServer server(80);

//读取温度信息
String readDHTTemperature() {
  // DHT系列传感器速度较慢,读取可能最多延迟2秒
  // 以摄氏度读取温度(默认)
  float t = dht.readTemperature();
  // 检查读取是否失败并提前退出(尝试重新读取)。
  if (isnan(t)) {    
    Serial.println("无法从DHT传感器读取数据!");
    return "--";
  }
  else {
    Serial.println(t);
    return String(t);
  }
}
//读取湿度信息
String readDHTHumidity() {
  float h = dht.readHumidity();
  if (isnan(h)) {
    Serial.println("无法从DHT传感器读取数据!");
    return "--";
  }
  else {
    Serial.println(h);
    return String(h);
  }
}
//网页端代码
//  <meta charset="UTF-8">代码为避免中文在显示时出现乱码
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta charset="UTF-8">
  <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">
  <style>
    html {
     font-family: Arial;
     display: inline-block;
     margin: 0px auto;
     text-align: center;
    }
    h2 { font-size: 3.0rem; }
    p { font-size: 3.0rem; }
    .units { font-size: 1.2rem; }
    .dht-labels{
      font-size: 1.5rem;
      vertical-align:middle;
      padding-bottom: 15px;
    }
  </style>
</head>
<body>
  <h2>ESP32 DHT 服务器</h2>
  <p>
    <i class="fas fa-thermometer-half" style="color:#059e8a;"></i> 
    <span class="dht-labels">温度</span> 
    <span id="temperature">%TEMPERATURE%</span>
    <sup class="units">&deg;C</sup>
  </p>
  <p>
    <i class="fas fa-tint" style="color:#00add6;"></i> 
    <span class="dht-labels">湿度</span>
    <span id="humidity">%HUMIDITY%</span>
    <sup class="units">&percnt;</sup>
  </p>
</body>
<script>
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("temperature").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/temperature", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("humidity").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/humidity", true);
  xhttp.send();
}, 10000 ) ;
</script>
</html>)rawliteral";

// 使用DHT值替换占位符
String processor(const String& var){
  //Serial.println(var);
  if(var == "TEMPERATURE"){
    return readDHTTemperature();
  }
  else if(var == "HUMIDITY"){
    return readDHTHumidity();
  }
  return String();
}

void setup(){
  // 用于调试的串口
  Serial.begin(115200);
  dht.begin();  //dht传感器初始化
  // 连接到Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("正在连接到WiFi...");
  }

  // 打印ESP32本地IP地址
  Serial.println(WiFi.localIP());

  // 路由根目录/网页
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });
  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readDHTTemperature().c_str());
  });
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readDHTHumidity().c_str());
  });

  // 启动服务器
  server.begin();
}
 
void loop(){
  
}

上述代码在编译后后可能会以下错误。

说这个文件\ESPAsyncWebServer\src\WebAuthentication.cpp的函数有误

Documents\Arduino\libraries\ESPAsyncWebServer\src\WebAuthentication.cpp:75:3: error: ‘mbedtls_md5_update_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_update’?
75 | mbedtls_md5_update_ret(&_ctx, data, len);
| ^~~~~~~~~~~~~~~~~~~~~~
| mbedtls_md5_update
\Documents\Arduino\libraries\ESPAsyncWebServer\src\WebAuthentication.cpp:76:3: error: ‘mbedtls_md5_finish_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_finish’?
76 | mbedtls_md5_finish_ret(&_ctx, _buf);
| ^~~~~~~~~~~~~~~~~~~~~~
| mbedtls_md5_finish

exit status 1

这个bug修复方法有2个,1是更新开发板的核心库或者更新ESPAsyncWebServer库
2是手动修改带代码。
找到ESPAsyncWebServer\src\WebAuthentication.cpp这个文件

将下述函数名称里_ret删除即可。

#ifdef ESP32
  mbedtls_md5_init_ret(&_ctx);
  mbedtls_md5_starts_ret(&_ctx);
  mbedtls_md5_update_ret(&_ctx, data, len);
  mbedtls_md5_finish_ret(&_ctx, _buf);

来看看各部分代码的工作原理

读取温湿度

String readDHTTemperature() {
  // DHT系列传感器速度较慢,读取可能最多延迟2秒
  // 以摄氏度读取温度(默认)
  float t = dht.readTemperature();
  // 检查读取是否失败并提前退出(尝试重新读取)。
  if (isnan(t)) {    
    Serial.println("无法从DHT传感器读取数据!");
    return "--";
  }
  else {
    Serial.println(t);
    return String(t);
  }
}

readTemperature()为读取温度的函数
isnan(t)函数用于检查数值是否是 “NaN”(Not a Number)。这个函数返回一个布尔值,通常用于判断传感器读取或数学运算的结果是否有效
最后将t强转为字符串类型。
湿度读取部分同理。

下述代码作用为让该网页兼容任意的浏览器

 <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">

下述代码为定义网页的CSS样式,通过控制字体、布局和文本对齐等属性,来美化和布局显示网页的元素。

<style>
  html {
    font-family: Arial;             /* 设置整个HTML页面的默认字体为 Arial */
    display: inline-block;          /* 以内联块的方式显示元素,方便水平居中 */
    margin: 0px auto;               /* 上下边距为0,左右自动居中以确保页面居中 */
    text-align: center;             /* 设置文本在整个页面中居中对齐 */
  }
  h2 { 
    font-size: 3.0rem;              /* 设置所有<h2>元素的字体大小为 3.0rem,较大标题样式 */
  }
  p { 
    font-size: 3.0rem;              /* 设置所有<p>元素的字体大小为 3.0rem,用于显示主要内容 */
  }
  .units { 
    font-size: 1.2rem;              /* 设置类为“units”的元素的字体大小为 1.2rem,用于单位符号 */
  }
  .dht-labels {
    font-size: 1.5rem;              /* 设置类为“dht-labels”的元素的字体大小为 1.5rem,用于标签 */
    vertical-align: middle;         /* 设置垂直对齐方式为居中,用于图标和文字的对齐 */
    padding-bottom: 15px;           /* 设置底部内边距为 15px,以增加与下方元素的间距 */
  }
</style>

这一部分代码用于在网页上显示温度信息,包括图标、温度标签、温度值和单位符号。
湿度部分同理。

<p>
  <i class="fas fa-thermometer-half" style="color:#059e8a;"></i> 
  <!-- 插入一个图标元素,使用Font Awesome的“半温度计”图标,颜色设置为#059e8a(绿色) -->
  
  <span class="dht-labels">温度</span> 
  <!-- 使用<span>元素显示“温度”标签,应用了"dht-labels"类来调整字体大小和对齐方式 -->
  
  <span id="temperature">%TEMPERATURE%</span>
  <!-- 使用<span>元素显示温度值,通过ID为“temperature”来标识,值会被动态替换为读取的温度(%TEMPERATURE%占位符) -->
  
  <sup class="units">&deg;C</sup>
  <!-- 使用<sup>元素显示温度单位“℃”,应用了“units”类来调整字体大小,且该元素为上标显示 -->
</p>

这一部分代码是JavaScript,用于每隔10秒向服务器发送一次GET请求,分别获取最新的温度和湿度数据,并更新页面上相应的部分

<script>
setInterval(function() {
  var xhttp = new XMLHttpRequest();  // 创建一个新的XMLHttpRequest对象,用于发送HTTP请求
  xhttp.onreadystatechange = function() {  // 当请求状态发生变化时触发的回调函数
    if (this.readyState == 4 && this.status == 200) {  // 检查请求是否完成并且响应状态是200(成功)
      document.getElementById("temperature").innerHTML = this.responseText;  // 将返回的温度数据更新到ID为"temperature"的元素中
    }
  };
  xhttp.open("GET", "/temperature", true);  // 初始化GET请求,目标为服务器路径“/temperature”,请求是异步的
  xhttp.send();  // 发送请求
}, 10000);  // 每隔10000毫秒(10秒)重复执行该函数
...
</script>

processor函数,用于在网页模板中替换占位符字符串 %TEMPERATURE% 和 %HUMIDITY%,将它们动态地替换为从DHT传感器读取的实际温度和湿度值。

String processor(const String& var) {
  //Serial.println(var);
  if (var == "TEMPERATURE") {
    return readDHTTemperature();  
    // 如果传入的字符串是 "TEMPERATURE",调用函数 readDHTTemperature() 获取温度值,并返回这个值
  } 
  else if (var == "HUMIDITY") {
    return readDHTHumidity();  
    // 如果传入的字符串是 "HUMIDITY",调用函数 readDHTHumidity() 获取湿度值,并返回这个值
  }
  return String();  
  // 如果传入的字符串既不是 "TEMPERATURE" 也不是 "HUMIDITY",返回一个空字符串
}

占位符(Placeholder) 是一种用于标记某些数据在之后会被替换的符号或文本,通常用作临时占据某些位置的文本,等到有了具体的数据或信息时再进行替换,使代码更易读、灵活,特别是在需要动态内容时非常有用。

%TEMPERATURE% 和 %HUMIDITY% 就是占位符。

<span id="temperature">%TEMPERATURE%</span>
<span id="humidity">%HUMIDITY%</span>

这段代码是配置ESP32的Web服务器,以处理不同的HTTP请求。使用了ESPAsyncWebServer库,用于设置在不同的路径上响应GET请求,并返回相应的数据或页面。

 server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });
  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readDHTTemperature().c_str());
  });
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readDHTHumidity().c_str());
  });
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html, processor);
});
  • server.on(“/”, HTTP_GET, …):
    这行代码定义了在服务器根路径 / 上的请求处理。
    "HTTP_GET" 表示处理的是HTTP GET请求。
    [] (AsyncWebServerRequest *request) 是一个Lambda表达式,即回调函数,用于在请求到来时执行特定的操作。

  • request->send_P(200, “text/html”, index_html, processor):

  • send_P() 方法发送网页的响应:

  • 200 表示HTTP状态码,200意味着成功。

  • "text/html" 表示内容类型为HTML。

  • index_html 是保存网页内容的HTML模板(用PROGMEM存储)。

  • processor 是处理模板中的占位符的函数(用于动态替换网页中的数据)。

当访问服务器的根目录 / 时,这行代码会将网页模板 index_html 发送给客户端,processor 函数会动态地替换模板中的占位符,如 %TEMPERATURE%%HUMIDITY%

server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/plain", readDHTTemperature().c_str());
});
  • server.on(“/temperature”, HTTP_GET, …):
    这行代码定义了当用户访问路径 /temperature 时的请求处理。
    "HTTP_GET" 表示处理HTTP GET请求。

  • request->send_P(200, “text/plain”, readDHTTemperature().c_str()):

  • 通过调用 readDHTTemperature() 函数获取当前的温度数据,并将其作为响应发送。

  • "text/plain" 表示内容类型是纯文本。

  • readDHTTemperature().c_str() 将温度值转换为C风格的字符串指针(用于发送)。

  • 当用户访问 /temperature 这个路径时,服务器会返回当前的温度数据。

结束

以上就是异步网页服务器的一个基本示例。如果觉得刷新速度较慢,可以更改刷新时间。
以上内容如有错误,欢迎指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值