ESP32 асинхронный веб-сервер – управление выходами с Arduino IDE (библиотека ESPAsyncWebServer)

В этом руководстве вы узнаете, как создать асинхронный веб-сервер на плате ESP32 для управления её выходами. Плата будет запрограммирована с помощью Arduino IDE, и мы будем использовать библиотеку ESPAsyncWebServer.

ESP32 асинхронный веб-сервер управление выходами с Arduino IDE

Вам также может понравиться: ESP8266 NodeMCU Async Web Server – управление выходами с Arduino IDE (библиотека ESPAsyncWebServer)

Асинхронный веб-сервер

Для создания веб-сервера мы будем использовать библиотеку ESPAsyncWebServer, которая предоставляет простой способ создания асинхронного веб-сервера. Создание асинхронного веб-сервера имеет ряд преимуществ, упомянутых на странице библиотеки в GitHub, таких как:

  • «Обработка более одного соединения одновременно»;

  • «Когда вы отправляете ответ, вы сразу же готовы обрабатывать другие соединения, пока сервер заботится об отправке ответа в фоновом режиме»;

  • «Простой движок обработки шаблонов для работы с шаблонами»;

  • И многое другое.

Ознакомьтесь с документацией библиотеки на её странице в GitHub.

Необходимые компоненты

В этом руководстве мы будем управлять тремя выходами. В качестве примера мы будем управлять светодиодами. Итак, вам потребуются следующие компоненты:

Вы можете воспользоваться приведёнными ссылками или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!

MakerAdvisor -- компоненты для проектов

Схема подключения

Перед тем как перейти к коду, подключите 3 светодиода к ESP32. Мы подключаем светодиоды к GPIO 2, 4 и 33, но вы можете использовать любые другие GPIO (читайте Справочник по GPIO ESP32).

ESP32 схема подключения трёх светодиодов

Установка библиотек – ESP Async Web Server

Для создания веб-сервера мы будем использовать следующие библиотеки:

Вы можете установить эти библиотеки через менеджер библиотек Arduino. Откройте менеджер библиотек, нажав на значок библиотеки на левой боковой панели.

Найдите ESPAsyncWebServer и установите ESPAsyncWebServer от ESP32Async.

Установка ESPAsyncWebServer ESP32 Arduino IDE

Затем установите библиотеку AsyncTCP. Найдите AsyncTCP и установите AsyncTCP от ESP32Async.

Установка AsyncTCP ESP32 Arduino IDE

Обзор проекта

Чтобы лучше понять код, давайте посмотрим, как работает веб-сервер.

ESP32 асинхронный веб-сервер управление выходами -- мобильная адаптивная веб-страница
  • Веб-сервер содержит один заголовок «ESP Web Server» и три кнопки (переключатели) для управления тремя выходами. Каждая кнопка-ползунок имеет подпись, указывающую на выходной пин GPIO. Вы можете легко удалить или добавить больше выходов.

  • Когда ползунок красный, это означает, что выход включён (его состояние HIGH). Если вы переключите ползунок, выход выключится (состояние изменится на LOW).

  • Когда ползунок серый, это означает, что выход выключен (его состояние LOW). Если вы переключите ползунок, выход включится (состояние изменится на HIGH).

Как это работает?

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

Давайте посмотрим, что происходит при переключении кнопок. Мы рассмотрим пример для GPIO 2. Для остальных кнопок всё работает аналогично.

1. В первом сценарии вы переключаете кнопку, чтобы включить GPIO 2. Когда это происходит, браузер выполняет HTTP GET запрос по URL /update?output= 2 &state= 1. На основе этого URL ESP изменяет состояние GPIO 2 на 1 ( HIGH) и включает светодиод.

2. Во втором примере вы переключаете кнопку, чтобы выключить GPIO 2. Когда это происходит, браузер выполняет HTTP GET запрос по URL /update?output= 2 &state= 0. На основе этого URL мы изменяем состояние GPIO 2 на 0 ( LOW) и выключаем светодиод.

Код для ESP Async Web Server

Скопируйте следующий код в вашу Arduino IDE.

/*********
  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() {

}

Просмотреть исходный код

Как работает код

В этом разделе мы объясним, как работает код. Продолжайте читать, если хотите узнать больше, или перейдите к разделу «Демонстрация», чтобы увидеть конечный результат.

Импорт библиотек

Сначала импортируйте необходимые библиотеки. Вам нужно подключить библиотеки WiFi, ESPAsyncWebserver и AsyncTCP.

#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

Создайте объект AsyncWebServer на порту 80.

AsyncWebServer server(80);

Создание веб-страницы

Весь HTML-текст со стилями и JavaScript хранится в переменной index_html. Теперь мы рассмотрим HTML-текст и увидим, что делает каждая часть.

Заголовок находится внутри тегов <title> и </title>. Заголовок – это именно то, что звучит: заголовок вашего документа, который отображается в строке заголовка вашего веб-браузера. В данном случае это «ESP Web Server».

<title>ESP Web Server</title>
ESP32 асинхронный веб-сервер -- заголовок веб-страницы HTML

Следующий тег <meta> делает вашу веб-страницу адаптивной в любом браузере (ноутбук, планшет или смартфон).

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

Следующая строка предотвращает запросы фавикона. В данном случае у нас нет фавикона. Фавикон – это значок сайта, который отображается рядом с заголовком во вкладке веб-браузера. Если мы не добавим следующую строку, ESP32 будет получать запрос на фавикон каждый раз, когда мы обращаемся к веб-серверу.

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

Когда вы обращаетесь к веб-серверу, вы хотите, чтобы он отображал правильные текущие состояния GPIO. Поэтому вместо добавления HTML-текста для создания кнопок мы добавим заполнитель %BUTTONPLACEHOLDER%. Этот заполнитель затем будет заменён фактическим 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); }

element.id возвращает id HTML-элемента. 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();
}

Вы можете легко удалить или добавить больше строк для создания дополнительных кнопок.

Давайте рассмотрим, как создаются кнопки. Мы создаём строковую переменную buttons, которая содержит 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>";

Символ \ используется для того, чтобы передать «» внутри строки (String).

Функция outputState() возвращает либо «checked», если GPIO включён, либо пустое поле «», если GPIO выключен.

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

Таким образом, HTML-текст для GPIO 2, когда он включён, будет выглядеть так:

<h4>Output - GPIO 2</h4>
<label class="switch">
<input type="checkbox" onchange="toggleCheckbox(this)" id="2" checked><span class="slider"></span>
</label>

Давайте разберём это на более мелкие части, чтобы понять, как это работает.

В HTML переключатель (toggle switch) – это тип ввода (input). Тег <input> определяет поле ввода, в которое пользователь может вводить данные. Переключатель – это поле ввода типа checkbox. Существует множество других типов полей ввода.

<input type="checkbox">

Чекбокс может быть отмечен или нет. Когда он отмечен, код выглядит следующим образом:

<input type="checkbox" checked>

Атрибут onchange – это атрибут события, который срабатывает при изменении значения элемента (чекбокса). Каждый раз, когда вы отмечаете или снимаете отметку с переключателя, вызывается JavaScript-функция toggleCheckbox() для этого конкретного элемента (this).

Атрибут id задаёт уникальный идентификатор для HTML-элемента. id позволяет нам манипулировать элементом с помощью JavaScript или CSS.

<input type="checkbox" onchange="toggleCheckbox(this)" id="2" checked>

setup()

В функции setup() инициализируйте монитор последовательного порта для целей отладки.

Serial.begin(115200);

Установите GPIO, которыми вы хотите управлять, как выходы с помощью функции pinMode() и установите их в LOW при первом запуске ESP32. Если вы добавили больше GPIO, выполните ту же процедуру.

pinMode(2, OUTPUT);
digitalWrite(2, LOW);
pinMode(4, OUTPUT);
digitalWrite(4, LOW);
pinMode(33, OUTPUT);
digitalWrite(33, LOW);

Подключитесь к вашей локальной сети и выведите IP-адрес ESP32.

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), и сохраняем соответствующие значения в переменных input1Message и input2Message.

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 с соответствующим состоянием (переменная inputMessage1 сохраняет номер GPIO, а inputMessage2 сохраняет состояние – 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-адрес.

Откройте браузер и введите IP-адрес ESP. Вы получите доступ к подобной веб-странице.

ESP32 асинхронный веб-сервер управление выходами -- демонстрация в веб-браузере

Нажимайте кнопки-переключатели для управления GPIO ESP32. Одновременно вы должны видеть следующие сообщения в мониторе последовательного порта, которые помогут вам отладить код.

ESP32 асинхронный веб-сервер -- демонстрация с монитором последовательного порта

Вы также можете получить доступ к веб-серверу из браузера на вашем смартфоне. Каждый раз, когда вы открываете веб-сервер, он отображает текущие состояния GPIO. Красный цвет означает, что GPIO включён, а серый – что GPIO выключен.

ESP32 веб-сервер -- адаптивная мобильная версия

Заключение

В этом руководстве вы научились создавать асинхронный веб-сервер на ESP32 для управления его выходами с помощью переключателей. Каждый раз, когда вы открываете веб-страницу, она отображает обновлённые состояния GPIO.

У нас есть другие примеры веб-серверов с использованием библиотеки ESPAsyncWebServer, которые могут вам понравиться:

Мы надеемся, что это руководство было полезным. Если у вас есть вопросы, оставьте комментарий ниже, и мы постараемся ответить вам.

Если вам нравится ESP32, вы можете рассмотреть возможность записи на наш курс «Learn ESP32 with Arduino IDE». Вы также можете получить доступ к нашим бесплатным ресурсам по ESP32 здесь.

Спасибо за чтение.


Источник: ESP32 Async Web Server – Control Outputs with Arduino IDE (ESPAsyncWebServer library)