Крошечный веб-сервер на ESP32

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

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

В качестве примера наш веб‑сервер будет управлять парой светодиодов, в соответствии с представленной схемой. Соответственно, на плате ESP у нас будет размещен веб‑сервер, с кнопками включения диодов.

5d9e0ba6137babaa201e125f3f9bb441.png

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

Центр всего или один из узлов

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

b44e4974fa0b0368c43ed7bbae57bd7c.png

Здесь наше устройство будет иметь фиксированный IP адрес и настройки для подключения к беспроводной сети в качестве клиента.

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

5c8c5c3adfb4bc28c8d8e70d43179a7b.png

Здесь у нас будет не только фиксированный 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 +="

LED1 Status: ON

OFF\n";} else  {ptr +="

LED1 Status: OFF

ON\n";} if(led2stat)  {ptr +="

LED2 Status: ON

OFF\n";} else  {ptr +="

LED2 Status: OFF

ON\n";}

Веб‑интерфейс будет иметь следующий вид:

b8d35dd31e160e96eb4d95fd67ab66a0.png

При нажатии:

314c070fe24e99f033c94eff25b6e52a.png

Полный код скетча.

Скрытый текст
#include 
#include 

/*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 = " \n";
  ptr +="\n";
  ptr +="LED Control\n";
  ptr +="\n";
  ptr +="\n";
  ptr +="\n";
  ptr +="

ESP32 Web Server

\n"; ptr +="

Using Station(STA) Mode

\n"; if(led1stat) {ptr +="

LED1 Status: ON

OFF\n";} else {ptr +="

LED1 Status: OFF

ON\n";} if(led2stat) {ptr +="

LED2 Status: ON

OFF\n";} else {ptr +="

LED2 Status: OFF

ON\n";} ptr +=" \n"; ptr +="\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 
#include 

/* 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 = " \n";
  ptr +="\n";
  ptr +="LED Control\n";
  ptr +="\n";
  ptr +="\n";
  ptr +="\n";
  ptr +="

ESP32 Web Server

\n"; ptr +="

Using Access Point(AP) Mode

\n"; if(led1stat) {ptr +="

LED1 Status: ON

OFF\n";} else {ptr +="

LED1 Status: OFF

ON\n";} if(led2stat) {ptr +="

LED2 Status: ON

OFF\n";} else {ptr +="

LED2 Status: OFF

ON\n";} ptr +="\n"; ptr +="\n"; return ptr; }

Заключение

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

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

В завершение рад пригласить всех желающих на открытые уроки, которые пройдут в рамках курса Otus «Embedded Developer»:

  • 2 апреля: Эхо технологий: Звуковая магия микроконтроллеров. Подробнее

  • 10 апреля: АЦП ESP32. Оцифровать сигнал, а не «погоду на Марсе». Нюансы, особенности, повышение точности. Подробнее

  • 21 апреля: Атмосфера в кармане: Управляйте климатом с легкостью. Подробнее

© Habrahabr.ru