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

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

ESP8266 NodeMCU асинхронный веб-сервер управление выходами с Arduino IDE библиотека ESPAsyncWebServer

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

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

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

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

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

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

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

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

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

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

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

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

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

ESP8266 NodeMCU схема подключения трёх светодиодов для асинхронного веб-сервера

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

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

Вы можете установить эти библиотеки через менеджер библиотек Arduino. Перейдите в Sketch > Include Library > Manage Libraries и найдите библиотеки по их названиям.

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

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

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

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

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

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

ESP8266 NodeMCU обзор проекта асинхронного веб-сервера библиотека ESPAsyncWebServer

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

1. В первом сценарии вы переключаете кнопку, чтобы включить GPIO 2. Когда это происходит, выполняется HTTP GET запрос на URL /update?output= 2 &state= 1. На основании этого URL мы изменяем состояние 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/esp8266-nodemcu-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 <ESP8266WiFi.h>
#include <ESPAsyncTCP.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 5</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"5\" " + outputState(5) + "><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 2</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"2\" " + outputState(2) + "><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(5, OUTPUT);
  digitalWrite(5, LOW);
  pinMode(4, OUTPUT);
  digitalWrite(4, LOW);
  pinMode(2, OUTPUT);
  digitalWrite(2, 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 и ESPAsyncTCP.

#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

Настройка сетевых учётных данных

Введите ваши сетевые учётные данные в следующие переменные, чтобы ESP8266 мог подключиться к вашей локальной сети.

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

Помните, что ESP8266 получает запросы вида: /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">

Следующая строка предотвращает запросы на favicon. В данном случае у нас нет favicon. Favicon – это значок сайта, который отображается рядом с заголовком на вкладке веб-браузера. Если мы не добавим следующую строку, ESP будет получать запрос на 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.

Когда вы обращаетесь к странице веб-сервера, вы хотите, чтобы она показывала правильные текущие состояния 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 5 кнопка >> element.id = 5

  • GPIO 4 кнопка >> element.id = 4

  • GPIO 2 кнопка >> element.id = 2

Процессор

Теперь нам нужно создать функцию processor(), которая заменяет заполнители в HTML-тексте на то, что мы определим.

Когда запрашивается веб-страница, проверяется, есть ли в HTML какие-либо заполнители. Если находится заполнитель %BUTTONPLACEHOLDER%, возвращается HTML-текст для создания кнопок.

String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons = "";
    buttons += "<h4>Output - GPIO 5</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"5\" " + outputState(5) + "><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 2</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"2\" " + outputState(2) + "><span class=\"slider\"></span></label>";
    return buttons;
  }
  return String();
}

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

Давайте посмотрим, как создаются кнопки. Мы создаём переменную 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 type). Тег <input> определяет поле ввода, куда пользователь может вводить данные. Переключатель – это поле ввода типа checkbox. Существует множество других типов полей ввода.

<input type="checkbox">

Чекбокс может быть отмечен или не отмечен. Когда он отмечен, вы получаете что-то вроде этого:

<input type="checkbox" checked>

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

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

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

setup()

В функции setup() инициализируйте Serial Monitor для целей отладки.

Serial.begin(115200);

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

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

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

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() вам нужно обработать, что происходит, когда ESP8266 получает запросы. Как мы видели ранее, вы получаете запрос такого вида:

<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();

Демонстрация

После загрузки кода на ваш ESP8266 откройте Serial Monitor на скорости 115200 бод. Нажмите встроенную кнопку RST/EN. Вы должны получить его IP-адрес.

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

ESP8266 NodeMCU асинхронный веб-сервер кнопки-переключатели в веб-браузере

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

ESP8266 NodeMCU асинхронный веб-сервер Arduino IDE Serial Monitor

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

ESP8266 NodeMCU асинхронный веб-сервер адаптивный мобильный интерфейс

Заключение

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

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

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

Если вам нравится ESP8266, вы можете рассмотреть возможность приобретения нашей электронной книги «Home Automation using ESP8266». Вы также можете получить доступ к нашим бесплатным ресурсам по ESP8266 здесь.

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