在本教程中,您将学习如何使用 ESP32 开发板构建异步 Web 服务器来控制其输出。该板将使用 Arduino IDE 进行编程,我们将使用 ESPAsyncWebServer 库。
目录
异步网络服务器
为了构建 Web 服务器,我们将使用 ESPAsyncWebServer 库 ,它提供了一种构建异步 Web 服务器的简单方法。如库 GitHub 页面中所述,构建异步 Web 服务器有几个优点,例如:
- “同时处理多个连接”;
- “当您发送响应时,您可以立即准备好处理其它连接,而服务器正在后台处理发送响应”;
- “处理模板的简单模板处理引擎”;
- 以及更多。
所需零件
在本教程中,我们将控制三个输出。例如,我们将控制 LED。因此,您需要以下部分:
- ESP32(阅读最佳 ESP32 开发板)
- 3x LED
- 3x 220 欧姆电阻
- 面包板
- 连接线
示意图
在继续执行代码之前,将 3 个 LED 连接到 ESP32。我们将 LED 连接到 GPIO 2、4 和 33,但您可以使用任何其它 GPIO(阅读ESP32 GPIO 参考指南)。
安装库 – ESP Async Web 服务器
要构建 Web 服务器,您需要安装以下库。单击下面的链接下载库。
这些库无法通过Arduino库管理器安装,因此您需要将库文件复制到Arduino安装库文件夹。或者,在Arduino IDE中,您可以转到Sketch>Include Library>Add.zip Library并选择刚刚下载的库。
项目概述
为了更好地理解代码,我们来看看Web服务器是如何工作的。
- web服务器包含一个标题“ESP web服务器”和三个按钮(切换开关),用于控制三个输出。每个滑块按钮都有一个指示GPIO输出引脚的标签。您可以轻松删除/添加更多输出。
- 滑块为红色时,表示输出为开(其状态为高)。如果切换滑块,它将关闭输出(将状态更改为低)。
- 当滑块为灰色时,表示输出关闭(其状态为 LOW)。如果切换滑块,它会打开输出(将状态更改为 HIGH)。
这个怎么运作?
让我们看看切换按钮时会发生什么。我们将看到 GPIO 2 的示例。其它按钮的工作方式类似。
1.在第一种情况下,您切换按钮以打开 GPIO 2。发生这种情况时,浏览器会在/update?output=2&state=1。基于该 URL,ESP 将 GPIO 2 的状态更改为 1 ( HIGH ) 并打开 LED。
2.在第二个示例中,您切换按钮以关闭 GPIO 2。发生这种情况时,浏览器会在/update?output=2&state=0。基于该 URL,我们将 GPIO 2 的状态更改为 0 ( LOW ) 并关闭 LED。
ESP 异步 Web 服务器的代码
将以下代码复制到您的 Arduino IDE。
// Import required libraries #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; const char* PARAM_INPUT_1 = "output"; const char* PARAM_INPUT_2 = "state"; // Create AsyncWebServer object on port 80 AsyncWebServer server(80); const char index_html[] PROGMEM = R"rawliteral( <!DOCTYPE HTML><html> <head> <title>ESP 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;} h2 {font-size: 3.0rem;} 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>ESP 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 2</h4><label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="2" " + outputState(2) + "><span class="slider"></span></label>"; buttons += "<h4>Output - GPIO 4</h4><label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="4" " + outputState(4) + "><span class="slider"></span></label>"; buttons += "<h4>Output - GPIO 33</h4><label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="33" " + outputState(33) + "><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(2, OUTPUT); digitalWrite(2, LOW); pinMode(4, OUTPUT); digitalWrite(4, LOW); pinMode(33, OUTPUT); digitalWrite(33, 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(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) { inputMessage1 = request->getParam(PARAM_INPUT_1)->value(); inputMessage2 = request->getParam(PARAM_INPUT_2)->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() { }
代码如何运作
在本节中,我们将解释代码是如何工作的。
导入库
首先,导入所需的库。您需要包括无线上网,ESPAsync网络服务器和异步TCP库。
#include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h>
设置您的网络凭据
在以下变量中插入您的网络凭据,以便 ESP32 可以连接到您的本地网络。
const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";
输入参数
为了检查 URL 上传递的参数(GPIO 编号及其状态),我们创建了两个变量,一个用于输出,另一个用于状态。
const char* PARAM_INPUT_1 = "output"; const char* PARAM_INPUT_2 = "state";
请记住,ESP32 接收如下请求:/update?output=2&state=0
AsyncWebServer 对象
创建一个异步网络服务器端口 80 上的对象。
AsyncWebServer server(80);
构建网页
所有带有样式和 JavaScript 的 HTML 文本都存储在 index_html variable。现在我们将浏览 HTML 文本并查看每个部分的作用。
标题位于<title>和</tile>标签内。标题正是它听起来的样子:文档的标题,显示在 Web 浏览器的标题栏中。在这种情况下,它是“ESP Web 服务器”。
<title>ESP Web Server</title>
以下<meta>标记使您的网页在任何浏览器(笔记本电脑、平板电脑或智能手机)中都能响应。
<meta name="viewport" content="width=device-width, initial-scale=1">
下一行阻止对 favicon 的请求。在这种情况下,我们没有网站图标。favicon 是显示在 Web 浏览器选项卡中标题旁边的网站图标。如果我们不添加以下行,ESP32 将在我们每次访问 Web 服务器时收到对 favicon 的请求。
<link rel="icon" href="data:,">
在<style></style> 标签之间,我们添加了一些 CSS 来设置网页的样式。我们不会详细介绍这种 CSS 样式是如何工作的。
<style> html {font-family: Arial; display: inline-block; text-align: center;} h2 {font-size: 3.0rem;} 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 正文
<body></body>标签内是我们添加网页内容的地方。
< h2></h2> 标签将标题添加到网页。在本例中为“ESP Web Server”文本,但您可以添加任何其它文本。
<h2>ESP Web Server</h2>
在标题之后,我们有按钮。按钮在网页上的显示方式(红色:如果 GPIO 开启;或灰色:如果 GPIO 关闭)取决于当前的 GPIO 状态。
当您访问 Web 服务器页面时,您希望它显示正确的当前 GPIO 状态。因此,我们将添加一个占位符,而不是添加 HTML 文本来构建按钮%按钮占位符%. 然后,当加载网页时,该占位符将被实际的 HTML 文本替换,以构建具有正确状态的按钮。
%BUTTONPLACEHOLDER%
JavaScript
然后,正如我们之前解释的那样,当您切换按钮时,有一些 JavaScript 负责发出HTTP GET 请求。
<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>
这是发出请求的行:
if(element.checked){ xhr.open("GET", "/update?output="+element.id+"&state=1", true); }
元素.id返回 HTML 元素的 id。每个按钮的 id 将是 GPIO 控制的,我们将在下一节中看到:
- GPIO 2 按钮 » element.id = 2
- GPIO 4 按钮 » element.id = 4
- GPIO 33 按钮 » element.id = 33
processor
现在,我们需要创建 processor() 函数,它将 HTML 文本中的占位符替换为我们定义的内容。
当请求网页时,检查 HTML 是否有任何占位符。如果它发现%BUTTONPLACEHOLDER%占位符,它返回 HTML 文本以创建按钮。
String processor(const String& var){ //Serial.println(var); if(var == "BUTTONPLACEHOLDER"){ String buttons = ""; buttons += "<h4>Output - GPIO 2</h4><label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="2" " + outputState(2) + "><span class="slider"></span></label>"; buttons += "<h4>Output - GPIO 4</h4><label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="4" " + outputState(4) + "><span class="slider"></span></label>"; buttons += "<h4>Output - GPIO 33</h4><label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="33" " + outputState(33) + "><span class="slider"></span></label>"; return buttons; } return String(); }
您可以轻松删除或添加更多行以创建更多按钮。
让我们看看按钮是如何创建的。我们创建一个名为的 String 变量纽扣包含用于构建按钮的 HTML 文本。我们将 HTML 文本与当前输出状态连接起来,以便切换按钮为灰色或红色。当前的输出状态由返回outputState(<GPIO>)函数(它接受 GPIO 编号作为参数)。见下文:
buttons += "<h4>Output - GPIO 2</h4><label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="2" " + outputState(2) + "><span class="slider"></span></label>";
使用 以便我们可以在字符串中传递“”。
这outputState()函数返回“检查”如果 GPIO 处于打开状态或为空字段“”如果 GPIO 关闭。
String outputState(int output){ if(digitalRead(output)){ return "checked"; } else { return ""; } }
因此,GPIO 2 打开时的 HTML 文本将是:
<h4>Output - GPIO 2</h4> <label class="switch"> <input type="checkbox" onchange="toggleCheckbox(this)" id="2" checked><span class="slider"></span> </label>
让我们把它分解成更小的部分来了解它是如何工作的。
在 HTML 中,切换开关是一种输入类型。<input>标签指定用户可以输入数据的输入字段。拨动开关是类型的输入字段复选框. 还有许多其它输入字段类型。
<input type="checkbox">
该复选框可以选中或不选中。检查时,您有以下内容:
<input type="checkbox" checked>
这改变是当我们更改元素(复选框)的值时发生的事件属性。每当您选中或取消选中切换开关时,它都会调用切换复选框()该特定元素 ID 的 JavaScript 函数 (这个)。
这ID指定该 HTML 元素的唯一 ID。id 允许我们使用 JavaScript 或 CSS 操作元素。
<input type="checkbox" onchange="toggleCheckbox(this)" id="2" checked>
setup()
在里面 setup() 初始化串行监视器以进行调试。
Serial.begin(115200);
使用 pinMode() 函数将要控制的 GPIO 设置为输出,并在 ESP32 首次启动时将其设置为 LOW。如果您添加了更多 GPIO,请执行相同的步骤。
pinMode(2, OUTPUT); digitalWrite(2, LOW); pinMode(4, OUTPUT); digitalWrite(4, LOW); pinMode(33, OUTPUT); digitalWrite(33, LOW);
连接到本地网络并打印 ESP32 IP 地址。
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());
在setup() 里面,您需要处理 ESP32 收到请求时发生的情况。正如我们之前看到的,您会收到这种类型的请求:
<ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
因此,我们检查请求是否包含PARAM_INPUT1变量值 (output) 和PARAM_INPUT2(state) 并将相应的值保存在输入1消息和输入2消息变量。
if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) { inputMessage1 = request->getParam(PARAM_INPUT_1)->value(); inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
然后,我们用相应的状态控制相应的GPIO(输入消息1变量保存 GPIO 编号和输入消息2保存状态 – 0 或 1)
digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
以下是处理 HTTP GET /update请求的完整代码:
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(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) { inputMessage1 = request->getParam(PARAM_INPUT_1)->value(); inputMessage2 = request->getParam(PARAM_INPUT_2)->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"); });
最后,启动服务器:
server.begin();
示范
将代码上传到 ESP32 后,以 115200 的波特率打开串行监视器。按下板载 RST/EN 按钮。你应该得到它的IP地址。
打开浏览器并输入 ESP IP 地址。您将可以访问类似的网页。
按下切换按钮来控制 ESP32 GPIO。同时,您应该在串行监视器中获得以下消息,以帮助您调试代码。
您还可以从智能手机中的浏览器访问 Web 服务器。每当您打开 Web 服务器时,它都会显示当前的 GPIO 状态。红色表示 GPIO 开启,灰色表示 GPIO 关闭。
总结
在本教程中,您学习了如何使用 ESP32 创建异步 Web 服务器,以使用拨动开关控制其输出。每当您打开网页时,它都会显示更新后的 GPIO 状态。
非常好的文章,非常详细!