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

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

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

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

  • обрабатывать более одного подключения одновременно;
  • после отправки ответа, вы сразу же готовы обрабатывать другие соединения, в то время как сервер заботится об отправке ответа в фоновом режиме;
  • простой механизм обработки шаблонов;
  • и многое другое…

Взгляните на  документацию библиотеки на ее странице GitHub.

Необходимые детали

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

  1. ESP8266 – 1 шт.
  2. Светодиод – 3 шт.
  3. Резистор 220 Ом – 3 шт.
  4. Макетная плата – 1 шт.
  5. Перемычки.

Схема

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

Установка библиотек

Для создания веб-сервера нам необходимо установить следующие библиотеки (щелкните ссылки ниже, чтобы загрузить эти библиотеки):

Эти библиотеки недоступны для установки посредством диспетчера библиотек Arduino, поэтому вам необходимо скопировать файлы библиотеки в папку библиотек установки Arduino. В качестве альтернативы в Arduino IDE вы можете перейти в  Эскиз > Include Library > Add.zip Library  и выбрать библиотеки, которые вы только что скачали.

Асинхронный веб-сервер ESP  — обзор проекта

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

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

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

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

Посмотрим, что происходит, когда вы переключаете кнопки. Мы рассмотрим на примере GPIO 2. Это все работает аналогично и для других кнопок.

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

Скетч

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

// Подключаем необходимые библиотеки
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Замените учетными данными своей сети
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";

// Создаем объект AsyncWebServer на порту 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";

// PLACEHOLDER выбранной кнопкой на вашей веб-странице
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.begin(115200);
pinMode(5, OUTPUT);
digitalWrite(5, LOW);
pinMode(4, OUTPUT);
digitalWrite(4, LOW);
pinMode(2, OUTPUT);
digitalWrite(2, LOW);

// Соединяемся по Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}

// Распечатать локальный IP-адрес ESP
Serial.println(WiFi.localIP());

// Маршрут для корневой / веб-страницы
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});

// Отправить GET запрос в <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage1;
String inputMessage2;

// Получить запрос GET значения input1 от <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();
}

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> и </tile>. Его название отображается в строке заголовка вашего веб-браузера. В данном случае это «ESP Web Server»:

<title>ESP Web Server</title>

Следующий тег <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 возвращает идентификатор HTML-элемента. Идентификатор каждой кнопки будет управлять GPIO, как мы увидим в следующем разделе:

  • GPIO 5 button » element.id = 5
  • GPIO 4 button » element.id = 4
  • GPIO 2 button » 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();
}

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

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

Функция 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 переключатель — это тип ввода. Тег <input> определяет поле ввода, в которое пользователь может вводить данные. Переключатель представляет собой поле ввода типа checkbox. Существует множество других типов полей ввода:

<input type="checkbox">

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

<input type="checkbox" checked>

Onchange — это атрибут события, который возникает, когда мы меняем значение элемента (checkbox). Всякий раз, когда вы устанавливаете или снимаете флажок переключателя, он вызывает функцию JavaScript toggleCheckbox() для этого конкретного идентификатора элемента (this).

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

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

setup()

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

Serial.begin(115200);

Установите GPIO, которыми вы хотите управлять, в качестве выхода с помощью функции pinMode() и установите их на низкий уровень при первом запуске 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..");
}
// Распечатать локальный IP адрес
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. Вы получите доступ к веб-странице:

Нажмите кнопки переключения для управления GPIO ESP. В процессе этого вы должны получить следующие сообщения в последовательном мониторе, которые помогут вам отладить ваш код.

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

Источник

Добавить комментарий

Ваш электронный адрес не будет опубликован.


*