在本用户指南中,我们将专注于构建异步 Web 服务器。Web 服务器将使用 Arduino IDE 通过 ESPAsyncWebServer 库在 ESP32 中创建。Web 服务器将允许用户通过使用 HTTP GET 请求将数据从用户传输到客户端,同时控制与 ESP32 GPIO 引脚连接的多个 LED。
异步 Web 服务器具有显着的优势,因为它们可以同时处理多个连接。

异步Web服务器介绍

什么是异步 Web 服务器?

异步 Web 服务器是可以以可扩展方式处理 XMLHttpRequests 请求的服务器。在传统的基于线程的服务器的情况下,每个客户端都有一个专用的单独线程来服务该特定客户端。但是当服务器等待进程释放资源时,它可能会导致阻塞问题。 
相反,异步服务器不会为每个客户端请求创建单独的线程。它有一个工作进程,接受来自所有客户端的 HTTP 请求,并通过使用事件驱动的高效循环来处理客户端请求。换句话说,在异步服务器中,客户端 HTTP 请求不会相互阻塞,而是并发执行。

异步 Web 服务器库 ESP32

在本教程中,我们将使用ESPAsyncWebServer 库通过 ESP32 和 Arduino IDE 构建异步 Web 服务器。正如这个库的官方 GitHub 页面所讨论的,使用 Async Web 服务器有很多好处:

  • 使用异步意味着服务器可以同时处理来自客户端的多个连接
  • 一旦请求准备好并被解析,您就会被调用
  • 当您发送响应时,您可以立即准备好处理其他连接,而服务器会在后台处理发送响应
  • 速度很快
  • 易于使用的 API、HTTP Basic 和 Digest MD5 身份验证(默认)、ChunkedResponse
  • 易于扩展以处理任何类型的内容
  • 支持继续 100
  • 异步 WebSocket 插件提供不同的位置,无需额外的服务器或端口
  • Async EventSource (Server-Sent Events) 插件向浏览器发送事件
  • 用于条件和永久 URL 重写的 URL 重写插件
  • 支持缓存、Last-Modified、默认索引等的 ServeStatic 插件
  • 处理模板的简单模板处理引擎

安装 ESPAsyncWebServer 库

ESPAsyncWebServer 库将帮助我们轻松创建 Web 服务器。使用这个库,我们将设置一个异步 HTTP 服务器。AsyncTCP 是我们将合并的另一个库,因为它是 ESPAsyncWebServer 库的依赖项。这个库不会直接在我们的程序代码中使用,它只是作为第一个库的基础。

这两个库在 Arduino 库管理器中都不可用,因此我们必须自己在 IDE 中下载和加载它们。我们将使用 GitHub 下载相应的库,然后将它们放在我们 Arduino IDE 的库文件夹中。
单击ESPAsyncWebServer库和AsyncTCP库以打开库的相应 GitHub 页面。
打开 ESPAsyncWeb 服务器链接时的网页将如下所示。

 

单击代码按钮,然后转到图中突出显示的下载 Zip 选项。您的 zip 文件将立即下载到您的计算机上。下载完成后,将 .zip 文件解压到 Arduino 库文件夹中。类似的过程也适用于 AsyncTCP 库的安装。确保将提取的文件相应地重命名为 ESPAsyncWebServer 和 AsyncTCP。

您也可以在 IDE 中转到Sketch > Include Library > Add .zip Library来添加库。通过这个过程,现在我们将能够使用 Arduino IDE 中的库的功能。

ESP32 异步 Web 服务器项目概述

我们将通过异步 Web 服务器控制 4 个 LED 输出。我们的目标是控制输出,因此我们将使用 LED 来简化和使用。在这个项目中,您将需要以下组件:

 

所需组件

  1. ESP32开发板
  2. 四个 5mm LED
  3. 四个 220ohm 电阻
  4. 面包板
  5. 连接电线

原理图,示意图

按照下面的示意图组装您的电路。

 

我们使用了四种不同颜色的 LED,并将它们的阳极引脚与四个不同的 GPIO 引脚连接起来。稍后,在程序代码中,我们会将这些 GPIO 引脚配置为输出引脚。阴极引脚通过 220 欧姆电阻接地。

下表显示了与每个 LED 连接的 GPIO 引脚。

LEDGPIO PIN
GreenGPIO32
RedGPIO25
YellowGPIO27
BlueGPIO13

 您可以根据需要选择任何合适的输出 GPIO 引脚。

异步 Web 服务器如何工作?

我们现在将演示网络服务器的工作。

该网页将由标题“ESP32 WEB SERVER”和一个接一个的四个切换按钮组成。这些按钮将控制与四个 LED 连接的 GPIO 输出引脚。按钮将能够来回滑动,从而产生切换效果。当滑块按钮为红色时,LED 将打开(输出打开),当滑块按钮为灰色时,LED 将关闭(输出关闭)。滑动按钮将切换输出。每个按钮将与按钮顶部指定的不同 GPIO 引脚相关联。
为了便于理解,我们将演示绿色 LED 如何通过用户输入打开/关闭。请注意,LED 与 GPIO32 连接。下图显示了切换滑动按钮的过程。

 最初,GPIO32 将关闭,如灰色按钮所示。当我们切换它时,Web 服务器将在 URL 上发出 HTTP GET 请求。HTTP 请求的格式为:/update?output=32&state=1。这表明我们想要将连接到引脚 32 的输出变为 1 状态,即高电平。因此,与相应 GPIO 连接的 LED 将打开。

同样,在第二种情况下,最初 GPIO32 为 ON,如红色按钮所示。当我们切换它时,Web 服务器将在 URL 上发出 HTTP GET 请求。HTTP 请求的格式为:/update?output=32&state=0。这表明我们想要将连接到引脚 32 的输出变为 0 状态,即低电平。

ESPAsyncWebServer Arduino 程序

打开您的 Arduino IDE 并创建一个新文件。在该文件中复制下面给出的代码。

// Importing necessary libraries
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Setting network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

const char* input_parameter1 = "output";
const char* input_parameter2 = "state";

// Creating a AsyncWebServer object 
AsyncWebServer server(80);


const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP32 WEB SERVER</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
    input:checked+.slider {background-color: #b30000}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
  </style>
</head>
<body>
  <h2>ESP32 WEB SERVER</h2>
  %BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?output="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?output="+element.id+"&state=0", true); }
  xhr.send();
}
</script>
</body>
</html>
)rawliteral";

// Replaces placeholder with button section in your web page
String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons = "";
    buttons += "<h4>Output - GPIO 32</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"32\" " + outputState(32) + "><span class=\"slider\"></span></label>";

    buttons += "<h4>Output - GPIO 25</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"25\" " + outputState(25) + "><span class=\"slider\"></span></label>";

    buttons += "<h4>Output - GPIO 27</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"27\" " + outputState(27) + "><span class=\"slider\"></span></label>";

   buttons += "<h4>Output - GPIO 13</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"13\" " + outputState(13) + "><span class=\"slider\"></span></label>";

    return buttons;
  }
  return String();
}

String outputState(int output){
  if(digitalRead(output)){
    return "checked";
  }
  else {
    return "";
  }
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

pinMode(32,OUTPUT);
digitalWrite(32, LOW);
pinMode(25, OUTPUT);
digitalWrite(25, LOW);
pinMode(27, OUTPUT);
digitalWrite(27, LOW);
pinMode(13, OUTPUT);
digitalWrite(13, LOW);

  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi");
  }

  // Print ESP Local IP Address
  Serial.println(WiFi.localIP());

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

  // Send a GET request to <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage1;
    String inputMessage2;
    // GET input1 value on <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
    if (request->hasParam(input_parameter1) && request->hasParam(input_parameter2)) {
      inputMessage1 = request->getParam(input_parameter1)->value();
      inputMessage2 = request->getParam(input_parameter2)->value();
      digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
    }
    else {
      inputMessage1 = "No message sent";
      inputMessage2 = "No message sent";
    }
    Serial.print("GPIO: ");
    Serial.print(inputMessage1);
    Serial.print(" - Set to: ");
    Serial.println(inputMessage2);
    request->send(200, "text/plain", "OK");
  });

  // Start server
  server.begin();
}

void loop() {

}

代码如何运作?

导入库

首先,我们将导入必要的库。对于这个项目,我们使用了其中的三个。WiFi.h、ESPAsyncWebServer.h 和 AsyncTCP.h。由于我们必须将 ESP32 连接到无线网络,因此我们需要 WiFi.h 库来实现此目的。另外两个库是我们最近下载的。我们将使用它们来构建我们的异步 HTTP Web 服务器。

// Importing necessary libraries
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

设置网络参数:

接下来,我们将创建两个全局变量,一个用于 SSID,另一个用于密码。这些将保存我们的网络凭据,用于连接到我们的无线网络。用您的凭据替换它们以确保连接成功。

// Setting network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

设置输入参数

我们将传递两个 char 类型的全局变量。这些将是我们将在 URL 上传递的输入参数。一个参数是我们将称为“输出”的 GPIO 编号,另一个是它的状态(1 或 0)。请注意,这两个输入都将采用数字形式。

const char* input_paramter1 = "output";
const char* input_parameter2 = "state";

创建 AsyncWebServer 对象

AsyncWebServer 对象将用于设置 ESP32 Web 服务器。我们将传递默认的 HTTP 端口 80 作为构造函数的输入。这将是服务器侦听请求的端口。

AsyncWebServer server(80);

创建网页

我们将创建 index_html 变量来存储 HTML 文本。我们将从网页的标题开始。标记将指示标题的开头,而 </tile> 标记将指示结尾。在这些标签之间,我们将指定“ESP32 WEB SERVER”,它将显示在浏览器的标题栏中。

<title>ESP32 WEB SERVER</title>

接下来,我们将创建一个元标记以确保我们的 Web 服务器可用于所有浏览器,例如智能手机、笔记本电脑、计算机等。

<meta name="viewport" content="width=device-width, initial-scale=1">

CSS样式网页

CSS 用于为网页提供样式。要在 head 标签中添加 CSS 文件,我们将使用标签来标记开始和结束。我们将显示文本设置为 Arial 字体类型并将其对齐在网页的中心。h2 和 p 指示的标题和第一段的字体大小也将被设置。接下来,当我们在我们的项目中使用两种不同颜色的滑动开关时,我们将合并它们的字体大小、颜色和位置。

<style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
    input:checked+.slider {background-color: #b30000}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
</style>

HTML 网页正文

下一步是定义 HTML 网页正文。这将进入标记脚本开始和结束的标签内。这部分将包括网页的标题和按钮。我们将在标签中包含我们网页的标题,它将与 Web 浏览器标题相同,即 ESP32 WEB SERVER。

<h2>ESP32 WEB SERVER</h2>

创建滑动按钮

然后,我们将定义网页上的按钮。我们有两种颜色的按钮,当 GPIO 状态为 1 时为红色,当 GPIO 状态为 0 时为灰色。因此我们将使用占位符来监控正确的 GPIO 状态。%BUTTONPLACEHOLDER% 将用作占位符,这将帮助我们创建按钮。
我们将使用 JavaScript 创建一个函数,该函数通过 if else 语句检查滑动按钮的正确切换功能。每当在 if 语句中滑动按钮时,它将创建 HTTP GET 请求。element.id 将对应于与按钮关联的 GPIO 引脚号。

<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?output="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?output="+element.id+"&state=0", true); }
  xhr.send();
}
</script>

processor()函数

在 processor() 函数中,我们将替换占位符 (%BUTTONPLACEHOLDER%) 并构建按钮。只要访问网页并且在 HTML 脚本中找到占位符,就会发生这种情况。如您所见,我们将构建四个按钮,但您可以通过添加/删除按钮部分轻松增加/减少按钮数量。按钮变量将是字符串类型,我们将首先传递一个空字符串。然后,我们将根据按钮的当前输出状态(1 或 0)连接按钮的 HTML 文本,并相应地构建它(红色或灰色)。

String processor(const String& var)
{
  
  if(var == "BUTTONPLACEHOLDER")
{
    String buttons = "";
    buttons += "<h4>Output - GPIO 32</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"32\" " + outputState(32) + "><span class=\"slider\"></span></label>";
    buttons += "<h4>Output - GPIO 25</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"25\" " + outputState(25) + "><span class=\"slider\"></span></label>";
    buttons += "<h4>Output - GPIO 27</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"27\" " + outputState(27) + "><span class=\"slider\"></span></label>";
   buttons += "<h4>Output - GPIO 13</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"13\" " + outputState(13) + "><span class=\"slider\"></span></label>";

    return buttons;
  }
  return String();
}

outputState()函数

我们还将定义另一个名为“outputState”的字符串类型函数。这会将按钮的输出作为参数,并通过 if-else 语句检查 GPIO 是否打开。这将由 digitalRead() 函数完成。如果 GPIO 确实打开了,那么它将返回字符串“checked”,否则将返回一个空字符串“”。拨动开关将充当输入类型,我们将其指定为“复选框”。每当复选框的值发生变化时,就会发生“onchange”。结果将调用 toggleCheckbox() 函数,该函数将访问与每个 GPIO 引脚关联的唯一 ID。

String outputState(int output){
  if(digitalRead(output)){
    return "checked";
  }
  else {
    return "";
  }
}

setup()函数

在 setup() 函数中,我们将以 115200 的波特率打开一个串行连接。通过使用 pinMode() 函数,GPIO 引脚将作为函数内部的参数传递,该函数将被配置为输出引脚。我们将在启动时将所有引脚初始化为低电平状态,这意味着所有 LED 都将熄灭。

Serial.begin(115200);

pinMode(32,OUTPUT);
digitalWrite(32, LOW);
pinMode(25, OUTPUT);
digitalWrite(25, LOW);
pinMode(27, OUTPUT);
digitalWrite(27, LOW);
pinMode(13, OUTPUT);
digitalWrite(13, LOW);

配置WIFI

以下代码部分将我们的 ESP32 板连接到我们在上面已经指定其网络凭据的本地网络。建立连接后,ESP32 板的 IP 地址将打印在串行监视器上。这将帮助我们向服务器发出请求。

WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting to WiFi");
}

Serial.println(WiFi.localIP());

处理请求(ESP32 端)

下一步将处理 ESP32 模块收到的请求。我们将检查我们已经在代码中指定的请求中的两个参数:输出和状态。这些保存在变量中:“input_parameter1”和“input_parameter2”。这些值将保存在名为 inputMessage1 的新变量中,该变量将包含 GPIO 引脚,而 inputMessage2 将包含 GPIO 引脚的状态。

if (request->hasParam(input_parameter1) && request->hasParam(input_parameter2)) {
  inputMessage1 = request->getParam(input_parameter1)->value();
  inputMessage2 = request->getParam(input_parameter2)->value();
digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());

然后这些将打印在串行监视器上。此外,send() 方法将用于返回 HTTP 响应。它接受三个参数。第一个参数是我们将指定为 200 的响应代码。它是 ok 的 HTTP 响应代码。第二个参数是响应的内容类型,我们将指定为“text/plain”,第三个参数是我们将作为 HTTP 响应发送的实际消息。这被设置为“OK”。箭头运算符将用于调用 AsyncWebServerRequest 对象的发送方法。

  Serial.print("GPIO: ");
  Serial.print(inputMessage1);
  Serial.print(" - Set to: ");
  Serial.println(inputMessage2);
  request->send(200, "text/plain", "OK");

启动连接

要启动服务器,我们将在服务器对象上调用 begin()。

server.begin();

loop()函数

我们的循环函数是空的,因为我们正在构建一个异步服务器并且它使用事件驱动的方法。因此,我们不需要在其中调用任何处理函数。

void loop() {

}

示范

将代码上传到 ESP32 开发板后,按下其RESET按钮。

 在您的 Arduino IDE 中,打开串行监视器,您将能够看到 ESP32 模块的 IP 地址。

 将该地址复制到网络浏览器中,然后按 Enter。Web 服务器将如下所示:

 现在您可以滑动按钮并切换连接到各种 LED 的输出。您将能够通过这些按钮控制所有四个 LED。同样,串行监视器还将打印您将更改的 GPIO 引脚的当前状态。该网页将根据显示的按钮颜色包含所有更新的 GPIO 状态。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐