Перевод статьи ESP-IDF: ESP32 Web Server Access Point (AP) ModeRui Santos & Sara Santos, Random Nerd Tutorials.

Это руководство показывает, как построить локальный веб-сервер в режиме точки доступа (Access Point, AP) на ESP32, запрограммированном через ESP-IDF, который отдаёт простую HTML-страницу. Страница доступна, когда вы подключены напрямую к точке доступа ESP32.

Что понадобится#

Прежде чем следовать этому руководству, нужно установить расширение ESP-IDF в VS Code. Также понадобится плата ESP32 любой модели на ваш выбор.

Что такое веб-сервер#

Простыми словами, веб-сервер — это «компьютер», который отдаёт веб-страницы. Он хранит файлы сайта: HTML-документы и сопутствующие ресурсы вроде изображений, таблиц стилей CSS, шрифтов и прочих файлов. Когда пользователь делает запрос, сервер отправляет эти файлы в его браузер.

Когда вы открываете страницу в браузере, вы на самом деле отправляете серверу запрос по протоколу HTTP. Этот протокол определяет, как информация запрашивается и доставляется в интернете. Сервер отвечает, присылая запрошенную страницу — тоже по HTTP.

ESP32 в роли точки доступа#

Когда ESP32 настроен как точка доступа (AP), другие устройства — смартфон, планшет, компьютер — могут подключаться к нему без роутера: ESP32 сам управляет своей Wi-Fi-сетью.

ESP32 в режиме точки доступа

ESP32 в режиме точки доступа: устройства подключаются напрямую

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

Создаём проект ESP-IDF из шаблона#

Расширение ESP-IDF позволяет легко создать проект с нуля: все нужные файлы и настройки генерируются автоматически. Чтобы создать новый проект ESP-IDF в VS Code:

  • Откройте расширение ESP-IDF Espressif и разверните меню Advanced
  • Нажмите New Project Wizard
  • Выберите Use ESP-IDF v6.X, чтобы задать версию фреймворка

В меню выберите шаблонный проект ESP-IDF Templates и нажмите кнопку Create project using template sample project. Откроется новое окно, где нужно заполнить поля:

  • Project Name — желаемое имя проекта;
  • Enter Project Directory — нажмите на иконку папки и выберите целевую папку для файлов проекта. Подойдёт любая директория. Важно: не используйте папки Google Drive, OneDrive или Dropbox — в процессе сборки создаётся масса файлов, и в облачной папке это может работать крайне медленно;
  • ESP-IDF Target — выберите целевой чип; у автора ESP32 с чипом esp32s3;
  • ESP-IDF Board — для чипа esp32s3 нужно также выбрать конфигурацию ESP32-S chip (via builtin USB-JTAG);
  • Serial Port — при подключённой к компьютеру плате выберите правильный номер COM-порта;
  • Choose Template — нажмите синюю кнопку, чтобы создать проект по шаблону.

Открываем проект в VS Code#

Через несколько секунд в VS Code появится уведомление. Нажмите Open Project, чтобы открыть только что созданный проект.

Если уведомление не появилось, откройте проект вручную: File → Open Folder…, найдите на компьютере папку проекта и нажмите Select Folder.

ESP-IDF создаёт для проекта много файлов, папок и подпапок. Для этого руководства рекомендуется оставить все файлы по умолчанию без изменений — мы будем менять только main.c. Чтобы его открыть, разверните проводник проекта, выберите папку проекта, разверните папку main и щёлкните по файлу main.c.

Код: ESP32 в режиме точки доступа на ESP-IDF#

Скопируйте следующий код в файл main.c. Он отдаёт простую HTML-страницу, размещённую на ESP32, пока тот работает точкой доступа.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  https://RandomNerdTutorials.com/esp-idf-esp32-web-server-access-point/
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_http_server.h"
#include "esp_mac.h"
#include "sdkconfig.h"

// Замените на свои SSID и пароль точки доступа (AP)
#define MY_ESP_WIFI_SSID      "ESP32_AP"
#define MY_ESP_WIFI_PASS      "password123"

static const char *TAG = "web_server_ap";

// Глобальный хэндл сетевого интерфейса AP
static esp_netif_t *ap_netif;

// HTML-страница, которую отдаём
static const char *html_page =
    "<!DOCTYPE html>"
    "<html>"
    "<head>"
    "<title>ESP-IDF: Web Server (Access Point)</title>"
    "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
    "</head>"
    "<body>"
    "<h1>ESP-IDF: Web Server (Access Point)</h1>"
    "<p>Hello from ESP32!</p>"
    "<p>Connected to ESP32 via Access Point (AP) Mode.</p>"
    "</body>"
    "</html>";

// Обработчик HTTP GET для корня "/"
static esp_err_t root_get_handler(httpd_req_t *req)
{
    httpd_resp_set_type(req, "text/html");
    httpd_resp_send(req, html_page, HTTPD_RESP_USE_STRLEN);
    return ESP_OK;
}

// Запуск HTTP-сервера
static httpd_handle_t start_web_server(void)
{
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    httpd_handle_t server = NULL;

    if (httpd_start(&server, &config) == ESP_OK) {
        ESP_LOGI(TAG, "HTTP server started on port %d", config.server_port);
        httpd_uri_t uri_get = {
            .uri       = "/",
            .method    = HTTP_GET,
            .handler   = root_get_handler,
            .user_ctx  = NULL
        };
        httpd_register_uri_handler(server, &uri_get);
        return server;
    }
    ESP_LOGE(TAG, "Failed to start HTTP server");
    return NULL;
}

// Обработчик событий Wi-Fi и IP
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                               int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) {
        ESP_LOGI(TAG, "Wi-Fi AP started! SSID: %s", MY_ESP_WIFI_SSID);

        esp_netif_ip_info_t ip_info;
        if (esp_netif_get_ip_info(ap_netif, &ip_info) == ESP_OK) {
            ESP_LOGI(TAG, "AP IP: " IPSTR, IP2STR(&ip_info.ip));
            ESP_LOGI(TAG, "Web server ready -> http://" IPSTR "/", IP2STR(&ip_info.ip));
        }
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
        ESP_LOGI(TAG, "Device connected: " MACSTR, MAC2STR(event->mac));
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
        ESP_LOGI(TAG, "Device disconnected: " MACSTR, MAC2STR(event->mac));
    }
}

void app_main(void)
{
    ESP_LOGI(TAG, "Starting... Free heap: %d bytes", esp_get_free_heap_size());

    // Инициализация NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // Инициализация стека TCP/IP и цикла событий
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // Создание интерфейса Wi-Fi AP по умолчанию
    ap_netif = esp_netif_create_default_wifi_ap();

    // Инициализация Wi-Fi
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // Регистрация обработчиков событий
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
                                                        &wifi_event_handler, NULL, NULL));

    // Конфигурация точки доступа (AP)
    wifi_config_t wifi_config = {
        .ap = {
            .ssid = MY_ESP_WIFI_SSID,
            .ssid_len = strlen(MY_ESP_WIFI_SSID),
            .channel = 1,
            .password = MY_ESP_WIFI_PASS,
            .max_connection = 4,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK,
        },
    };

    // Проверки SSID/пароля, чтобы избежать перезагрузок
    if (strlen(MY_ESP_WIFI_SSID) == 0 || strlen(MY_ESP_WIFI_SSID) > 32) {
        ESP_LOGE(TAG, "Invalid SSID length!");
        return;
    }
    if (strlen(MY_ESP_WIFI_PASS) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
        ESP_LOGW(TAG, "No password -> Open AP (anyone can connect)");
    } else if (strlen(MY_ESP_WIFI_PASS) < 8) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
        wifi_config.ap.password[0] = '\0';
        ESP_LOGW(TAG, "Password too short (<8 chars) -> Forced Open AP");
    } else {
        ESP_LOGI(TAG, "Using WPA2-PSK (password OK)");
    }

    // Запуск интерфейса Wi-Fi в режиме точки доступа
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    // Запуск веб-сервера в режиме AP
    httpd_handle_t server = start_web_server();
    if (server) {
        ESP_LOGI(TAG, "Web server initialized. Connect to the Access Point (AP) now!");
    }
}

Как работает код#

Разберём код по частям.

Библиотеки#

Начинаем с подключения нужных библиотек:

  • stdio.h — стандартная библиотека C, нужна для printf, выводящей отладочную информацию в монитор порта;
  • string.h — стандартная библиотека C для работы со строками;
  • FreeRTOS.h — базовые типы и функции FreeRTOS;
  • task.h — управление задачами;
  • esp_system.h — системные функции вроде перезапуска и сведений о железе;
  • esp_wifi.h — конфигурация Wi-Fi (режимы станции и точки доступа, параметры подключения);
  • esp_event.h — обработка событий вроде изменения статуса Wi-Fi;
  • esp_log.h — фреймворк форматирования отладочных сообщений в мониторе порта;
  • nvs_flash.h — постоянное хранение данных «ключ-значение» в энергонезависимой памяти (NVS);
  • esp_netif.h — сетевой интерфейс;
  • esp_http_server.h — создание HTTP-сервера и обработка запросов и ответов;
  • esp_mac.h — функции печати MAC-адресов через ESP_LOGI;
  • sdkconfig.h — файл конфигурации проекта.

Настройки точки доступа#

Нужно задать имя сети (SSID) и пароль для доступа к ESP32. В этом примере SSID — ESP32_AP, но вы можете поменять имя на любое. Пароль — password123, его тоже можно изменить.

#define MY_ESP_WIFI_SSID      "ESP32_AP"
#define MY_ESP_WIFI_PASS     "password123"

Тег для логов#

Префикс web_server_ap будет использоваться для всех отладочных сообщений.

static const char *TAG = "web_server_ap";

HTML-страница#

Это та самая страница, которая отправляется подключённым клиентам.

static const char *html_page =
    "<!DOCTYPE html>"
    "<html>"
    "<head>"
    "<title>ESP-IDF: Web Server (Access Point)</title>"
    "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
    "</head>"
    "<body>"
    "<h1>ESP-IDF: Web Server (Access Point)</h1>"
    "<p>Hello from ESP32!</p>"
    "<p>Connected to ESP32 via Access Point (AP) Mode.</p>"
    "</body>"
    "</html>";

Обработчик HTTP GET#

Функция root_get_handler выполняется каждый раз, когда клиент, подключённый к точке доступа ESP32, вводит в браузере IP-адрес платы (это запрос к корневой странице /). Функция httpd_resp_set_type сообщает браузеру, что отправляемый файл в формате HTML, а httpd_resp_send отправляет клиенту html_page.

static esp_err_t root_get_handler(httpd_req_t *req)
{
    httpd_resp_set_type(req, "text/html");
    httpd_resp_send(req, html_page, HTTPD_RESP_USE_STRLEN);
    return ESP_OK;
}

Запуск HTTP-сервера#

Функция start_web_server() начинает с конфигурации сервера (использует порт 80 по умолчанию):

httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;

Вызов httpd_start(&server, &config) поднимает сам HTTP-сервер и начинает слушать подключения на порту 80. Затем мы регистрируем маршрут, чтобы при открытии URL / вызывалась функция root_get_handler(), отправляющая объявленную ранее переменную html_page.

if (httpd_start(&server, &config) == ESP_OK) {
    ESP_LOGI(TAG, "HTTP server started on port %d", config.server_port);

    // Регистрация обработчика URI
    httpd_uri_t uri_get = {
        .uri       = "/",
        .method    = HTTP_GET,
        .handler   = root_get_handler,
        .user_ctx  = NULL
    };
    httpd_register_uri_handler(server, &uri_get);

    return server;
}

Если поднять сервер не удалось, выводится сообщение об ошибке.

ESP_LOGE(TAG, "Failed to start HTTP server");

Обработчик событий Wi-Fi и IP#

Эта callback-функция вызывается, когда происходит событие, связанное с Wi-Fi: старт точки доступа, подключение или отключение устройства.

static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                               int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) {
        ESP_LOGI(TAG, "Wi-Fi AP started! SSID: %s", MY_ESP_WIFI_SSID);

        esp_netif_ip_info_t ip_info;
        if (esp_netif_get_ip_info(ap_netif, &ip_info) == ESP_OK) {
            ESP_LOGI(TAG, "AP IP: " IPSTR, IP2STR(&ip_info.ip));
            ESP_LOGI(TAG, "Web server ready -> http://" IPSTR "/", IP2STR(&ip_info.ip));
        }
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
        ESP_LOGI(TAG, "Device connected: " MACSTR, MAC2STR(event->mac));
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
        ESP_LOGI(TAG, "Device disconnected: " MACSTR, MAC2STR(event->mac));
    }
}

Функция app_main#

При создании проекта ESP-IDF всегда вызывается функция app_main — именно в ней вы пишете код любого приложения ESP-IDF, это аналог setup() в программировании на Arduino. Когда ESP32 загружается, фреймворк ESP-IDF вызывает app_main.

void app_main(void)
{
    // здесь ваш код
}

В app_main мы начинаем с инициализации NVS — ESP32 хранит настройки Wi-Fi во флеш-памяти.

esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    ESP_ERROR_CHECK(nvs_flash_erase());
    ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);

Затем инициализируем стек TCP/IP, необходимый для работы с сетью.

ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());

Запуск точки доступа#

Запускаем интерфейс Wi-Fi в режиме точки доступа через esp_netif_create_default_wifi_ap(), чтобы другие устройства могли подключаться к ESP32, работающему как роутер. Здесь же инициализируется сам Wi-Fi.

ap_netif = esp_netif_create_default_wifi_ap();

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));

Назначаем обработчики событий Wi-Fi, чтобы при установке соединения или получении IP-адреса запускалось соответствующее событие.

ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));

Следующие строки задают SSID, пароль и прочие настройки точки доступа.

wifi_config_t wifi_config = {
    .ap = {
        .ssid = MY_ESP_WIFI_SSID,
        .ssid_len = strlen(MY_ESP_WIFI_SSID),
        .channel = 1,
        .password = MY_ESP_WIFI_PASS,
        .max_connection = 4,
        .authmode = WIFI_AUTH_WPA_WPA2_PSK,
    },
};

А эти — проверяют, что SSID и пароль корректны для запуска ESP32 в режиме AP. Обратите внимание: если пароль пустой или короче восьми символов, точка доступа принудительно становится открытой.

if (strlen(MY_ESP_WIFI_SSID) == 0 || strlen(MY_ESP_WIFI_SSID) > 32) {
    ESP_LOGE(TAG, "Invalid SSID length!");
    return;
}
if (strlen(MY_ESP_WIFI_PASS) == 0) {
    wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    ESP_LOGW(TAG, "No password -> Open AP (anyone can connect)");
} else if (strlen(MY_ESP_WIFI_PASS) < 8) {
    wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    wifi_config.ap.password[0] = '\0';
    ESP_LOGW(TAG, "Password too short (<8 chars) -> Forced Open AP");
} else {
    ESP_LOGI(TAG, "Using WPA2-PSK (password OK)");
}

Переводим ESP32 в режим AP с созданными настройками и запускаем интерфейс Wi-Fi.

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());

Наконец, запускаем веб-сервер:

httpd_handle_t server = start_web_server();
if (server) {
    ESP_LOGI(TAG, "Web Server initialized. Waiting for Wi-Fi connection...");
}

Сборка и прошивка платы#

Чтобы собрать и залить код ESP-IDF в ESP32, всегда нужно пройти одну и ту же процедуру: выбрать метод прошивки (UART), номер COM-порта, целевое устройство (ESP32), собрать код и залить его на плату. Все эти команды доступны в нижней панели VS Code.

Убедитесь, что все параметры выбраны верно — если вы пользовались мастером создания проекта, они уже могут быть настроены. Если нет, нажмите иконку звезды и выберите метод прошивки UART. При подключённой плате нажмите на иконку COM-порта (вилка) и выберите нужный номер порта. Затем выберите целевое устройство, щёлкнув по иконке чипа в нижней панели; для esp32s3 понадобится также выбрать конфигурацию ESP32-S chip (via builtin USB-JTAG).

Теперь можно собрать проект, нажав иконку гаечного ключа (Build Project). Первая сборка обычно занимает больше времени; по завершении в терминале появится сообщение Build Successfully.

Последний шаг — залить проект на ESP32 кнопкой Flash Device (иконка молнии). В зависимости от платы, может понадобиться удерживать кнопку BOOT, чтобы перевести ESP32 в режим прошивки. По окончании появится сообщение Flash Done.

Демонстрация#

Если вы выполнили все шаги, пример должен успешно работать на плате. Откройте окно терминала, нажав Monitor Device (иконка экрана).

ESP32 запускает интерфейс Wi-Fi в режиме точки доступа и выводит свой IP-адрес в монитор порта. Скопируйте этот адрес (192.168.4.1) — он понадобится для доступа к веб-серверу.

Информация в мониторе порта

ESP32 сообщает о запуске точки доступа и свой IP-адрес

Если в мониторе порта ничего не появилось, нажмите на плате кнопку EN (ENABLE/RESET рядом с разъёмом microUSB).

Чтобы попасть на веб-сервер ESP32, нужно подключить своё устройство напрямую к точке доступа:

  • откройте меню Wi-Fi;
  • выберите сеть ESP32_AP;
  • введите пароль (по умолчанию password123);
  • нажмите кнопку подключения.
Подключение к точке доступа ESP32

Подключаемся к сети ESP32_AP

Наконец, откройте браузер и введите IP-адрес ESP32 — это должно быть http://192.168.4.1. Вы увидите страницу, которую отдаёт ваша плата.

Веб-страница, отданная ESP32

Страница, которую отдаёт ESP32 в режиме точки доступа

Монитор порта при этом печатает информацию о подключённом устройстве.

Монитор порта: клиент подключился

В мониторе порта видно MAC-адрес подключившегося устройства

Подводя итог#

В этом руководстве вы научились программировать ESP32 на фреймворке ESP-IDF в VS Code, чтобы запустить точку доступа с простой веб-страницей. Когда ESP32 работает точкой доступа, роутер для удалённого подключения не нужен — вы подключаетесь напрямую к его собственной Wi-Fi-сети.