domingo, 9 de agosto de 2026

Encender LED del ESP32-C3 desde página web

Muy buenas a todos y todas!!!

En este post quiero explicar como crear una pagina web dentro de nuestro ESP32C3.
La idea es un proyecto sencillo pero completo que te permita encender y apagar el LED integrado de tu ESP32-C3 desde cualquier navegador.

Como extra, en la pantalla integrada en el ESP32 también nos indicará el estado del LED On o Off

Voy a intentar explicar cada parte del programa por separado antes de ver el programa completo.


Sistema de archivos

El ESP32 dispone de varios métodos para almacenar una página web, desde una pequeña, como en este ejemplo, donde usaremos PROGMEM o usar la memoria Flash interna (unos 4MB) para almacenar el HTML, CSS o registros.

PROGMEM, LittleFS, SPIFFS o incluso las tarjetas SD son las opciones de almacenamiento más comunes:


Sistema de archivos Descripción Cuándo usarlo
PROGMEM Los archivos se incluyen como cadenas de texto en el código y se compilan junto con el programa. Para páginas pequeñas y estáticas que no van a cambiar. Es la opción más simple si solo tienes un archivo HTML pequeño.
LittleFS Un sistema de archivos en la flash, diseñado para ser ligero y resistente a cortes de energía. Es la opción más equilibrada para la mayoría de los proyectos. Soporta archivos, carpetas y es fácil de usar con un plugin en el IDE de Arduino.
Puedes ver un ejemplo de como usar LittleFS aquí.
SPIFFS El predecesor de LittleFS. También funciona en la flash, pero es menos eficiente y ya no se recomienda para nuevos proyectos. Para mantener compatibilidad con código antiguo. Se recomienda migrar a LittleFS.
Tarjeta SD Usa una tarjeta microSD externa conectada al ESP32 por SPI o SDIO. Para proyectos que necesitan almacenar muchos archivos (imágenes, vídeos, logs) o cuando el espacio en la flash es insuficiente.


🛠️ Componentes necesarios
ESP32-C3 con OLED de 0.42, Antena Cerámica, Wifi, Bluetooth
ESP32-C3OLED de 0.42" Placa de Desarrollo ESP32-C3 con OLED de 0.42", Antena Cerámica, Wifi, Bluetooth.

¿Qué necesitamos saber primero?

El LED integrado en placa o LED_BUILTIN

En la mayoría de las placas basadas en el ESP32-C3, el LED integrado está conectado al GPIO 8.

En algunas placas como la XIAO ESP32-C3, puede estar en el GPIO 21, consulta la documentación de tu placa para confirmar.

Nota: Este pin esta invertido, es decir que para apagar hay que ponerlo en estado HIGH y para encender el LED hay que ponerlo en LOW.


Librerías para el proyecto

Una librería Arduino es un paquete de código que simplifica la programación.
Además nos proporciona funciones y ejemplos para controlar hardware complejo como sensores, pantallas o motores sin necesidad de conocer los detalles técnicos internos.


Librería ¿Para qué sirve?
WiFi.h Permite que el ESP32-C3 se conecte a tu red o cree su propio punto de acceso
WebServer.h Crea un servidor HTTP en el ESP32-C3 para servir páginas web y recibir peticiones

Explicación del código

Paso 1: Incluir las librerías y definir credenciales


#include <WiFi.h>
#include <WebServer.h>
#include <U8g2lib.h>


// Credenciales de tu red Wi-Fi
const char* ssid = "TU_SSID";
const char* password = "TU_CONTRASEÑA";

Librerías:

#include <WiFi.h> nos da acceso a las funciones de conectividad inalámbrica.
#include <WebServer.h> nos permite crear un servidor web que escuche peticiones HTTP en el puerto 80 que es el estándar para páginas web.
Gestiona peticiones GET y POST, y se configura fácilmente asignando funciones a diferentes rutas URL


Credenciales:

const char* ssid = "TU_SSID"; El nombre de tu red domestica.
const char* password = "TU_CONTRASEÑA"; La contraseña de la red.


Pantalla:

#include <U8g2lib.h> Puedes revisar la información sobre como usar la pantalla 0.42" aquí

Paso 2: Configurar el LED y el servidor


// El LED integrado en la mayoría de placas ESP32-C3 está en el GPIO 8
const int LED_PIN = 8;

// Creamos el objeto del servidor web en el puerto 80
WebServer server(80);

// Variable para guardar el estado del LED
bool ledState = false;

Definir el LED:

const int LED_PIN = 8; El pin GPIO8 sera el LED.


Crear el servidor:

WebServer server(80); crea un servidor que escuchará peticiones HTTP en el puerto 80. Cuando un navegador se conecte a la IP del ESP32, el servidor procesará la petición


Paso 3: La página HTML (el "frontend")

El código de la página web se guarda como una cadena de texto en el programa del ESP32. Cuando alguien visita la IP del ESP32, el servidor envía este HTML al navegador




const char HTML_PAGE[] PROGMEM = R"HTML(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Control LED ESP32-C3</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      margin: 0;
      background: #1a1a2e;
      color: #eee;
    }
    button {
      padding: 20px 50px;
      font-size: 1.5rem;
      border: none;
      border-radius: 10px;
      cursor: pointer;
      margin: 10px;
    }
    .btn-on {
      background: #4CAF50;
      color: white;
    }
    .btn-off {
      background: #f44336;
      color: white;
    }
    .btn-on:hover { background: #45a049; }
    .btn-off:hover { background: #d32f2f; }
    .estado {
      font-size: 1.2rem;
      margin: 20px;
      padding: 10px 30px;
      border-radius: 20px;
      background: #16213e;
    }
  </style>
</head>
<body>
  <h1>⚡ Control LED</h1>
  <div class="estado" id="estadoLED">
    LED: <span id="estadoTexto">Desconocido</span>
  </div>
  <div>
    <button class="btn-on" onclick="controlarLED('on')">🔆 ENCENDER</button>
    <button class="btn-off" onclick="controlarLED('off')">🔅 APAGAR</button>
  </div>

  <script>
    // Función que se ejecuta al hacer clic en un botón
    function controlarLED(accion) {
      // Envía una petición GET al ESP32: /led?accion=on o /led?accion=off
      fetch('/led?accion=' + accion)
        .then(response => response.text())
        .then(data => {
          // Actualiza el texto del estado con la respuesta del ESP32
          document.getElementById('estadoTexto').textContent = data;
        });
    }

    // Al cargar la página, consulta el estado actual del LED
    window.onload = function() {
      fetch('/estado')
        .then(response => response.text())
        .then(data => {
          document.getElementById('estadoTexto').textContent = data;
        });
    };
  </script>
</body>
</html>
)HTML";


Explicación del código HTML/JavaScript:

  1. La página tiene dos botones: "ENCENDER" y "APAGAR".

  2. Al hacer clic, la función JavaScript controlarLED() envía una petición fetch() a la URL /led?accion=on o /led?accion=off.

  3. El ESP32 recibe esta petición, cambia el estado del LED y responde con el nuevo estado.

  4. También hay una función que consulta el estado actual al cargar la página (window.onload).


Paso 4: Los "handlers" (el "backend")

Los Handlers o controladores son funciones que se ejecutan en respuesta a un evento específico.
En el contexto de un servidor web en un ESP32, un handler es una función que se asigna a una ruta URL por ejemplo, /led y se ejecuta automáticamente cuando el servidor recibe una petición HTTP a esa dirección, procesando la solicitud y generando una respuesta.


// Maneja la petición a la raíz "/" enviando la página HTML
void handleRoot() {
  server.send(200, "text/html", HTML_PAGE);
}

// Maneja la petición para cambiar el estado del LED
void handleLED() {
  String accion = server.arg("accion"); // Obtiene el valor del parámetro "accion"

  if (accion == "on") {
    digitalWrite(LED_PIN, LOW);  // Enciende el LED (recordemos que el pin esta invertido)
    ledState = true;
    printScreen = true;
    server.send(200, "text/plain", "ENCENDIDO");
  } else if (accion == "off") {
    digitalWrite(LED_PIN, HIGH);   // Apaga el LED
    ledState = false;
    printScreen = true;
    server.send(200, "text/plain", "APAGADO");
  } else {
    server.send(400, "text/plain", "Acción no válida");
  }
}

// Maneja la petición para consultar el estado actual
void handleEstado() {
  if (ledState) {
    server.send(200, "text/plain", "ENCENDIDO");
  } else {
    server.send(200, "text/plain", "APAGADO");
  }
}

Explicación de los handlers:

handleRoot(): Cuando alguien visita la URL raíz (/), el servidor envía el código HTML que hemos guardado.

handleLED(): Cuando se visita /led?accion=on, el servidor extrae el valor del parámetro accion, enciende o apaga el LED físicamente con digitalWrite(), y responde con el nuevo estado.

handleEstado(): Cuando se visita /estado, el servidor responde con el estado actual del LED sin modificarlo.



Paso 5: Configuración inicial (setup)

El setup() de nuestro programa es el "director de orquesta" que prepara todo el escenario: configura el LED, conecta el ESP32-C3 a la red Wi-Fi, asigna las rutas del servidor web y lo pone en marcha.
Sin esta configuración inicial, el servidor no sabría qué hacer cuando reciba una petición HTTP, ni el LED estaría listo para ser controlado.


void setup() {
  Serial.begin(115200);

  // Configurar el pin del LED como salida
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW); // Inicia apagado

  // Conectar a la red Wi-Fi
  WiFi.begin(ssid, password);
  Serial.print("Conectando a Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConectado!");
  Serial.print("Dirección IP: ");
  Serial.println(WiFi.localIP());

  // Asignar funciones a las rutas del servidor
  server.on("/", handleRoot);
  server.on("/led", handleLED);
  server.on("/estado", handleEstado);

  // Iniciar el servidor
  server.begin();
  Serial.println("Servidor web iniciado.");
}


Paso 6: El bucle principal (loop)

El loop() en nuestro programa del LED es el "guardia de la puerta" que está siempre atento.
Se ejecuta sin parar, y su única misión es llamar a server.handleClient() para asegurarse de que cualquier petición web que llegue sea atendida al instante.
Sin él, el ESP32-C3 no podría responder a los clics en los botones de la página web, y el proyecto dejaría de funcionar.


void loop() {
  server.handleClient(); // Atiende las peticiones HTTP entrantes
}

Explicación:

El método server.handleClient() es el que escucha y procesa las peticiones HTTP.
Si no se llama continuamente, el servidor no responderá a las peticiones del navegador


Programa completo

Aquí tienes el código completo listo para copiar y pegar en la Arduino IDE:


/*
  Control del LED integrado del ESP32-C3 desde página web
  Basado en los ejemplos de Fab Academy y proyectos de la comunidad
  Fuente: Fab Academy 2026, Week 15 [citation:12]
*/

#include <WiFi.h>
#include <WebServer.h>
#include <U8g2lib.h>


// ===== CONFIGURACIÓN =====
// Reemplaza con las credenciales de tu red Wi-Fi
const char* ssid = "TU_SSID";
const char* password = "TU_CONTRASEÑA";

// ========== PANTALLA ==========
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 6, 5);
const int X_OFFSET = 28;
const int Y_OFFSET = 24;
bool printScreen = true;

// En la mayoría de placas ESP32-C3, el LED integrado está en GPIO 8
// Si usas XIAO ESP32-C3, usa GPIO 21
const int LED_PIN = 8;

// ===== VARIABLES GLOBALES =====
WebServer server(80);
bool ledState = false;

// ===== PÁGINA HTML =====
const char HTML_PAGE[] PROGMEM = R"HTML(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Control LED ESP32-C3</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      margin: 0;
      background: #1a1a2e;
      color: #eee;
    }
    h1 {
      font-weight: 300;
      letter-spacing: 0.1em;
    }
    .estado {
      font-size: 1.2rem;
      margin: 20px;
      padding: 10px 30px;
      border-radius: 20px;
      background: #16213e;
    }
    .botones {
      display: flex;
      gap: 20px;
      flex-wrap: wrap;
      justify-content: center;
    }
    button {
      padding: 20px 50px;
      font-size: 1.5rem;
      border: none;
      border-radius: 12px;
      cursor: pointer;
      transition: transform 0.2s, box-shadow 0.2s;
      min-width: 150px;
    }
    button:active {
      transform: scale(0.95);
    }
    .btn-on {
      background: #4CAF50;
      color: white;
      box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);
    }
    .btn-on:hover {
      background: #45a049;
      box-shadow: 0 6px 20px rgba(76, 175, 80, 0.5);
    }
    .btn-off {
      background: #f44336;
      color: white;
      box-shadow: 0 4px 15px rgba(244, 67, 54, 0.3);
    }
    .btn-off:hover {
      background: #d32f2f;
      box-shadow: 0 6px 20px rgba(244, 67, 54, 0.5);
    }
    .footer {
      margin-top: 30px;
      font-size: 0.8rem;
      color: #666;
    }
  </style>
</head>
<body>
  <h1>⚡ Control LED</h1>
  <div class="estado" id="estadoLED">
    LED: <span id="estadoTexto">Desconocido</span>
  </div>
  <div class="botones">
    <button class="btn-on" onclick="controlarLED('on')">🔆 ENCENDER</button>
    <button class="btn-off" onclick="controlarLED('off')">🔅 APAGAR</button>
  </div>
  <div class="footer">ESP32-C3 - Servidor Web</div>

  <script>
    function controlarLED(accion) {
      fetch('/led?accion=' + accion)
        .then(response => response.text())
        .then(data => {
          document.getElementById('estadoTexto').textContent = data;
        })
        .catch(error => {
          console.error('Error:', error);
          document.getElementById('estadoTexto').textContent = 'Error';
        });
    }

    window.onload = function() {
      fetch('/estado')
        .then(response => response.text())
        .then(data => {
          document.getElementById('estadoTexto').textContent = data;
        })
        .catch(error => {
          console.error('Error:', error);
          document.getElementById('estadoTexto').textContent = 'Error de conexión';
        });
    };
  </script>
</body>
</html>
)HTML";

// ===== HANDLERS DEL SERVIDOR =====

void handleRoot() {
  server.send(200, "text/html", HTML_PAGE);
}

void handleLED() {
  String accion = server.arg("accion");

  if (accion == "on") {
    digitalWrite(LED_PIN, LOW);
    ledState = true;
    printScreen = true;
    server.send(200, "text/plain", "ENCENDIDO");
  } else if (accion == "off") {
    digitalWrite(LED_PIN, HIGH);
    ledState = false;
    printScreen = true;
    server.send(200, "text/plain", "APAGADO");
  } else {
    server.send(400, "text/plain", "Acción no válida");
  }
}

void handleEstado() {
  if (ledState) {
    server.send(200, "text/plain", "ENCENDIDO");
  } else {
    server.send(200, "text/plain", "APAGADO");
  }
}

void handleNotFound() {
  server.send(404, "text/plain", "Página no encontrada");
}

// ===== SETUP =====
void setup() {
  delay(200);
  Serial.begin(115200);
  delay(200);
  
  // Configurar pantalla
  u8g2.begin();
  u8g2.setContrast(200);

  // Configurar LED
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  // Conectar a Wi-Fi
  WiFi.begin(ssid, password);
  Serial.print("Conectando a Wi-Fi");
  
  int intentos = 0;
  while (WiFi.status() != WL_CONNECTED && intentos < 30) {
    delay(500);
    Serial.print(".");
    intentos++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\n✅ Conectado!");
    Serial.print("📡 Dirección IP: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("\n❌ No se pudo conectar a Wi-Fi");
    Serial.println("Verifica las credenciales en el código");
  }

  // Configurar rutas del servidor
  server.on("/", handleRoot);
  server.on("/led", handleLED);
  server.on("/estado", handleEstado);
  server.onNotFound(handleNotFound);

  // Iniciar servidor
  server.begin();
  Serial.println("🌐 Servidor web iniciado en el puerto 80");
  Serial.println("Abre un navegador y visita la IP indicada");
}

// ===== LOOP =====
void loop() {
  server.handleClient();
  if (printScreen)
  {
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_6x10_tf);
    if (ledState)
      u8g2.drawStr(X_OFFSET + 10, Y_OFFSET + 20, "ON");
  	else
      u8g2.drawStr(X_OFFSET + 10, Y_OFFSET + 20, "OFF");
    u8g2.sendBuffer();
    printScreen = false;
  }
}

Cómo usar el programa

  1. Configura las credenciales Wi-Fi: Reemplaza "TU_SSID" y "TU_CONTRASEÑA" con los datos de tu red.

  2. Verifica el pin del LED: Si tu placa no usa el GPIO 8 (como la XIAO ESP32-C3 que usa GPIO 21), cambia la línea const int LED_PIN = 8; al pin correcto.

  3. Carga el programa a tu ESP32-C3 desde la Arduino IDE.

  4. Abre el Monitor Serie para ver la IP que obtiene el ESP32.

  5. Desde cualquier navegador en la misma red, visita http://[IP_del_ESP32]/.

  6. Haz clic en los botones para encender o apagar el LED.


ESP32C3 Interfaz web led

Resumen de conceptos clave y conclusión

Concepto Explicación
Servidor web El ESP32-C3 actúa como un pequeño servidor que aloja una página web en su memoria.
Peticiones HTTP El navegador envía peticiones GET al ESP32 (ej: /led?accion=on) para controlar el LED.
fetch() Función JavaScript que permite hacer peticiones HTTP sin recargar la página (AJAX).
server.on() Asigna una función a una ruta URL. Cuando alguien visita esa URL, se ejecuta la función correspondiente.
server.handleClient() Escucha y procesa las peticiones HTTP entrantes. Debe llamarse constantemente en el loop().

Ya tenemos todo lo necesario para controlar el LED integrado en el ESP32-C3 funcional desde cualquier navegador.


Saludos a todos y todas!!!


No hay comentarios :