ESP32 и асинхронный веб-сервер на библиотеке ESPAsyncWebServer

Перевод статьи ESP32 Async Web Server using ESPAsyncWebServer library — Rui Santos & Sara Santos, Random Nerd Tutorials.
В этом руководстве соберём на ESP32 асинхронный веб-сервер с помощью библиотеки ESPAsyncWebServer и научимся управлять выводами GPIO через веб-страницу. На примере трёх светодиодов сделаем страницу с переключателями: щёлкаешь тумблер в браузере — соответствующий выход ESP32 включается или выключается.
Асинхронный сервер удобен тем, что умеет обрабатывать сразу несколько подключений и не блокирует основной цикл программы: пока сервер ждёт запросы, ESP32 свободна для других задач.
Что понадобится#
- плата ESP32 (о выборе и настройке — ниже);
- 3 светодиода;
- 3 резистора на 220 Ом;
- макетная плата;
- соединительные провода.
Предварительная настройка#
Чтобы прошивать ESP32 из Arduino IDE, в ней должна быть установлена поддержка плат ESP32:
Схема подключения#
Подключите три светодиода к выводам GPIO 2, GPIO 4 и GPIO 33 через токоограничивающие резисторы на 220 Ом. При желании можно взять другие выводы — не забудьте поправить их номера в коде.

Три светодиода на GPIO 2, 4 и 33, каждый через резистор 220 Ом.
Установка библиотек#
Для асинхронного сервера нужны две библиотеки. Установите их через Менеджер библиотек Arduino IDE:
- ESPAsyncWebServer от ESP32Async;
- AsyncTCP от ESP32Async.
Код#
Ниже — полный скетч. Не забудьте вписать имя и пароль своей Wi-Fi-сети в переменные ssid и password.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp32-async-web-server-espasyncwebserver-library/
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*********/
// 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(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() {
}
Как это работает#
На веб-странице отображаются три переключателя — по одному на каждый выход. Логика простая:
- при включении тумблера браузер отправляет HTTP-запрос
GETна/update?output=X&state=1— светодиод загорается, а сам тумблер становится красным; - при выключении уходит запрос
/update?output=X&state=0— светодиод гаснет, тумблер сереет.
ESP32 принимает эти запросы и переключает соответствующий вывод GPIO.
Разбор кода#
Страница в index_html. Вся HTML-страница со стилями и скриптом хранится в программной памяти (PROGMEM) как «сырой» строковый литерал. Вместо готового списка кнопок в ней стоит метка-заполнитель %BUTTONPLACEHOLDER%.
Функция processor(). Перед отправкой страницы браузеру ESP32 подставляет вместо %BUTTONPLACEHOLDER% сгенерированный HTML с тремя переключателями. Для каждого выхода вызывается outputState(), чтобы тумблер сразу отражал текущее состояние вывода.
Функция outputState(). Возвращает строку checked, если вывод в состоянии HIGH, и пустую строку, если LOW. Именно от неё зависит, будет ли тумблер при загрузке страницы включён или выключен.
JavaScript toggleCheckbox(). Когда вы щёлкаете тумблер, скрипт через XMLHttpRequest отправляет на ESP32 асинхронный запрос с номером вывода (id элемента) и нужным состоянием.
Обработчик /update. Достаёт из запроса параметры output и state и применяет их: digitalWrite(inputMessage1.toInt(), inputMessage2.toInt()). Заодно номер вывода и новое состояние выводятся в монитор порта.
Обработчик /. Отдаёт главную страницу, пропуская её через processor — так метка-заполнитель заменяется на актуальные переключатели.
Результат#
Впишите в код данные своей Wi-Fi-сети и загрузите скетч в ESP32. Откройте монитор порта на скорости 115200 бод и нажмите кнопку RST — плата подключится к сети и выведет свой IP-адрес.
Откройте этот адрес в браузере на телефоне или компьютере, подключённом к той же сети. Появится страница «ESP Web Server» с тремя переключателями — теперь можно управлять светодиодами прямо из браузера.
Что дальше#
Этот пример легко расширить: добавить больше выходов, поменять номера GPIO под свою схему или управлять вместо светодиодов реле, чтобы включать и выключать реальную нагрузку.