Saltar al contenido

Cómo crear una web en ESP32 para ver sensores y controlar GPIO (paso a paso)

Objetivo: montar un servidor web en un ESP32 (30 pines / ESP-WROOM-32) que sirva una página HTML y permita:

  • Ver lecturas de sensores desde el navegador.
  • Controlar salidas (GPIO) (por ejemplo, encender/apagar un LED o un relé).

La idea base es separar en dos capas:

  1. UI (navegador): una página HTML con JavaScript.
  2. API (ESP32): endpoints HTTP que devuelven/reciben datos en JSON.

1) Qué vas a construir (arquitectura)

Componentes

  • ESP32 conectado a tu Wi-Fi.
  • Servidor HTTP escuchando en el puerto 80.
  • Página web servida por el ESP32.
  • API REST:
    • GET /api/sensors → devuelve lecturas en JSON.
    • POST /api/gpio → recibe comandos y cambia pines.

Por qué esta arquitectura

  • Es simple para empezar.
  • Es escalable: cambiar sensores o añadir nuevos endpoints no obliga a rehacer la web.

2) Material y requisitos

Hardware mínimo

  • 1× ESP32 DevKit V1 (30 pines)
  • 1× LED (o relé) + resistencia si procede
  • 1× sensor (para el ejemplo: lectura analógica por ADC1, p. ej. LDR)

Recomendación: usa ADC1 (GPIO 32–39) si vas a leer analógico con Wi-Fi activo.

Software

  • Arduino IDE
  • Soporte de placas ESP32 en Arduino IDE
  • Librerías:
    • ESPAsyncWebServer
    • AsyncTCP
    • ArduinoJson

3) Paso a paso en Arduino IDE

Paso 3.1 — Instalar soporte ESP32

  1. En Arduino IDE, abre Archivo → Preferencias.
  2. En Gestor de URLs Adicionales de Tarjetas, añade la URL del paquete de ESP32.
  3. Ve a Herramientas → Placa → Gestor de tarjetas.
  4. Busca ESP32 e instala el paquete.

Paso 3.2 — Instalar librerías

  1. Abre Programa → Incluir librería → Gestionar bibliotecas.
  2. Instala:
    • ESPAsyncWebServer
    • AsyncTCP
    • ArduinoJson

Nota: si Arduino IDE no encuentra ESPAsyncWebServer desde el gestor, instálala desde el repositorio oficial de la librería (ZIP) e importa con Añadir biblioteca .ZIP.

Paso 3.3 — Seleccionar placa y puerto

  1. Conecta el ESP32 por USB.
  2. En Herramientas → Placa, selecciona tu modelo (por ejemplo DOIT ESP32 DEVKIT V1).
  3. En Herramientas → Puerto, selecciona el COM correspondiente.

4) Definir los endpoints (API)

Antes de programar, define qué datos moverás.

Endpoint 1: Sensores

  • GET /api/sensors
  • Respuesta JSON ejemplo:
{
  "adc": 1234,
  "uptime": 456789
}

Endpoint 2: Control GPIO

  • POST /api/gpio
  • Cuerpo JSON ejemplo:
{
  "pin": 2,
  "state": 1
}
  • Respuesta JSON:
{ "ok": true }

5) Código completo (ESP32 + Web)

Este ejemplo:

  • Conecta el ESP32 a tu Wi-Fi.
  • Publica una página HTML.
  • Expone /api/sensors y /api/gpio.
  • La web actualiza lecturas cada 1 segundo.

Ajusta WIFI_SSID y WIFI_PASS.

#include <WiFi.h>
#include <ESPmDNS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>

// ====== WiFi ======
static const char* WIFI_SSID = "TU_WIFI";
static const char* WIFI_PASS = "TU_PASSWORD";

// ====== Pines ======
static const int PIN_LED = 2;   // LED/relé (salida)
static const int PIN_ADC = 34;  // ADC1 recomendado (GPIO34 solo entrada)

// ====== Servidor ======
AsyncWebServer server(80);

// HTML embebido (luego podrás migrarlo a LittleFS)
static const char INDEX_HTML[] PROGMEM = R"HTML(
<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Panel IoT ESP32</title>
  <style>
    body { font-family: system-ui, Arial; margin: 16px; }
    .card { border: 1px solid #ddd; border-radius: 10px; padding: 14px; max-width: 520px; }
    button { padding: 10px 12px; margin-right: 8px; cursor: pointer; }
    code { background: #f6f6f6; padding: 2px 6px; border-radius: 6px; }
    .row { margin: 10px 0; }
  </style>
</head>
<body>
  <h1>Panel IoT (ESP32)</h1>

  <div class="card">
    <h2>Sensores</h2>
    <div class="row">ADC: <code id="adc">-</code></div>
    <div class="row">Uptime (ms): <code id="uptime">-</code></div>

    <h2>Control</h2>
    <button onclick="setGpio(2,1)">LED ON</button>
    <button onclick="setGpio(2,0)">LED OFF</button>

    <p style="margin-top:12px;">
      Actualiza cada 1s usando <code>fetch('/api/sensors')</code>.
    </p>
  </div>

<script>
async function refreshSensors(){
  try{
    const res = await fetch('/api/sensors', { cache: 'no-store' });
    const data = await res.json();
    document.getElementById('adc').textContent = data.adc;
    document.getElementById('uptime').textContent = data.uptime;
  }catch(e){
    console.log('Error leyendo sensores', e);
  }
}

async function setGpio(pin, state){
  try{
    const res = await fetch('/api/gpio', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ pin, state })
    });
    const data = await res.json();
    console.log('GPIO', data);
    refreshSensors();
  }catch(e){
    console.log('Error controlando GPIO', e);
  }
}

setInterval(refreshSensors, 1000);
refreshSensors();
</script>
</body>
</html>
)HTML";

static String jsonOK(bool ok) {
  StaticJsonDocument<64> doc;
  doc["ok"] = ok;
  String out;
  serializeJson(doc, out);
  return out;
}

void setupRoutes() {
  // Página principal
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
    request->send_P(200, "text/html; charset=utf-8", INDEX_HTML);
  });

  // API: sensores
  server.on("/api/sensors", HTTP_GET, [](AsyncWebServerRequest* request) {
    const int adc = analogRead(PIN_ADC);
    const uint32_t uptime = millis();

    StaticJsonDocument<256> doc;
    doc["adc"] = adc;
    doc["uptime"] = uptime;

    String out;
    serializeJson(doc, out);
    request->send(200, "application/json; charset=utf-8", out);
  });

  // API: control GPIO (POST JSON)
  server.on("/api/gpio", HTTP_POST,
    [](AsyncWebServerRequest* request) {},
    nullptr,
    [](AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
      StaticJsonDocument<128> doc;
      DeserializationError err = deserializeJson(doc, data, len);
      if (err) {
        request->send(400, "application/json; charset=utf-8", jsonOK(false));
        return;
      }

      int pin = doc["pin"] | -1;
      int state = doc["state"] | 0;

      if (pin < 0) {
        request->send(400, "application/json; charset=utf-8", jsonOK(false));
        return;
      }

      // Seguridad: limita pines controlables
      if (pin == PIN_LED) {
        digitalWrite(PIN_LED, state ? HIGH : LOW);
        request->send(200, "application/json; charset=utf-8", jsonOK(true));
      } else {
        request->send(403, "application/json; charset=utf-8", jsonOK(false));
      }
    }
  );

  server.onNotFound([](AsyncWebServerRequest* request) {
    request->send(404, "text/plain; charset=utf-8", "404 - Not Found");
  });
}

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

  pinMode(PIN_LED, OUTPUT);
  digitalWrite(PIN_LED, LOW);

  // ADC (12 bits: 0..4095)
  analogReadResolution(12);

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.print("Conectando a WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }

  Serial.println();
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());

  // mDNS (si tu red lo soporta)
  if (MDNS.begin("esp32")) {
    Serial.println("mDNS activo: http://esp32.local");
  }

  setupRoutes();
  server.begin();
  Serial.println("Servidor HTTP iniciado");
}

void loop() {
  // AsyncWebServer no requiere loop activo
}

6) Cómo probarlo

  1. Sube el sketch al ESP32.
  2. Abre el Monitor Serie a 115200.
  3. Copia la IP que aparece (por ejemplo 192.168.1.50).
  4. Entra desde tu móvil o PC a: http://192.168.1.50/.
  5. Prueba:
    • Ver cómo cambian ADC y uptime.
    • Pulsar LED ON y LED OFF.

7) Ajustes típicos y errores comunes

7.1 Si el LED no responde

  • No todas las placas usan GPIO2 como LED integrado.
  • Cambia PIN_LED a otro pin (por ejemplo 4, 5, 18, 19, 21, 22, 23) y conecta un LED externo.

7.2 Si el ADC da valores raros

  • Asegúrate de usar ADC1 (GPIO 32–39).
  • GPIO34/35 son solo entrada, correcto para lectura.
  • Revisa el divisor de tensión si usas LDR.

7.3 Si no abre la web

  • Comprueba que el móvil/PC está en la misma red Wi-Fi.
  • Revisa firewall del router (raro en LAN, pero posible).

8) Evolución del proyecto (siguiente nivel)

Cuando esto esté estable, los siguientes pasos naturales son:

  1. Añadir sensores reales (DHT22, BME280, DS18B20) y ampliar el JSON:
    • {"temp":..., "hum":..., "adc":..., "uptime":...}
  2. Servir archivos desde LittleFS
    • Mover HTML/JS/CSS a ficheros para una web más profesional.
  3. Tiempo real con WebSocket
    • En lugar de setInterval(fetch...), enviar datos “push” al navegador.
  4. Persistencia de configuración
    • Guardar SSID/clave o parámetros con Preferences o un JSON en LittleFS.
  5. Seguridad básica
    • Autenticación (por ejemplo Basic Auth) y listas blancas de pines.
    • Evitar exponer el ESP32 a Internet sin VPN/proxy seguro.

9) Snippet para adaptar a tu blog (infogonzalez)

Entradilla sugerida:

En esta guía voy a montar un panel web minimalista alojado en un ESP32, capaz de mostrar lecturas de sensores y controlar GPIO desde cualq

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Información básica sobre protección de datos Ver más

  • Responsable: Tomas Gonzalez.
  • Finalidad:  Moderar los comentarios.
  • Legitimación:  Por consentimiento del interesado.
  • Destinatarios y encargados de tratamiento:  No se ceden o comunican datos a terceros para prestar este servicio.
  • Derechos: Acceder, rectificar y suprimir los datos.
  • Información Adicional: Puede consultar la información detallada en la Política de Privacidad.

error: Content is protected !!
Este sitio web utiliza cookies, si necesitas más información puedes visitar nuestra política de privacidad    Ver
Privacidad