ESP-IDF: веб-сервер на ESP32 в режиме точки доступа (AP)

Перевод статьи ESP-IDF: ESP32 Web Server Access Point (AP) Mode — Rui 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 не подключается дальше к проводной сети или интернету, поэтому вы не сможете обращаться к внешним библиотекам, публиковать показания датчиков в облако или пользоваться сервисами вроде почты.
Создаём проект 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_AP
Наконец, откройте браузер и введите IP-адрес ESP32 — это должно быть http://192.168.4.1. Вы увидите страницу, которую отдаёт ваша плата.

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

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