Создание веб-сервера на ESP32: Полное руководство для начинающих
Вы новичок в работе с веб-серверами ESP32? Вы попали по адресу. В этом подробном руководстве мы рассмотрим основные концепции, которые вам нужно знать для создания веб-серверов на ESP32, чтобы вы могли удалённо управлять и контролировать его выходы. Мы начнём с основной теории, а затем перейдём к практическим примерам, которые помогут вам применить полученные знания.
Мы рассмотрим создание веб-сервера для управления выходами и отображения показаний датчиков, добавление аутентификации к веб-серверу, настройку ESP32 в качестве точки доступа и многое другое. Для максимальной простоты мы будем использовать встроенную библиотеку WebServer.h.
Новичок в ESP32? Рекомендуем сначала ознакомиться с этими вводными руководствами:
Обзор руководства
В этом руководстве мы рассмотрим следующие темы. Если вы новичок в веб-серверах ESP32 и хотите узнать больше об этой теме, рекомендуем следовать этим разделам по порядку.
Рекомендуемая книга: Build Web Servers with the ESP32 and ESP8266 (3rd edition)
Предварительные требования
Это руководство сосредоточено на программировании ESP32 с использованием ядра Arduino. Перед тем как продолжить, у вас должно быть установлено ядро ESP32 Arduino в вашей Arduino IDE. Следуйте следующему руководству для установки ESP32 в Arduino IDE, если вы этого ещё не сделали.
Введение в веб-серверы (Основные концепции)
Говоря простым языком, веб-сервер — это «компьютер», который доставляет веб-страницы. Он хранит файлы веб-сайта, включая HTML-документы и связанные ресурсы, такие как изображения, таблицы стилей CSS, шрифты и другие файлы. Когда пользователь отправляет запрос, сервер отправляет эти файлы в веб-браузер пользователя.
Когда вы открываете веб-страницу в браузере, вы фактически отправляете запрос на сервер, используя протокол передачи гипертекста (HTTP). Этот протокол управляет тем, как информация запрашивается и доставляется в Интернете. Сервер затем отвечает, отправляя запрошенную вами веб-страницу — также через HTTP.
Чтобы лучше понять, как всё это работает с вашими платами ESP32, давайте рассмотрим некоторые термины, которые вы, вероятно, слышали раньше, но, возможно, не полностью понимаете.
Запрос-Ответ
Запрос-ответ — это шаблон обмена сообщениями, в котором запрашивающая сторона (ваш браузер) отправляет сообщение-запрос системе-ответчику (ESP32 в качестве веб-сервера), которая получает и обрабатывает запрос и возвращает сообщение в ответ.
В большинстве наших проектов ответное сообщение будет веб-страницей, которая отображает последние показания датчиков или предоставляет интерфейс для управления выходами.
Это простой, но мощный шаблон обмена сообщениями, особенно в архитектурах клиент-сервер.
Клиент-Сервер
Когда вы вводите URL в браузере, ваше устройство (клиент) отправляет запрос на сервер, используя протокол передачи гипертекста (HTTP). Сервер получает запрос и отвечает — также через HTTP — отправляя обратно веб-страницу. Этот обмен между клиентами и серверами происходит через компьютерную сеть.
Проще говоря, клиенты отправляют запросы серверам. Серверы обрабатывают запросы клиентов.
В этом руководстве мы будем рассматривать ESP32 как сервер, а вас — использующих браузер — как клиента. В наших проектах есть только один сервер (плата ESP32), но может быть несколько клиентов. Это могут быть различные веб-браузеры на разных устройствах, таких как компьютеры, смартфоны или планшеты, все подключённые к одной сети, или даже несколько вкладок браузера, открытых на одном устройстве.
IP-адрес
IP-адрес — это числовая метка, присвоенная каждому устройству, подключённому к компьютерной сети.
Это серия из четырёх значений, разделённых точками, где каждое значение варьируется от 0 до 255. Вот пример:
192.168.1.75
Дома ваши устройства подключены к частной сети через роутер (локальная сеть). Все устройства, подключённые к вашему роутеру, являются частью вашей локальной сети. Внутри этой сети каждое устройство имеет свой собственный IP-адрес.
Устройства, подключённые к одному роутеру, могут обращаться друг к другу через IP-адрес. Устройства вне вашей локальной сети не могут получить доступ к вашим локальным устройствам, используя их локальный IP-адрес.
Когда вы подключаете платы ESP32 к роутеру, они становятся частью вашей локальной сети. Таким образом, вашим платам ESP32 присваивается IP-адрес.
В вашей локальной сети IP-адрес ESP32 (и других устройств) назначается роутером с использованием так называемого DHCP (Dynamic Host Configuration Protocol — протокол динамической настройки хоста). Вам не нужно беспокоиться о деталях. Вам просто нужно знать, что DHCP автоматически назначает IP-адрес и другие сетевые параметры каждому устройству в сети.
Роутер отслеживает каждое устройство в сети и сопоставляет IP-адрес каждому устройству каждый раз, когда оно присоединяется к сети. Два устройства в одной сети не могут иметь одинаковый IP-адрес.
Опять же, когда ESP32 подключён к вашему роутеру, IP-адрес, который он получает, является локальным адресом. Это означает, что вы можете получить к нему доступ только с устройств, которые также подключены к той же сети. Как показано на предыдущем изображении, вы можете получить доступ к ESP32 с помощью компьютера или смартфона, находящегося в той же сети.
Wi-Fi станция и Wi-Fi точка доступа
Плата ESP32 может работать как Wi-Fi станция, точка доступа или и то, и другое.
Wi-Fi станция
Когда ESP32 настроен для работы в качестве Wi-Fi станции, он подключается к существующей сети, например к вашему домашнему роутеру. В этой конфигурации роутер даёт ESP32 уникальный локальный IP-адрес. Затем вы можете связываться с ESP32 с других устройств (таких как ваш телефон или компьютер), подключённых к той же сети, просто используя этот IP-адрес.
Поскольку роутер также подключён к интернету, мы можем запрашивать информацию из интернета, используя наши платы ESP32, например данные из API, публиковать данные на онлайн-платформах, использовать иконки и изображения из интернета на страницах нашего веб-сервера или подключать JavaScript-библиотеки.
Однако в некоторых случаях у нас может не быть роутера поблизости для подключения ESP32. В этом сценарии вы должны настроить плату ESP32 как точку доступа.
Точка доступа
Когда ваш ESP32 настроен как точка доступа, другие устройства (такие как ваш смартфон, планшет или компьютер) могут подключаться к нему без необходимости роутера; ESP управляет своей собственной Wi-Fi сетью.
В отличие от роутера, точка доступа ESP32 не подключается далее к проводной сети или интернету, поэтому вы не можете получить доступ к внешним библиотекам, публиковать показания датчиков в облаке или использовать сервисы, такие как электронная почта. В большинстве наших примеров мы обычно настраиваем платы ESP32 как станции. Вы можете легко изменить наши примеры и настроить платы как точки доступа, если это больше подходит для ваших проектов.
Коммуникация клиент-сервер
Существует несколько способов связи между клиентом и сервером: HTTP Polling, Server-Sent Events (SSE) и WebSocket. Мы сосредоточимся на HTTP Polling, который является самым простым протоколом для начала работы и лучшего понимания того, как работают веб-серверы.
У нас есть электронная книга, посвящённая веб-серверам, которая подробно рассматривает эти три протокола связи:
HTTP Polling
При HTTP polling клиент многократно запрашивает у сервера новую информацию. Когда сервер получает запрос, он отвечает запрошенными данными. Сервер отправляет информацию только тогда, когда клиент запрашивает её.
Например, ваш браузер отправляет запрос к ESP32, и он отвечает тем, что вы запрограммировали, например веб-страницей, показывающей последние показания датчиков. Чтобы поддерживать веб-страницу в актуальном состоянии с помощью этого метода, браузер должен продолжать отправлять запросы через регулярные интервалы.
Веб-сервер ESP32
Давайте рассмотрим практический пример с ESP32, который действует как веб-сервер в локальной сети.
Обычно веб-сервер с ESP32 в локальной сети выглядит так: ESP32, работающий как веб-сервер, подключён через Wi-Fi к вашему роутеру. Ваш компьютер, смартфон или планшет также подключены к вашему роутеру через Wi-Fi или Ethernet-кабель. Таким образом, ESP32 и ваш браузер находятся в одной сети.
Когда вы вводите IP-адрес ESP32 в браузере, вы отправляете HTTP-запрос к ESP32. Затем ESP32 отвечает ответом, который может содержать значение, показание, HTML-текст для отображения веб-страницы или любые другие данные.
Пример веб-сервера ESP32 (Управление выходами)
Основываясь на том, что мы узнали о веб-серверах и ESP32, как можно объединить всё это для создания IoT-проектов? Поскольку ESP32 имеет GPIO-пины, вы можете подключать датчики, исполнительные механизмы и другие устройства, а затем управлять ими или контролировать их через веб-интерфейс.
Вот пример веб-сервера, который мы создали для управления выходом. Следующая веб-страница появляется, когда вы вводите IP-адрес ESP32 в браузере.
Когда вы нажимаете кнопку ON, URL меняется на IP-адрес ESP, за которым следует /on. ESP получает запрос по этому новому URL, проверяет, какой URL запрашивается, и соответственно изменяет состояние светодиода.
Нажмите кнопку ON > запрос: /on > светодиод включается
Когда вы нажимаете кнопку OFF, новый запрос отправляется к ESP32 по URL /off. ESP снова проверяет, какой URL запрашивается, и выключает светодиод.
Нажмите кнопку OFF > запрос: /off > светодиод выключается
Та же концепция может быть применена для управления несколькими выходами.
Обзор проекта
Прежде чем переходить непосредственно к проекту, важно описать, что будет делать наш веб-сервер, чтобы потом было легче следовать и понимать шаги.
Веб-сервер, который вы создадите, управляет двумя светодиодами: один подключён к GPIO 26 ESP32, а другой — к GPIO 27.
Вы можете получить доступ к веб-серверу ESP32, введя IP-адрес ESP32 в браузере в локальной сети.
Нажимая кнопки на веб-сервере, вы можете мгновенно изменять состояние каждого светодиода.
Это простой пример, который иллюстрирует, как создать веб-сервер, управляющий двумя светодиодами. Идея состоит в том, чтобы заменить эти светодиоды реле или любыми другими электронными компонентами, которыми вы хотите управлять.
Сборка схемы
Начните со сборки схемы. Подключите два светодиода к ESP32, как показано на следующей схеме — один светодиод подключён к GPIO 26, а другой к GPIO 27.
Вот список компонентов, необходимых для сборки схемы:
Рекомендуемая литература: ESP32 Pinout Reference: Which GPIO pins should you use?
Создание веб-сервера
После сборки схемы следующий шаг — загрузка кода на ESP32. Скопируйте код ниже в Arduino IDE, но пока не загружайте его. Вам нужно внести некоторые изменения, чтобы он заработал.
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
https://RandomNerdTutorials.com/esp32-web-server-beginners-guide/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <WiFi.h>
#include <WebServer.h>
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Assign output variables to GPIO pins
const int output26 = 26;
const int output27 = 27;
String output26State = "off";
String output27State = "off";
// Create a web server object
WebServer server(80);
// Function to handle turning GPIO 26 on
void handleGPIO26On() {
output26State = "on";
digitalWrite(output26, HIGH);
handleRoot();
}
// Function to handle turning GPIO 26 off
void handleGPIO26Off() {
output26State = "off";
digitalWrite(output26, LOW);
handleRoot();
}
// Function to handle turning GPIO 27 on
void handleGPIO27On() {
output27State = "on";
digitalWrite(output27, HIGH);
handleRoot();
}
// Function to handle turning GPIO 27 off
void handleGPIO27Off() {
output27State = "off";
digitalWrite(output27, LOW);
handleRoot();
}
// Function to handle the root URL and show the current states
void handleRoot() {
String html = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
html += "<link rel=\"icon\" href=\"data:,\">";
html += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}";
html += ".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}";
html += ".button2 { background-color: #555555; }</style></head>";
html += "<body><h1>ESP32 Web Server</h1>";
// Display GPIO 26 controls
html += "<p>GPIO 26 - State " + output26State + "</p>";
if (output26State == "off") {
html += "<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>";
} else {
html += "<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>";
}
// Display GPIO 27 controls
html += "<p>GPIO 27 - State " + output27State + "</p>";
if (output27State == "off") {
html += "<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>";
} else {
html += "<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>";
}
html += "</body></html>";
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
// Initialize the output variables as outputs
pinMode(output26, OUTPUT);
pinMode(output27, OUTPUT);
// Set outputs to LOW
digitalWrite(output26, LOW);
digitalWrite(output27, LOW);
// Connect to Wi-Fi network
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Set up the web server to handle different routes
server.on("/", handleRoot);
server.on("/26/on", handleGPIO26On);
server.on("/26/off", handleGPIO26Off);
server.on("/27/on", handleGPIO27On);
server.on("/27/off", handleGPIO27Off);
// Start the web server
server.begin();
Serial.println("HTTP server started");
}
void loop() {
// Handle incoming client requests
server.handleClient();
}
Настройка сетевых учётных данных
Вам нужно изменить следующие строки, указав свои сетевые учётные данные: SSID и пароль.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = " REPLACE_WITH_YOUR_PASSWORD";
Получение IP-адреса ESP32
Теперь вы можете загрузить код, и он сразу заработает. Не забудьте проверить, что у вас выбрана правильная плата и COM-порт.
Откройте Serial Monitor на скорости 115200 бод.
ESP32 подключается к Wi-Fi и выводит свой IP-адрес в Serial Monitor. Скопируйте этот IP-адрес, потому что он нужен вам для доступа к веб-серверу ESP32.
Примечание: Если в Serial Monitor ничего не появляется, нажмите кнопку «EN» на ESP32 (кнопка ENABLE/RESET рядом с портом microUSB).
Доступ к веб-серверу
Откройте браузер, вставьте IP-адрес ESP32, и вы увидите следующую страницу.
Тестирование веб-сервера
Давайте протестируем веб-сервер. Нажмите кнопку, чтобы включить GPIO 26. В Serial Monitor вы можете увидеть, что ESP32 получает запрос по URL /26/on.
Когда ESP получает этот запрос, он включает светодиод, подключённый к GPIO 26, и его состояние также обновляется на веб-странице. Протестируйте кнопку для GPIO 27 и убедитесь, что она работает аналогично.
Вы также можете получить доступ к веб-серверу на своём смартфоне, если он подключён к той же сети.
Как работает код
Теперь давайте подробнее рассмотрим код, чтобы понять, как он работает.
Первое, что вам нужно сделать — подключить необходимые библиотеки для подключения к Wi-Fi и настройки веб-сервера.
#include <WiFi.h>
#include <WebServer.h>
Как упоминалось ранее, вам нужно вставить свои ssid и password в следующие строки внутри двойных кавычек.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = " REPLACE_WITH_YOUR_PASSWORD";
Назначьте GPIO-пины каждому из ваших выходов. В этом примере мы используем GPIO 26 и GPIO 27, но вы можете использовать любые другие подходящие GPIO.
const int output26 = 26;
const int output27 = 27;
Затем создайте переменные для хранения состояний этих выходов. Вы можете добавить больше выходов, определив дополнительные переменные.
String output26State = "off";
String output27State = "off";
Создайте объект веб-сервера на порту 80 с именем server.
// Create a web server object
WebServer server(80);
setup()
Теперь перейдём к setup(). Сначала мы запускаем последовательную связь на скорости 115200 бод для отладки.
Serial.begin(115200);
Также определяем GPIO как OUTPUT и устанавливаем их в LOW.
// Initialize the output variables as outputs
pinMode(output26, OUTPUT);
pinMode(output27, OUTPUT);
// Set outputs to LOW
digitalWrite(output26, LOW);
digitalWrite(output27, LOW);
Следующие строки начинают Wi-Fi подключение с помощью WiFi.begin(ssid, password), ожидают успешного подключения и выводят IP-адрес ESP32 в Serial Monitor.
// Connect to Wi-Fi network
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
Прочитайте это руководство, чтобы узнать больше о Wi-Fi с ESP32: ESP32 Useful Wi-Fi Library Functions (Arduino IDE).
Наконец, мы настраиваем веб-сервер и определяем маршруты, которые он должен обрабатывать. Эти маршруты будут запрашиваться при нажатии на различные кнопки на веб-странице.
// Set up the web server to handle different routes
server.on("/", handleRoot);
server.on("/26/on", handleGPIO26On);
server.on("/26/off", handleGPIO26Off);
server.on("/27/on", handleGPIO27On);
server.on("/27/off", handleGPIO27Off);
// Start the web server
server.begin();
Serial.println("HTTP server started");
Например, когда вы делаете запрос по корневому URL / (вы просто вставляете IP-адрес ESP32 в веб-браузер), будет выполнена функция handleRoot(). Когда вы нажимаете кнопку GPIO 26 ON, будет сделан запрос по маршруту /26/on, и плата выполнит функцию handleGPIO26On(), и так далее… Эти функции определены в начале кода перед setup(). Мы рассмотрим их далее.
loop()
В loop() мы непрерывно слушаем входящие запросы клиентов. Это гарантирует, что ESP32 всегда готов отвечать на запросы из вашего браузера.
server.handleClient();
Обработка управления GPIO
На веб-странице вы видели, что есть четыре кнопки для управления GPIO:
Кнопка GPIO 26 ON > запрос: /26/on > функция: handleGPIO26On()
Кнопка GPIO 26 OFF > запрос: /26/off > функция: handleGPIO26Off()
Кнопка GPIO 27 ON > запрос: /27/on > функция: handleGPIO27On()
Кнопка GPIO 27 OFF > запрос: /27/off > функция: handleGPIO27Off()
Давайте рассмотрим кнопку GPIO 26 ON. Когда вы нажимаете эту кнопку, она делает запрос к ESP32 по URL /26/on. Когда это происходит, выполняется функция handleGPIO26On().
// Function to handle turning GPIO 26 on
void handleGPIO26On() {
output26State = "on";
digitalWrite(output26, HIGH);
handleRoot();
}
Эта функция обновляет состояние GPIO в переменной output26State и включает GPIO. Наконец, она вызывает функцию handleRoot() для отображения веб-страницы с правильным состоянием GPIO.
Это работает аналогично для других кнопок и соответствующих маршрутов и функций. Обратите внимание, что все эти функции вызывают функцию handleRoot() для отображения веб-страницы с правильными состояниями GPIO.
// Function to handle turning GPIO 26 off
void handleGPIO26Off() {
output26State = "off";
digitalWrite(output26, LOW);
handleRoot();
}
// Function to handle turning GPIO 27 on
void handleGPIO27On() {
output27State = "on";
digitalWrite(output27, HIGH);
handleRoot();
}
// Function to handle turning GPIO 27 off
void handleGPIO27Off() {
output27State = "off";
digitalWrite(output27, LOW);
handleRoot();
}
Отображение веб-страницы
Функция handleRoot() отвечает за генерацию веб-страницы. Она отправляет HTML и CSS, необходимые для построения страницы. HTML и CSS текст, необходимый для построения веб-страницы, сохраняется в переменной html.
// Function to handle the root URL and show the current states
void handleRoot() {
String html = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
html += "<link rel=\"icon\" href=\"data:,\">";
html += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}";
html += ".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}";
html += ".button2 { background-color: #555555; }</style></head>";
html += "<body><h1>ESP32 Web Server</h1>";
// Display GPIO 26 controls
html += "<p>GPIO 26 - State " + output26State + "</p>";
if (output26State == "off") {
html += "<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>";
} else {
html += "<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>";
}
// Display GPIO 27 controls
html += "<p>GPIO 27 - State " + output27State + "</p>";
if (output27State == "off") {
html += "<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>";
} else {
html += "<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>";
}
html += "</body></html>";
server.send(200, "text/html", html);
}
Обратите внимание, что для генерации кнопок мы используем операторы if и else для отображения правильной кнопки и состояния в соответствии с текущим состоянием GPIO.
// Display GPIO 26 controls
html += "<p>GPIO 26 - State " + output26State + "</p>";
if (output26State == "off") {
html += "<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>";
} else {
html += "<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>";
}
// Display GPIO 27 controls
html += "<p>GPIO 27 - State " + output27State + "</p>";
if (output27State == "off") {
html += "<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>";
} else {
html += "<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>";
}
Наконец, веб-страница отправляется клиенту:
server.send(200, "text/html", HTML);
Добавление аутентификации к веб-серверу
В некоторых проектах вы можете захотеть защитить веб-сервер ESP32 именем пользователя и паролем. Следующий код защищает доступ к веб-серверу с помощью имени пользователя и пароля.
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
https://RandomNerdTutorials.com/esp32-web-server-beginners-guide/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <WiFi.h>
#include <WebServer.h>
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Username and password for web page access
const char* http_username = "admin";
const char* http_password = "admin";
// Assign output variables to GPIO pins
const int output26 = 26;
const int output27 = 27;
String output26State = "off";
String output27State = "off";
// Create a web server object
WebServer server(80);
// Function to authenticate user
bool isAuthenticated() {
if (!server.authenticate(http_username, http_password)) {
server.requestAuthentication();
return false;
}
return true;
}
// Function to handle turning GPIO 26 on
void handleGPIO26On() {
if (!isAuthenticated()) return;
output26State = "on";
digitalWrite(output26, HIGH);
handleRoot();
}
// Function to handle turning GPIO 26 off
void handleGPIO26Off() {
if (!isAuthenticated()) return;
output26State = "off";
digitalWrite(output26, LOW);
handleRoot();
}
// Function to handle turning GPIO 27 on
void handleGPIO27On() {
if (!isAuthenticated()) return;
output27State = "on";
digitalWrite(output27, HIGH);
handleRoot();
}
// Function to handle turning GPIO 27 off
void handleGPIO27Off() {
if (!isAuthenticated()) return;
output27State = "off";
digitalWrite(output27, LOW);
handleRoot();
}
// Function to handle the root URL and show the current states
void handleRoot() {
if (!isAuthenticated()) return;
String html = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
html += "<link rel=\"icon\" href=\"data:,\">";
html += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}";
html += ".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}";
html += ".button2 { background-color: #555555; }</style></head>";
html += "<body><h1>ESP32 Web Server</h1>";
// Display GPIO 26 controls
html += "<p>GPIO 26 - State " + output26State + "</p>";
if (output26State == "off") {
html += "<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>";
} else {
html += "<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>";
}
// Display GPIO 27 controls
html += "<p>GPIO 27 - State " + output27State + "</p>";
if (output27State == "off") {
html += "<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>";
} else {
html += "<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>";
}
html += "</body></html>";
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
// Initialize the output variables as outputs
pinMode(output26, OUTPUT);
pinMode(output27, OUTPUT);
// Set outputs to LOW
digitalWrite(output26, LOW);
digitalWrite(output27, LOW);
// Connect to Wi-Fi network
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Set up the web server to handle different routes with authentication
server.on("/", handleRoot);
server.on("/26/on", handleGPIO26On);
server.on("/26/off", handleGPIO26Off);
server.on("/27/on", handleGPIO27On);
server.on("/27/off", handleGPIO27Off);
// Start the web server
server.begin();
Serial.println("HTTP server started");
}
void loop() {
// Handle incoming client requests
server.handleClient();
}
Добавление имени пользователя и пароля
Следующий код защищает ваш веб-сервер именем пользователя и паролем. По умолчанию имя пользователя — admin, и пароль — admin.
Вы можете добавить желаемое имя пользователя и пароль в следующих строках.
const char* http_username = "admin";
const char* http_password = "admin";
Обработка аутентификации и запросов
Для защиты вашей веб-страницы мы добавили простой механизм аутентификации. Перед доступом к любой части веб-сервера пользователь должен ввести правильное имя пользователя и пароль. Это обрабатывается функцией isAuthenticated().
bool isAuthenticated() {
if (!server.authenticate(http_username, http_password)) {
server.requestAuthentication();
return false;
}
return true;
}
Эта функция проверяет, аутентифицирован ли пользователь. Если нет, она предлагает пользователю ввести учётные данные. Функция возвращает true, если пользователь аутентифицирован, или false в противном случае.
Перед отправкой любого ответа клиенту мы проверяем, аутентифицирован ли пользователь. Обратите внимание на использование следующей строки перед возвратом чего-либо клиенту.
if (!isAuthenticated()) return;
Например, в случае функции handleGPIO26On():
// Function to handle turning GPIO 26 on
void handleGPIO26On() {
if (!isAuthenticated()) return;
output26State = "on";
digitalWrite(output26, HIGH);
handleRoot();
}
Тестирование веб-сервера с аутентификацией
Теперь, когда вы попытаетесь получить доступ к IP-адресу вашего ESP32, вам потребуется ввести имя пользователя и пароль. Затем нажмите кнопку «Sign in»:
Вы сможете получить доступ к веб-странице для управления выходами.
Если вы введёте неправильный пароль, окно входа появится снова.
Веб-сервер ESP32 в режиме точки доступа (AP)
Представьте, что вы хотите управлять ESP32 через веб-страницу на смартфоне, но ESP32 не имеет доступа к роутеру для подключения к интернету (то есть вы не можете настроить его как Wi-Fi станцию, как в предыдущих примерах). В этом случае вы можете настроить ESP32 как точку доступа.
Когда ваш ESP32 настроен как точка доступа, другие устройства (такие как ваш смартфон, планшет или компьютер) могут подключаться к нему без необходимости роутера. Давайте посмотрим, как это реализовать.
Настройка ESP32 как точки доступа
Чтобы настроить ESP32 как точку доступа, установите режим Wi-Fi как точку доступа следующим образом:
WiFi.mode(WIFI_AP);
А затем используйте метод softAP() следующим образом:
WiFi.softAP(ssid, password);
ssid — это имя, которое вы хотите дать точке доступа ESP32, а параметр password — это пароль для точки доступа. Если вы не хотите устанавливать пароль, установите его в NULL.
Есть также другие необязательные параметры, которые вы можете передать методу softAP(). Вот все параметры:
WiFi.softAP(const char* ssid, const char* password, int channel, int ssid_hidden, int max_connection);
ssid: имя для точки доступа — максимум 63 символа;
password: минимум 8 символов; установите NULL, если хотите, чтобы точка доступа была открытой;
channel: номер Wi-Fi канала (1-13)
ssid_hidden: (0 = транслировать SSID, 1 = скрыть SSID)
max_connection: максимальное количество одновременно подключённых клиентов (1-4)
Следующий код создаёт тот же веб-сервер, что и ранее, но в режиме точки доступа.
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
https://RandomNerdTutorials.com/esp32-web-server-beginners-guide/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <WiFi.h>
#include <WebServer.h>
// Replace with SSID and PASSWORD for the ESP32 ACCESS POINT
// (for testing you can leave the default)
const char* ssid = "ESP32_ACCESS_POINT";
const char* password = "pass123456";
// Assign output variables to GPIO pins
const int output26 = 26;
const int output27 = 27;
String output26State = "off";
String output27State = "off";
// Create a web server object
WebServer server(80);
// Function to handle turning GPIO 26 on
void handleGPIO26On() {
output26State = "on";
digitalWrite(output26, HIGH);
handleRoot();
}
// Function to handle turning GPIO 26 off
void handleGPIO26Off() {
output26State = "off";
digitalWrite(output26, LOW);
handleRoot();
}
// Function to handle turning GPIO 27 on
void handleGPIO27On() {
output27State = "on";
digitalWrite(output27, HIGH);
handleRoot();
}
// Function to handle turning GPIO 27 off
void handleGPIO27Off() {
output27State = "off";
digitalWrite(output27, LOW);
handleRoot();
}
// Function to handle the root URL and show the current states
void handleRoot() {
String html = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
html += "<link rel=\"icon\" href=\"data:,\">";
html += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}";
html += ".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}";
html += ".button2 { background-color: #555555; }</style></head>";
html += "<body><h1>ESP32 Web Server</h1>";
// Display GPIO 26 controls
html += "<p>GPIO 26 - State " + output26State + "</p>";
if (output26State == "off") {
html += "<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>";
} else {
html += "<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>";
}
// Display GPIO 27 controls
html += "<p>GPIO 27 - State " + output27State + "</p>";
if (output27State == "off") {
html += "<p><a href=\"/27/on\"><button class=\"button\">ON</button></a></p>";
} else {
html += "<p><a href=\"/27/off\"><button class=\"button button2\">OFF</button></a></p>";
}
html += "</body></html>";
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
// Initialize the output variables as outputs
pinMode(output26, OUTPUT);
pinMode(output27, OUTPUT);
// Set outputs to LOW
digitalWrite(output26, LOW);
digitalWrite(output27, LOW);
// Set the ESP32 as access point
Serial.print("Setting as access point ");
WiFi.mode(WIFI_AP);
WiFi.softAP(ssid, password);
Serial.println("");
Serial.println("ESP32 Wi-Fi Access Point ready!");
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
// Set up the web server to handle different routes
server.on("/", handleRoot);
server.on("/26/on", handleGPIO26On);
server.on("/26/off", handleGPIO26Off);
server.on("/27/on", handleGPIO27On);
server.on("/27/off", handleGPIO27Off);
// Start the web server
server.begin();
Serial.println("HTTP server started");
}
void loop() {
// Handle incoming client requests
server.handleClient();
}
Вы можете загрузить предыдущий код на плату.
ESP32 настроит свою собственную Wi-Fi сеть. Теперь, чтобы получить доступ к веб-серверу, вам нужно подключить компьютер или смартфон к сети ESP32.
На смартфоне перейдите в настройки Wi-Fi и подключитесь к ESP32_ACCESS_POINT. Пароль — pass123456 (если вы оставили значения по умолчанию). Затем откройте браузер и введите IP-адрес ESP32: 192.168.4.1.
Теперь вы должны иметь возможность управлять ESP32 без необходимости роутера. Вы используете Wi-Fi сеть ESP32.
Пример веб-сервера ESP32 (Отображение показаний датчиков)
Для завершения нашего руководства по веб-серверам ESP32 мы создадим простой пример веб-сервера для отображения показаний датчиков. Мы будем отображать показания температуры, влажности и давления с датчика BME280, но вы можете использовать любой другой датчик.
Рекомендуемая литература: ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity).
У нас есть руководства для более чем 30 датчиков и модулей с ESP32, ознакомьтесь с ними здесь: ESP32: 30+ Free Guides for Sensors and Modules.
Обзор проекта
В этом примере мы будем отображать показания датчика BME280 в таблице. Веб-страница для отображения показаний будет выглядеть следующим образом:
Значения температуры, влажности, давления и высоты будут объединены с HTML-строкой. Когда ESP32 получает запрос по корневому URL, мы отправляем HTML-строку, объединённую с текущими показаниями датчиков.
Чтобы получить новые показания, вам нужно обновить веб-страницу для отправки нового запроса.
Создание таблицы в HTML
В этом примере мы отображаем показания датчика BME280 в таблице. Поэтому нам нужно написать HTML-текст для построения таблицы.
Для создания таблицы в HTML используются теги <table> и </table>.
Для создания строки используются теги <tr> и </tr>. Заголовок таблицы определяется с помощью тегов <th> и </th>, а каждая ячейка таблицы определяется с помощью тегов <td> и </td>.
Для создания таблицы наших показаний используется следующий HTML-текст:
<table>
<tr>
<th>MEASUREMENT</th>
<th>VALUE</th>
</tr>
<tr>
<td>Temp. Celsius</td>
<td>--- *C</td>
</tr>
<tr>
<td>Temp. Fahrenheit</td>
<td>--- *F</td>
</tr>
<tr>
<td>Pressure</td>
<td>--- hPa</td>
</tr>
<tr>
<td>Approx. Altitude</td>
<td>--- meters</td></tr>
<tr>
<td>Humidity</td>
<td>--- %</td>
</tr>
</table>
Мы создаём заголовок таблицы с ячейкой MEASUREMENT и другой с именем VALUE.
Затем мы создаём шесть строк для отображения каждого из показаний, используя теги <tr> и </tr>. Внутри каждой строки мы создаём две ячейки, используя теги <td> и </td>, одну с названием измерения и другую для хранения значения измерения. Три тире «—» затем должны быть заменены фактическими измерениями с датчика BME280.
Вы можете сохранить этот текст как table.html, перетащить файл в браузер и увидеть результат. Предыдущий HTML-текст создаёт следующую таблицу.
Таблица не имеет применённых стилей. Вы можете использовать CSS для стилизации таблицы. Вы можете прочитать руководство по следующей ссылке, чтобы узнать, как стилизовать таблицу: CSS Styling Tables.
Сборка схемы
Подключите датчик к пинам SDA и SCL ESP32, как показано на следующей схеме. Для платы ESP32 Devkit по умолчанию используются пины GPIO 21 (SDA) и GPIO 22 (SCL). Проверьте пины по умолчанию для используемой вами платы.
Вот список компонентов, необходимых для сборки этой схемы:
Отображение показаний датчика BME280 на веб-сервере — Код
Теперь, когда вы знаете, как создать таблицу для отображения результатов и основы создания веб-сервера, пришло время для кода. Если вы следовали предыдущим примерам, вам будут знакомы большинство строк кода.
Скопируйте следующий код в Arduino IDE. Перед загрузкой вам нужно вставить свои SSID и пароль.
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
https://RandomNerdTutorials.com/esp32-web-server-beginners-guide/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
#include <WebServer.h>
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME280 bme; // I2C
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Create an instance of the WebServer on port 80
WebServer server(80);
void handleRoot() {
String html = "<!DOCTYPE html><html>";
html += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
html += "<link rel=\"icon\" href=\"data:,\">";
html += "<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}";
html += "table { border-collapse: collapse; width:60%; margin-left:auto; margin-right:auto; }";
html += "th { padding: 10px; background-color: #0043af; color: white; }";
html += "tr { border: 1px solid #ddd; padding: 10px; }";
html += "tr:hover { background-color: #bcbcbc; }";
html += "td { border: none; padding: 8px; }";
html += ".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }</style></head>";
html += "<body><h1>ESP32 with BME280</h1>";
html += "<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>";
html += "<tr><td>Temp. Celsius</td><td><span class=\"sensor\">";
html += String(bme.readTemperature());
html += " *C</span></td></tr>";
html += "<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">";
html += String(1.8 * bme.readTemperature() + 32);
html += " *F</span></td></tr>";
html += "<tr><td>Pressure</td><td><span class=\"sensor\">";
html += String(bme.readPressure() / 100.0F);
html += " hPa</span></td></tr>";
html += "<tr><td>Approx. Altitude</td><td><span class=\"sensor\">";
html += String(bme.readAltitude(SEALEVELPRESSURE_HPA));
html += " m</span></td></tr>";
html += "<tr><td>Humidity</td><td><span class=\"sensor\">";
html += String(bme.readHumidity());
html += " %</span></td></tr></table></body></html>";
// Send the response to the client
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
// Initialize the BME280 sensor
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
// Connect to Wi-Fi
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Set up the routes
server.on("/", handleRoot);
// Start the server
server.begin();
}
void loop() {
server.handleClient();
}
Измените следующие строки, чтобы указать свои SSID и пароль.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Затем проверьте, что у вас выбрана правильная плата и COM-порт, и загрузите код на ESP32. После загрузки откройте Serial Monitor на скорости 115200 бод и скопируйте IP-адрес ESP32.
Откройте браузер, вставьте IP-адрес, и вы должны увидеть последние показания датчиков. Чтобы обновить показания, просто обновите веб-страницу.
Как работает код
Этот скетч очень похож на предыдущие скетчи в этом руководстве. Вам должна быть знакома большая часть кода. Сначала вы подключаете библиотеки WiFi и WebServer для создания веб-сервера и необходимые библиотеки для чтения с датчика BME280.
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
#include <WebServer.h>
Следующая строка определяет переменную для сохранения давления на уровне моря. Для более точной оценки высоты замените значение текущим давлением на уровне моря в вашем местоположении.
#define SEALEVELPRESSURE_HPA (1013.25)
В следующей строке вы создаёте объект Adafruit_BME280 с именем bme, который по умолчанию устанавливает связь с датчиком через I2C.
Adafruit_BME280 bme; // I2C
Узнайте больше об I2C с ESP32: ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE).
Как упоминалось ранее, вам нужно вставить свои ssid и password в следующие строки внутри двойных кавычек.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = " REPLACE_WITH_YOUR_PASSWORD";
Затем инициализируйте сервер на порту 80.
WebServer server(80);
setup()
В setup() мы запускаем последовательную связь на скорости 115200 бод для отладки.
Serial.begin(115200);
Вы проверяете, что датчик BME280 был успешно инициализирован.
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
Следующие строки начинают Wi-Fi подключение с помощью WiFi.begin(ssid, password), ожидают успешного подключения и выводят IP-адрес ESP в Serial Monitor.
// Connect to Wi-Fi
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
Наконец, мы настраиваем веб-сервер и определяем маршруты, которые он должен обрабатывать. Этот веб-сервер будет обрабатывать только корневой URL для отображения последних показаний датчиков. При доступе к корневому URL будет вызвана функция handleRoot().
// Set up the routes
server.on("/", handleRoot);
// Start the server
server.begin();
Отображение веб-страницы
В этом примере функция handleRoot() отвечает за генерацию веб-страницы. Она отправляет HTML и CSS, необходимые для построения страницы. HTML и CSS текст, необходимый для построения веб-страницы, сохраняется в переменной html. Мы объединяем текущие показания датчиков с переменной html. Таким образом, HTML, отправленный в веб-браузер, будет содержать последние показания датчиков.
void handleRoot() {
String html = "<!DOCTYPE html><html>";
html += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
html += "<link rel=\"icon\" href=\"data:,\">";
html += "<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}";
html += "table { border-collapse: collapse; width:50%; margin-left:auto; margin-right:auto; }";
html += "th { padding: 10px; background-color: #0043af; color: white; }";
html += "tr { border: 1px solid #ddd; padding: 12px; }";
html += "tr:hover { background-color: #bcbcbc; }";
html += "td { border: none; padding: 10px; }";
html += ".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }</style></head>";
html += "<body><h1>ESP32 with BME280</h1>";
html += "<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>";
html += "<tr><td>Temp. Celsius</td><td><span class=\"sensor\">";
html += String(bme.readTemperature());
html += " *C</span></td></tr>";
html += "<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">";
html += String(1.8 * bme.readTemperature() + 32);
html += " *F</span></td></tr>";
html += "<tr><td>Pressure</td><td><span class=\"sensor\">";
html += String(bme.readPressure() / 100.0F);
html += " hPa</span></td></tr>";
html += "<tr><td>Approx. Altitude</td><td><span class=\"sensor\">";
html += String(bme.readAltitude(SEALEVELPRESSURE_HPA));
html += " m</span></td></tr>";
html += "<tr><td>Humidity</td><td><span class=\"sensor\">";
html += String(bme.readHumidity());
html += " %</span></td></tr></table></body></html>";
// Send the response to the client
server.send(200, "text/html", html);
}
Для отображения показаний датчиков в таблице нам просто нужно отправить их между соответствующими тегами <td> и </td>. Например, для отображения температуры:
html += "<tr><td>Temp. Celsius</td><td><span class=\"sensor\">";
html += String(bme.readTemperature());
html += " *C</span></td></tr>";
Примечание: тег <span> полезен для стилизации определённой части текста. В данном случае мы используем тег <span> для включения показания датчика в класс «sensor». Это полезно для стилизации этой конкретной части текста с помощью CSS.
По умолчанию таблица отображает показания температуры как в градусах Цельсия, так и в градусах Фаренгейта.
Чтобы получить последние показания датчиков, просто обновите веб-страницу. Для автоматического обновления показаний вам нужно изучить другие протоколы связи, такие как server-sent events или websockets.
Для более глубокого изучения создания веб-серверов ESP32 рекомендуем ознакомиться с нашей электронной книгой: Build Web Servers with ESP32 and ESP8266 eBook (3rd Edition).
Заключение
В этом руководстве вы узнали о создании веб-серверов на ESP32. Мы рассмотрели основные концепции и ознакомились с базовыми примерами для начала работы. Мы надеемся, что это руководство было для вас полезным и что вы сможете модифицировать примеры для своих проектов.
Если вы новичок в ESP32 и веб-серверах, рекомендуем следующие электронные книги:
Вот другие руководства, которые могут вам понравиться:
ESP32 Async Web Server – Control Outputs with Arduino IDE (ESPAsyncWebServer library)
ESP32 Web Server (WebSocket) with Multiple Sliders: Control LEDs Brightness (PWM)
ESP32 Web Server using Server-Sent Events (Update Sensor Readings Automatically)
Источник: Random Nerd Tutorials — Rui Santos & Sara Santos https://randomnerdtutorials.com/esp32-web-server-beginners-guide/