Доступ по сети к вашему DIY‑устройству позволяет сделать его более гибким, ведь для того, чтобы внести какие‑то изменения в настройки к примеру, вашей метеостанции, вы можете просто подключиться к ней удаленно. Еще лучше, если доступ к устройству можно получить с помощью Wi‑Fi. В таком случае мы можем сделать устройство полностью мобильным, подключив к аккумулятору или пауэрбанку.
В этой статье мы рассмотрим использование ESP32 в качестве веб‑сервера для администрирования вашего DIY‑устройства. Пожалуй, веб‑интерфейс сейчас является наиболее распространенным способом удаленного управления различным оборудованием и приложениями, опережая столь любимую инженерами командную строку. Для работы через веб-интерфейс нужен только браузер и не требуется какой‑либо толстый клиент.
В качестве примера наш веб‑сервер будет управлять парой светодиодов, в соответствии с представленной схемой. Соответственно, на плате ESP у нас будет размещен веб‑сервер, с кнопками включения диодов.

Далее мы покажем общие принципы написания скетчей, с помощью которых вы сможете без труда модифицировать прошивку под свои задачи.
Центр всего или один из узлов
Для начала нам необходимо определиться с сетевой топологией, которую мы будем использовать. Здесь возможны два варианта. В первом случае, наше устройство подключено к беспроводной сети и является одним из ее клиентов. Это типичная топология для домашней сети, где у нас есть стационарная точка доступа, к которой все подключаются.

Здесь наше устройство будет иметь фиксированный IP адрес и настройки для подключения к беспроводной сети в качестве клиента.
Во втором случае, наша плата ESP будет сама выступать в качестве точки доступа. Этот вариант предполагает, что у нас нет никаких других точек доступа и наше устройство должно стать такой точкой доступа, к которой смогут подключаться клиенты для управления.

Здесь у нас будет не только фиксированный IP адрес, но и настройки, необходимые для работы в режиме точки доступа. Кстати, в качестве клиента, как показано на схеме, может также выступать плата ESP.
Обычно, второй вариант используется там, где нет никакой инфраструктуры WiFI. То есть наше устройство является полностью мобильным и для управления им необходимо подключаться к нему по WiFi.
Теперь давайте посмотрим реализацию каждого из вариантов.
Клиент, просто клиент
Для начала рассмотрим более простой вариант, когда ESP является просто клиентом. Полный код скетча будет приведен в конце статьи, а здесь мы проясним основные моменты.
Для подключения к беспроводной сети необходимо в блоке Setup() воспользоваться функцией WiFi.begin(), передав ей в качестве параметров SSID и ключ сети.
const char* ssid = "ESP32"; const char* password = "12345678"; WiFi.begin(ssid, password);
Пока ESP32 пытается подключиться к сети, мы можем использовать функцию WiFi.status(), чтобы проверить статус подключения.
while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); }
Здесь возможны следующие статусы:
-
WL_CONNECTED: при успешном подключении к сети Wi‑Fi
-
WL_NO_SHIELD: отсутствует модуль Wi‑Fi
-
WL_IDLE_STATUS: временное состояние, присваиваемое при вызове WiFi.begin() и остающееся активным до истечения количества попыток (в результате WL_CONNECT_FAILED) или установления соединения (в результате WL_CONNECTED)
-
WL_NO_SSID_AVAIL: ни один SSID не доступен
-
WL_SCAN_COMPLETED: сканирование сетей завершено
-
WL_CONNECT_FAILED: соединение не удалось после всех попыток
-
WL_CONNECTION_LOST: соединение потеряно
-
WL_DISCONNECTED: отключение от сети
После подключения к сети функция WiFi.localIP() используется для вывода на серийный порт IP‑адреса ESP32.
Serial.println(""); Serial.println("WiFi connected..!"); Serial.print("Got IP: "); Serial.println(WiFi.localIP());
Дальше мы рассмотрим код, который будет идентичен для обоих вариантов реализации нашего устройства.
Инициализация
В блоке Setup(), который выполняется только при включении устройства, мы создаем объект библиотеки WebServer, чтобы иметь доступ к ее функциям. Конструктор этого объекта принимает в качестве параметра порт, который будет прослушивать сервер. Поскольку по умолчанию HTTP использует порт 80, мы будем использовать это значение. Это позволит нам подключаться к серверу, не указывая порт в URL.
WebServer server(80);
Далее мы объявляем пины GPIO ESP32, к которым подключены светодиоды, а также их начальное состояние.
uint8_t LED1pin = 4; bool LED1status = LOW; uint8_t LED2pin = 5; bool LED2status = LOW;
Для отладки мы будем использовать серийный порт. Проинициализируем его и оба светодиода.
Serial.begin(115200); pinMode(LED1pin, OUTPUT); pinMode(LED2pin, OUTPUT);
Далее в блоке Setup() нам надо указать, какой код должен выполняться при обращении к определенному URL. Для этого мы используем метод.on(). Этот метод принимает два параметра: относительный путь к URL и имя функции, которая будет выполняться при посещении этого URL.
Например, первая строка приведенного ниже фрагмента кода указывает, что когда сервер получает HTTP‑запрос по корневому (/) пути, он вызовет функцию handle_OnConnect(). Важно отметить, что указанный URL является относительным путем.
Аналогично, мы должны указать еще четыре URL‑адреса для обработки двух состояний двух светодиодов.
server.on("/", handle_OnConnect); server.on("/led1on", handle_led1on); server.on("/led1off", handle_led1off); server.on("/led2on", handle_led2on); server.on("/led2off", handle_led2off);
Мы не указали, что должен выдать сервер, если клиент запрашивает URL, который не указан в server.on(). В качестве ответа он должен выдать ошибку 404 (Page Not Found). Для этого мы используем метод server.onNotFound().
server.onNotFound(handle_NotFound);
И нам остается только запустить сервер с помощью метода begin()
server.begin(); Serial.println("HTTP server started");
Бесконечный цикл
В блоке loop() у нас обрабатываются входящие HTTP‑запросы. Для этого мы используем метод handleClient(). Мы также изменяем состояние светодиодов в зависимости от запроса.
void loop() { server.handleClient(); if(LED1status) {digitalWrite(LED1pin, HIGH);} else {digitalWrite(LED1pin, LOW);} if(LED2status) {digitalWrite(LED2pin, HIGH);} else {digitalWrite(LED2pin, LOW);} }
Теперь мы должны написать функцию handle_OnConnect(), которую мы ранее прикрепили к корневому (/) URL с помощью server.on. Установим начальные состояния обоих светодиодов в LOW.
Мы используем метод send для ответа на HTTP‑запрос. Хотя этот метод может быть вызван с различными аргументами, в простейшей форме он требует код ответа HTTP, тип содержимого и содержимое.
Первым параметром, который мы передаем методу send, является код 200 (один из кодов состояния HTTP), который соответствует ответу OK. Затем мы указываем тип содержимого «text/html» и, наконец, передаем пользовательскую функцию SendHTML(), которая генерирует динамическую HTML‑страницу со статусом LED.
void handle_OnConnect() { LED1status = LOW; LED2status = LOW; Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF"); server.send(200, "text/html", SendHTML(LED1status,LED2status)); } void handle_led1on() { LED1status = HIGH; Serial.println("GPIO4 Status: ON"); server.send(200, "text/html", SendHTML(true,LED2status)); } void handle_led1off() { LED1status = LOW; Serial.println("GPIO4 Status: OFF"); server.send(200, "text/html", SendHTML(false,LED2status)); } void handle_led2on() { LED2status = HIGH; Serial.println("GPIO5 Status: ON"); server.send(200, "text/html", SendHTML(LED1status,true)); } void handle_led2off() { LED2status = LOW; Serial.println("GPIO5 Status: OFF"); server.send(200, "text/html", SendHTML(LED1status,false)); } void handle_NotFound(){ server.send(404, "text/plain", "Not found"); }
Всякий раз, когда веб‑сервер ESP32 получает запрос от веб‑клиента, функция sendHTML() генерирует веб‑страницу. Она просто конкатенирует HTML‑код в длинную строку и возвращается к функции server.send(), о которой мы говорили ранее. Функция использует состояние светодиодов в качестве параметра для динамической генерации HTML‑контента.
Подробно рассматривать HTML и стили мы не будем но обратим внимание на следующее. С помощью if мы можем динамически менять отображаемый на странице контент, просто подставляя соответствующую строку при изменении состояния светодиодов.
if(led1stat) {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";} else {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";} if(led2stat) {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";} else {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}
Веб‑интерфейс будет иметь следующий вид:

При нажатии:

Полный код скетча.
Скрытый текст
#include <WiFi.h> #include <WebServer.h> /*Put your SSID & Password*/ const char* ssid = " YourNetworkName"; // Enter SSID here const char* password = " YourPassword"; //Enter Password here WebServer server(80); uint8_t LED1pin = 4; bool LED1status = LOW; uint8_t LED2pin = 5; bool LED2status = LOW; void setup() { Serial.begin(115200); delay(100); pinMode(LED1pin, OUTPUT); pinMode(LED2pin, OUTPUT); Serial.println("Connecting to "); Serial.println(ssid); //connect to your local wi-fi network WiFi.begin(ssid, password); //check wi-fi is connected to wi-fi network while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected..!"); Serial.print("Got IP: "); Serial.println(WiFi.localIP()); server.on("/", handle_OnConnect); server.on("/led1on", handle_led1on); server.on("/led1off", handle_led1off); server.on("/led2on", handle_led2on); server.on("/led2off", handle_led2off); server.onNotFound(handle_NotFound); server.begin(); Serial.println("HTTP server started"); } void loop() { server.handleClient(); if(LED1status) {digitalWrite(LED1pin, HIGH);} else {digitalWrite(LED1pin, LOW);} if(LED2status) {digitalWrite(LED2pin, HIGH);} else {digitalWrite(LED2pin, LOW);} } void handle_OnConnect() { LED1status = LOW; LED2status = LOW; Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF"); server.send(200, "text/html", SendHTML(LED1status,LED2status)); } void handle_led1on() { LED1status = HIGH; Serial.println("GPIO4 Status: ON"); server.send(200, "text/html", SendHTML(true,LED2status)); } void handle_led1off() { LED1status = LOW; Serial.println("GPIO4 Status: OFF"); server.send(200, "text/html", SendHTML(false,LED2status)); } void handle_led2on() { LED2status = HIGH; Serial.println("GPIO5 Status: ON"); server.send(200, "text/html", SendHTML(LED1status,true)); } void handle_led2off() { LED2status = LOW; Serial.println("GPIO5 Status: OFF"); server.send(200, "text/html", SendHTML(LED1status,false)); } void handle_NotFound(){ server.send(404, "text/plain", "Not found"); } String SendHTML(uint8_t led1stat,uint8_t led2stat){ String ptr = "<!DOCTYPE html> <html>\n"; ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n"; ptr +="<title>LED Control</title>\n"; ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n"; ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n"; ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n"; ptr +=".button-on {background-color: #3498db;}\n"; ptr +=".button-on:active {background-color: #2980b9;}\n"; ptr +=".button-off {background-color: #34495e;}\n"; ptr +=".button-off:active {background-color: #2c3e50;}\n"; ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n"; ptr +="</style>\n"; ptr +="</head>\n"; ptr +="<body>\n"; ptr +="<h1>ESP32 Web Server</h1>\n"; ptr +="<h3>Using Station(STA) Mode</h3>\n"; if(led1stat) {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";} else {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";} if(led2stat) {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";} else {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";} ptr +="</body>\n"; ptr +="</html>\n"; return ptr; }
Точка доступа
Теперь рассмотрим второй вариант, когда наше устройство само является точкой доступа. Как уже упоминалось, весь код в блоке loop() будет такой же. А вот в блоке Setup() нам необходимо будет инициализировать режим точки доступа.
Здесь мы также указываем SSID и ключ.
const char* ssid = "ESP32"; const char* password = "12345678";
Далее укажем адрес, маску и шлюз. Обратите внимание, что здесь у нас нет DHCP так что на клиентах адрес нужно будет указывать вручную.
IPAddress local_ip(192,168,1,1); IPAddress gateway(192,168,1,1); IPAddress subnet(255,255,255,0);
Запускаем и настраиваем точку доступа.
WiFi.softAP(ssid, password); WiFi.softAPConfig(local_ip, gateway, subnet); delay(100);
Ну а весь остальной код у нас будет таким же, и найти его можно здесь.
Скрытый текст
#include <WiFi.h> #include <WebServer.h> /* Put your SSID & Password */ const char* ssid = "ESP32"; // Enter SSID here const char* password = "12345678"; //Enter Password here /* Put IP Address details */ IPAddress local_ip(192,168,1,1); IPAddress gateway(192,168,1,1); IPAddress subnet(255,255,255,0); WebServer server(80); uint8_t LED1pin = 4; bool LED1status = LOW; uint8_t LED2pin = 5; bool LED2status = LOW; void setup() { Serial.begin(115200); pinMode(LED1pin, OUTPUT); pinMode(LED2pin, OUTPUT); WiFi.softAP(ssid, password); WiFi.softAPConfig(local_ip, gateway, subnet); delay(100); server.on("/", handle_OnConnect); server.on("/led1on", handle_led1on); server.on("/led1off", handle_led1off); server.on("/led2on", handle_led2on); server.on("/led2off", handle_led2off); server.onNotFound(handle_NotFound); server.begin(); Serial.println("HTTP server started"); } void loop() { server.handleClient(); if(LED1status) {digitalWrite(LED1pin, HIGH);} else {digitalWrite(LED1pin, LOW);} if(LED2status) {digitalWrite(LED2pin, HIGH);} else {digitalWrite(LED2pin, LOW);} } void handle_OnConnect() { LED1status = LOW; LED2status = LOW; Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF"); server.send(200, "text/html", SendHTML(LED1status,LED2status)); } void handle_led1on() { LED1status = HIGH; Serial.println("GPIO4 Status: ON"); server.send(200, "text/html", SendHTML(true,LED2status)); } void handle_led1off() { LED1status = LOW; Serial.println("GPIO4 Status: OFF"); server.send(200, "text/html", SendHTML(false,LED2status)); } void handle_led2on() { LED2status = HIGH; Serial.println("GPIO5 Status: ON"); server.send(200, "text/html", SendHTML(LED1status,true)); } void handle_led2off() { LED2status = LOW; Serial.println("GPIO5 Status: OFF"); server.send(200, "text/html", SendHTML(LED1status,false)); } void handle_NotFound(){ server.send(404, "text/plain", "Not found"); } String SendHTML(uint8_t led1stat,uint8_t led2stat){ String ptr = "<!DOCTYPE html> <html>\n"; ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n"; ptr +="<title>LED Control</title>\n"; ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n"; ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n"; ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n"; ptr +=".button-on {background-color: #3498db;}\n"; ptr +=".button-on:active {background-color: #2980b9;}\n"; ptr +=".button-off {background-color: #34495e;}\n"; ptr +=".button-off:active {background-color: #2c3e50;}\n"; ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n"; ptr +="</style>\n"; ptr +="</head>\n"; ptr +="<body>\n"; ptr +="<h1>ESP32 Web Server</h1>\n"; ptr +="<h3>Using Access Point(AP) Mode</h3>\n"; if(led1stat) {ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";} else {ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";} if(led2stat) {ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";} else {ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";} ptr +="</body>\n"; ptr +="</html>\n"; return ptr; }
Заключение
В этой статье мы рассмотрели использование веб интерфейса для управления устройством на базе платы ESP32. Конечно, при желании представленную концепцию можно усовершенствовать, добавив к примеру, аутентификацию при доступе к серверу или что‑то еще.
Однако, предложенное решение тоже можно использовать для управления различными устройствами как в режиме клиента беспроводной сети, так и в качестве самостоятельной точки доступа.
В завершение рад пригласить всех желающих на открытые уроки, которые пройдут в рамках курса Otus «Embedded Developer»:
ссылка на оригинал статьи https://habr.com/ru/articles/894174/
Добавить комментарий