Embedded

Подключение к Wi-Fi и веб-сервер на ESP32: WiFi.h и WebServer

Введение

Прежде чем переходить к MQTT (статьи 311+), разберём более простой и наглядный первый шаг работы с сетью на ESP32 — подключение к домашней Wi-Fi сети и создание простого встроенного веб-сервера, к которому можно обратиться через обычный браузер для проверки состояния устройства или отправки простых команд, без необходимости в Qt-приложении вовсе на этом начальном этапе.

Концепция

WiFi.h предоставляет простой интерфейс подключения к существующей Wi-Fi сети (режим station, как обычное устройство-клиент) — после успешного подключения ESP32 получает IP-адрес в локальной сети, через который к нему можно обратиться. Встроенная библиотека WebServer (для ESP32) позволяет легко создать минимальный HTTP-сервер, обрабатывающий запросы на конкретные пути (/, /status, /led/on) — что даёт простой, не требующий специального клиента способ взаимодействия с устройством для отладки и простых сценариев.

Пример кода: Подключение к Wi-Fi и веб-сервер на ESP32

// firmware/src/main.cpp — подключение к Wi-Fi и простой веб-сервер с управлением светодиодом
#include <WiFi.h>
#include <WebServer.h>

const char *WIFI_SSID = "MyHomeNetwork";
const char *WIFI_PASSWORD = "secure_password_here";

WebServer server(80);
const int LED_PIN = 2;
bool ledState = false;

void setup()
{
    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);

    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.print("Подключение к Wi-Fi");

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println();
    Serial.print("Подключено! IP-адрес: ");
    Serial.println(WiFi.localIP());

    server.on("/", handleRoot);
    server.on("/led/on", handleLedOn);
    server.on("/led/off", handleLedOff);
    server.on("/status", handleStatus);

    server.begin();
}

void loop()
{
    server.handleClient(); // обязательный вызов — без него сервер не обрабатывает входящие запросы
}

void handleRoot()
{
    server.send(200, "text/html",
        "<h1>ESP32 Control Panel</h1>"
        "<a href='/led/on'>Включить LED</a><br>"
        "<a href='/led/off'>Выключить LED</a>");
}

void handleLedOn()
{
    digitalWrite(LED_PIN, HIGH);
    ledState = true;
    server.send(200, "text/plain", "LED включён");
}

void handleLedOff()
{
    digitalWrite(LED_PIN, LOW);
    ledState = false;
    server.send(200, "text/plain", "LED выключен");
}

void handleStatus()
{
    // Простой JSON-ответ для программного обращения, в дополнение к человекочитаемому HTML
    String json = "{\"led\":" + String(ledState ? "true" : "false") + "}";
    server.send(200, "application/json", json);
}

Пояснения к коду

WiFi.begin(WIFI_SSID, WIFI_PASSWORD) инициирует подключение асинхронно, и цикл while (WiFi.status() != WL_CONNECTED) с короткими паузами явно ожидает завершения этого процесса перед продолжением — на практике подключение занимает от долей секунды до нескольких секунд в зависимости от условий сети. После подключения WiFi.localIP() сообщает полученный IP-адрес — именно по этому адресу затем нужно обращаться браузером или Qt-приложением, и поскольку адрес обычно выдаётся динамически роутером (DHCP), он может изменяться между перезагрузками устройства, если не настроена статическая привязка на самом роутере.

server.on(path, handler) регистрирует обработчик для конкретного URL-пути — структурно это похоже на MessageDispatcher из статьи 288 (паттерн диспетчеризации по ключу), но применённый к HTTP-путям вместо префиксов текстового протокола последовательного порта. handleStatus() показывает, что один и тот же сервер может одновременно обслуживать как простые HTML-страницы для просмотра в браузере человеком, так и JSON-эндпоинты, предназначенные для программного обращения (например, будущим Qt-приложением через QNetworkAccessManager, аналогично основному циклу статей про сеть).

Подводные камни

  • Хранение пароля Wi-Fi непосредственно в виде открытой строки в исходном коде прошивки — для учебных и домашних проектов это обычно приемлемо, но для прошивки, которая может попасть в публичный репозиторий или быть скопирована неавторизованными людьми, пароль сети оказывается раскрыт; для более чувствительных сценариев credentials стоит выносить в отдельный, не публикуемый конфигурационный файл или использовать механизм настройки Wi-Fi через временный режим точки доступа самого ESP32 (WiFiManager-подобные библиотеки) вместо жёсткого кодирования.
  • Забытый вызов server.handleClient() внутри loop() — без этого вызова веб-сервер, формально запущенный через server.begin(), не будет реально обрабатывать входящие HTTP-запросы; это частая первая ошибка, аналогичная забытому IrReceiver.resume() из статьи 302 — компонент инициализирован, но не вызывается регулярно для реальной обработки событий.
  • Блокирующий цикл ожидания подключения к Wi-Fi (while (WiFi.status() != WL_CONNECTED)) без тайм-аута — если сеть с указанным именем недоступна (неверный пароль, сеть выключена, устройство физически вне зоны покрытия), устройство зависнет в этом цикле бесконечно, не переходя к остальной части прошивки вовсе; для надёжных, автономных устройств стоит добавить тайм-аут с последующей логикой повторных попыток или альтернативным режимом работы (например, локальной точкой доступа для первоначальной настройки).
  • Динамически выдаваемый IP-адрес устройства, меняющийся между перезагрузками, затрудняет последующее автоматическое обнаружение устройства Qt-приложением, если адрес жёстко закодирован где-либо на стороне приложения — для надёжного обнаружения устройств в локальной сети без необходимости вручную каждый раз искать новый IP-адрес обычно используют отдельные протоколы обнаружения (mDNS/Bonjour через библиотеку ESPmDNS) или, что особенно естественно для следующих статей цикла, центральный MQTT-брокер с известным, фиксированным адресом, к которому подключаются все устройства, вместо необходимости знать IP-адрес каждого отдельного устройства напрямую.