Estás instalando un sensor en una bodega, en un invernadero o en un cerro sin señal, y necesitas leer el dato en el celular. No hay router, no hay internet y llevar un notebook con cable USB cada vez no es opción. Para ese escenario el ESP32 tiene una respuesta directa: puede dejar de ser un cliente de tu red WiFi y pasar a crear su propia red, con servidor web incluido.
En esta guía vas a montar, programando con ESP IDF (el framework nativo de Espressif, no el Arduino Core), un ESP32 en modo Access Point que levanta una página web accesible desde cualquier teléfono o notebook que se conecte a él. Al terminar vas a saber crear el proyecto desde la plantilla oficial, entender qué hace cada bloque del código, compilarlo, grabarlo y depurarlo cuando algo falle.
Access Point o estación: dos formas distintas de usar el WiFi
El ESP32 puede trabajar el WiFi de dos maneras y conviene tenerlas claras antes de escribir una línea de código, porque cambian todo el resto del proyecto.
En modo estación (STA) el ESP32 se conecta a un router existente, igual que tu celular. Tiene salida a internet, puede consultar una API, sincronizar la hora por NTP o publicar a un broker MQTT en la nube, pero depende de que esa red exista y de que tú tengas la clave.
En modo Access Point (AP) el ESP32 es el que genera la red. Los demás dispositivos se conectan a él directamente. No hay router de por medio y tampoco hay salida a internet: el ESP32 no enruta el tráfico hacia afuera. Eso significa que en AP puro no vas a poder usar NTP, ni llamar a una API externa, ni validar certificados HTTPS. A cambio, funciona en cualquier parte, sin infraestructura.

La regla práctica: si el proyecto vive dentro de una casa con WiFi, usa STA. Si tiene que funcionar en terreno, en una feria, en un taller ajeno o como interfaz de configuración inicial, usa AP.
| Estación (STA) | Access Point (AP) | |
|---|---|---|
| Quién crea la red | El router | El propio ESP32 |
| Necesita infraestructura | Sí, router y clave | No |
| Salida a internet | Sí | No |
| APIs externas, NTP, MQTT en la nube | Disponibles | No disponibles |
| Clientes conectados | No aplica | Hasta 10 (4 por defecto en el código) |
| Alcance típico | El del router | Unos 10 a 30 m según obstáculos |
| Uso habitual | Domótica en casa, telemetría a la nube | Terreno, portal de configuración, demos |
Vale la pena mencionar que el ESP32 también admite el modo mixto WIFI_MODE_APSTA,
en el que actúa como cliente de tu router y como punto de acceso al mismo tiempo.
Es más exigente en memoria y complica la depuración, pero es la base de los
equipos que siguen ofreciendo su propia red de mantenimiento mientras están
conectados a internet.
Lo que vas a necesitar
- Una placa ESP32 DevKit V1 (o cualquier variante ESP32/ESP32-S3; el ejemplo está probado en un ESP32-S3)
- Un cable USB de datos para programar y alimentar la placa
- VS Code con la extensión ESP IDF ya instalada y funcionando
- Un computador y un celular o notebook para conectarse a la red del ESP32
No se necesita ningún componente externo: todo el proyecto usa solo la radio WiFi que la placa ya trae integrada.
Paso 1: crear el proyecto desde la plantilla de ESP IDF
La extensión de ESP IDF genera el esqueleto completo del proyecto (carpetas,
CMakeLists.txt, configuración) sin que tengas que armar nada a mano. Abre la
extensión Espressif, despliega el menú Advanced y elige New Project
Wizard.
Lo primero que pide es la versión del framework. Selecciona Use ESP IDF v6.X.

Después, en la lista de proyectos de ejemplo, elige ESP IDF Templates y presiona el botón para crear el proyecto a partir de esa plantilla.

Se abre una ventana con los campos de configuración. Complétalos así:
- Project Name: el nombre que quieras para el proyecto.
- Enter Project Directory: la carpeta donde se guardará todo. Importante: no uses una carpeta sincronizada de Google Drive, OneDrive o Dropbox. La compilación crea y escribe miles de archivos, y sobre una carpeta en la nube el proceso se vuelve lentísimo.
- ESP IDF Target: el chip de tu placa (
esp32,esp32s3, etc.). - ESP IDF Board: la configuración de la placa. Para el ESP32-S3 corresponde ESP32-S chip (via builtin USB JTAG).
- Serial Port: con la placa conectada, el puerto COM que le corresponde.

Al terminar, VS Code muestra una notificación para abrir el proyecto recién creado. Haz clic en Open Project.

Si la notificación no aparece, no pasa nada: ve a File > Open Folder…, busca la carpeta del proyecto y selecciónala.
La plantilla genera bastantes archivos y subcarpetas. Para esta guía deja todo
como está: lo único que vas a modificar es main.c. Ábrelo desde el explorador
lateral, dentro de la carpeta main.

Paso 2: el código completo
Reemplaza el contenido de main.c por este código. Levanta el Access Point y el
servidor HTTP que entrega una página HTML simple.
/*
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"
// Replace with your Access Point (AP) SSID and Password
#define MY_ESP_WIFI_SSID "ESP32_AP"
#define MY_ESP_WIFI_PASS "password123"
static const char *TAG = "web_server_ap";
// Global AP netif handle
static esp_netif_t *ap_netif;
// HTML web page to serve
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 handler for root "/"
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;
}
// Start the HTTP server
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 and IP event handler
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());
// Initialize 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);
// Initialize TCP/IP stack and event loop
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Create default Wi-Fi STA interface
ap_netif = esp_netif_create_default_wifi_ap();
// Initialize Wi-Fi
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Register event handlers
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&wifi_event_handler, NULL, NULL));
// Configure Wi-Fi Access Point (AP) Configuration
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,
},
};
// Safety checks for correct SSID/Password to prevent reboots
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)");
}
// Start Wi-Fi interface in Access Point (AP) Mode
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());
// Start web server in AP mode
httpd_handle_t server = start_web_server();
if (server) {
ESP_LOGI(TAG, "Web server initialized. Connect to the Access Point (AP) now!");
}
}
Paso 3: qué hace cada parte del código
Las librerías
#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"
Las dos primeras son de la biblioteca estándar de C: stdio.h para imprimir y
string.h para manipular cadenas. FreeRTOS.h y task.h traen el sistema
operativo de tiempo real sobre el que corre todo en el ESP32. esp_wifi.h
configura la radio, esp_event.h maneja los eventos, esp_log.h da el sistema
de logging, nvs_flash.h permite guardar datos en memoria no volátil,
esp_netif.h aporta la interfaz de red, esp_http_server.h es el servidor HTTP
y esp_mac.h sirve para imprimir direcciones MAC.
El nombre y la clave de la red
#define MY_ESP_WIFI_SSID "ESP32_AP"
#define MY_ESP_WIFI_PASS "password123"
Estos dos valores son el nombre de red (SSID) y la contraseña que verás en el menú WiFi de tu celular. Cámbialos por lo que quieras, pero ojo con la clave: más abajo hay una regla importante sobre su largo.
La etiqueta de log
static const char *TAG = "web_server_ap";
Todos los mensajes de depuración van a salir en el monitor serial precedidos por
web_server_ap. Usar una etiqueta propia por módulo es lo que después te permite
filtrar el log cuando el proyecto crece.
La página que se entrega
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>";
El HTML va como una cadena de C constante, guardada en flash. Fíjate en las
comillas escapadas (\") dentro del meta viewport: son necesarias porque el
HTML vive dentro de una cadena de C. Para páginas más grandes esto se vuelve
incómodo y conviene pasar los archivos a una partición SPIFFS o LittleFS, pero
para una página de prueba resuelve bien.
El manejador de la petición
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;
}
Esta función corre cada vez que alguien conectado al AP abre la raíz / en el
navegador. httpd_resp_set_type le avisa al navegador que lo que viene es HTML,
y httpd_resp_send envía la página. HTTPD_RESP_USE_STRLEN le dice al servidor
que calcule solo el largo de la cadena, así no tienes que pasarlo a mano.
Levantar el servidor HTTP
El servidor arranca con la configuración por defecto, que usa el puerto 80:
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
Luego se registra la ruta: cuando alguien pide /, se ejecuta
root_get_handler.
if (httpd_start(&server, &config) == ESP_OK) {
ESP_LOGI(TAG, "HTTP server started on port %d", config.server_port);
// Register URI handler
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;
}
Si el servidor no logra iniciar, se imprime el error:
ESP_LOGE(TAG, "Failed to start HTTP server");
Los eventos de WiFi
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));
}
}
Esta función se ejecuta sola cuando ocurre algo en la radio. WIFI_EVENT_AP_START
avisa que el AP quedó operativo y es el momento en que se imprime la IP.
WIFI_EVENT_AP_STACONNECTED y WIFI_EVENT_AP_STADISCONNECTED se disparan cuando
un dispositivo entra o sale de la red, e imprimen su MAC. Ese par de eventos es
útil de verdad: te permite saber cuántos clientes tienes conectados sin adivinar.
app_main
En ESP IDF no existen setup() ni loop(). El punto de entrada es app_main, y
es la función que el framework llama al arrancar.
void app_main(void)
{
// your code goes here
}
Lo primero es inicializar la NVS, porque el driver de WiFi guarda ahí sus parámetros de calibración. Si la partición está llena o quedó de una versión anterior del framework, se borra y se vuelve a inicializar:
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);
Después se levanta la pila TCP/IP y el bucle de eventos por defecto:
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap() crea la interfaz de red en modo AP. Esta
llamada hace más de lo que parece, y es la razón de un detalle que aparece más
adelante: además de la interfaz, levanta un servidor DHCP que le asigna
direcciones a quien se conecte. Por eso el ESP32 queda en 192.168.4.1 y los
clientes reciben 192.168.4.2, .3, etc. Esa subred es el valor por defecto de
ESP IDF; se puede cambiar deteniendo el DHCP con esp_netif_dhcps_stop(),
fijando otra IP y volviéndolo a iniciar.
ap_netif = esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
Se registra el manejador de eventos para toda la familia WIFI_EVENT:
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
Y se arma la configuración del punto de acceso:
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,
},
};
Dos campos merecen atención. channel = 1 fija el canal de la banda de 2,4 GHz;
en un lugar con muchas redes alrededor conviene probar el 6 o el 11, que son los
otros dos canales que no se solapan. Y max_connection = 4 limita los clientes
simultáneos: el ESP32 admite hasta 10, pero cada cliente consume RAM, así que
subir ese número sin necesidad solo te deja con menos memoria para tu propia
aplicación.
La validación que evita un reinicio en bucle
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)");
}
Este bloque es la parte más valiosa del ejemplo y la que más dolores de cabeza
ahorra. WPA2 exige una clave de al menos 8 caracteres. Si le pasas una más
corta, el driver de WiFi falla al aplicar la configuración, ESP_ERROR_CHECK
aborta y la placa entra en un ciclo de reinicios que desde afuera parece un
problema de hardware. En vez de eso, el código detecta el caso y baja la red a
abierta (WIFI_AUTH_OPEN) dejando una advertencia en el log. Lo mismo con el
SSID: vacío o de más de 32 caracteres, y sale con un error claro.
Finalmente se aplica el modo AP, se arranca la radio y se levanta el servidor:
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...");
}
Paso 4: compilar y grabar en la placa
Todo el flujo se maneja desde la barra inferior de VS Code. Si usaste el asistente, lo más probable es que ya esté bien configurado; si no, revisa estas cuatro cosas en orden.
Primero, el método de grabado: haz clic en el ícono correspondiente y elige UART.

Con la placa conectada, haz clic en el ícono del enchufe y selecciona el puerto COM correcto.

Luego el chip de destino, con el ícono del circuito integrado.

Ya con eso configurado, compila con el ícono de la llave inglesa (Build Project).

La primera compilación demora bastante más que las siguientes, porque construye todo el framework desde cero. Al terminar debería aparecer el mensaje de compilación exitosa en la terminal.
Ahora graba el firmware con el botón del rayo (Flash Device). Según la placa, puede que tengas que mantener presionado el botón BOOT para que entre en modo de grabado. Al terminar aparece el aviso Flash Done.

Paso 5: probar el servidor
Abre el monitor serial con la herramienta Monitor Device (el ícono de pantalla). El ESP32 levanta la interfaz en modo AP e imprime su dirección IP. Anótala: es la que vas a escribir en el navegador.

Si el monitor no muestra nada, presiona el botón EN (reset) de la placa: es normal cuando el monitor se abre después de que el chip ya arrancó y te perdiste los primeros mensajes.
Ahora conecta tu computador o celular a la red del ESP32: abre el menú WiFi,
elige la red ESP32_AP e ingresa la clave (password123 si no la cambiaste).

Con la conexión establecida, abre el navegador y escribe la IP:
http://192.168.4.1. Deberías ver la página servida por el ESP32.

Al mismo tiempo, el monitor serial imprime la MAC del dispositivo que se conectó.

Errores comunes y cómo depurarlos
- La placa se reinicia en bucle apenas arranca. Casi siempre es la clave de menos de 8 caracteres. El código de esta guía ya lo controla, pero si partiste de otro ejemplo, revisa eso primero.
- La red aparece pero el navegador no carga nada. Verifica que estás usando
http://y nohttps://. El servidor de este ejemplo no maneja TLS, y muchos navegadores fuerzan HTTPS por defecto. - El celular se conecta y se desconecta solo. Android e iOS detectan que la red no tiene salida a internet y a veces vuelven a los datos móviles. Hay que aceptar el aviso de "mantener conexión" para que no te saque.
- No aparece nada en el monitor serial. Presiona EN, y confirma que el cable USB es de datos y no solo de carga: es la causa más frecuente y la más fácil de descartar.
Failed to start HTTP server. Suele ser falta de memoria. El log de arranque imprime el heap libre; si está muy bajo, revisa qué más estás levantando antes del servidor.
Una nota sobre seguridad antes de dejarlo instalado
El ejemplo trae ESP32_AP y password123 para que puedas probarlo de
inmediato, pero eso no debería sobrevivir a la etapa de prototipo. Si el equipo
va a quedar funcionando en algún lado, cambia ambos valores: cualquiera que vea
un tutorial con estas credenciales por defecto puede conectarse a tu red.
Ten presente además que el servidor entrega la página por HTTP plano, sin cifrado a nivel de aplicación. La protección real la da WPA2 en la capa WiFi, así que dejar la red abierta (que es lo que hace el código automáticamente cuando la clave es muy corta) significa que el tráfico viaja sin ninguna protección. Para un proyecto que solo muestra una lectura ambiental da lo mismo; para uno que acciona un relé conectado a la red eléctrica, no.
Por último, este servidor no tiene autenticación: cualquiera que esté conectado al AP puede abrir la página y, si agregas las rutas de control de las variantes, accionar las salidas. Si eso importa en tu caso, el paso siguiente es agregar una verificación de token en el manejador antes de ejecutar la acción.
Variantes y mejoras
El ejemplo entrega una página estática, pero la estructura ya está lista para crecer. Tres extensiones concretas:
- Panel de control de un relé. Registra dos rutas más (
/ony/off) conhttpd_register_uri_handlery en cada manejador cambia un GPIO congpio_set_level(). Agrega dos botones a la página HTML apuntando a esas rutas y ya tienes un control de encendido accesible desde el celular, sin router. - Mostrar la lectura de un sensor. Con un DHT22 conectado a un GPIO puedes
armar la página en tiempo de ejecución con
snprintf()en vez de servir una cadena fija, e inyectar temperatura y humedad en el HTML. Combinado con lo anterior, en la misma página tienes lectura y control. - Portal de configuración WiFi. Es el uso más habitual del modo AP en
productos reales: el equipo parte como Access Point, muestra un formulario
donde el usuario escribe la red y la clave de su casa, las guarda en NVS y
reinicia en modo estación. El
nvs_flash_init()que ya está en el código es justamente la pieza que te permite guardar esos datos.
Personalización para Chile
Este proyecto no necesita componentes externos: basta la placa, el cable y, si quieres dejarlo montado, una protoboard. En MechatronicStore puedes conseguir todo lo necesario con despacho local:
- Placa ESP32 DevKit V1. la opción más común y económica para partir. El
código de esta guía funciona tal cual; solo tienes que elegir
esp32en vez deesp32s3como target del proyecto. - Placa ESP32-S3. si quieres seguir el ejemplo exactamente como está escrito, incluido el grabado por USB JTAG integrado que evita el botón BOOT.
- Cable USB de datos. el que corresponda a tu placa (micro USB en las DevKit V1 clásicas, USB-C en las más nuevas). Asegúrate de que sea de datos.
- Protoboard 830 puntos. opcional, para dejar la placa firme mientras trabajas y tener espacio para los sensores de las variantes de arriba.
Si el tutorial original menciona una placa de otra marca, cualquier ESP32 del catálogo cumple la misma función: lo que importa es el chip, no el fabricante de la placa. Consulta precios y stock actualizados directamente en el catálogo.
Recursos
- Tutorial original: ESP IDF: ESP32 Web Server Access Point (AP) Mode
- Repositorio del framework: espressif/esp idf
- Documentación del servidor HTTP: ESP HTTP Server API Reference
- Documentación de WiFi (modo AP): WiFi Driver API Guide
Guía inspirada en el tutorial de Random Nerd Tutorials, reescrita y ampliada con la comparación entre modos STA y AP, la explicación del servidor DHCP interno, la sección de errores frecuentes y tres variantes de extensión que el original no cubre. Versión chilena con componentes en stock local en MechatronicStore.




