Caso práctico: Ventilación de invernadero con ESP32

Caso práctico: Ventilación de invernadero con ESP32 — hero

Objetivo y caso de uso

Qué construirás: Un servomecanismo controlado por web local que regula la ventilación de un modelo de invernadero. Integra un interruptor de límite para un posicionamiento mecánico preciso en cero para evitar que el motor se atasque y garantizar un accionamiento preciso.

Por qué es importante / Casos de uso

  • Control climático automatizado: Demuestra la capa de accionamiento físico requerida para liberar el exceso de calor y humedad en entornos agrícolas del mundo real.
  • Seguridad de bucle cerrado: La integración del interruptor de límite evita comandos ciegos de bucle abierto, evitando corrientes de bloqueo >1A y rechineo mecánico si los enlaces se deslizan.
  • Control de red local: El ESP32 actúa como un SoftAP, proporcionando control directo desde el teléfono inteligente con una latencia <50ms sin requerir un enrutador externo o una conexión a internet activa.
  • Gestión de estado asíncrona: Los barridos de servo no bloqueantes garantizan que el servidor web procese continuamente solicitudes de clientes concurrentes sin retrasos de ejecución o tiempos de espera agotados.

Resultado esperado

  • Un Punto de Acceso WiFi local activo llamado ESP32-Greenhouse transmitido por el ESP32.
  • Un panel web responsivo accesible en 192.168.4.1 para ordenar ángulos de ventilación específicos (0° a 90°).
  • Retorno a la posición de origen (homing) mecánico automático que calibra de manera confiable la ventilación a una posición 0 física al iniciar o reiniciar el sistema.

Audiencia: Desarrolladores de IoT, Ingenieros AgTech; Nivel: Intermedio

Arquitectura/flujo: Cliente Web (Teléfono inteligente) → ESP32 SoftAP (Servidor Web Asíncrono) → Control PWM → Servomotor ↔ Interruptor de límite (Retroalimentación GPIO).

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. Para este perfil ESP32 DevKitC, el proyecto se comprobó como proyecto PlatformIO: el validador extrajo platformio.ini y src/main.cpp, creó un proyecto temporal y ejecutó pio run contra platform = espressif32, board = esp32dev y framework = arduino. También revisó la estructura del artículo, que los comandos sean copiables con guiones ASCII, y que no aparezcan stacks no soportados como ESP-IDF directo o placas ESP32 no acotadas.

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 4 apartados, 2 tablas y 2 bloques de código detectados antes de publicar.
  • Código comprobado: 1 PlatformIO config + 1 ESP32 source/pio run.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el código publicado, pero no sustituye la prueba física sobre tu placa ESP32 DevKitC exacta, tu cableado, alimentación y entorno WiFi local.

Nota educativa de seguridad

Este proyecto es un prototipo educativo de bajo voltaje, no un producto certificado. Antes de encender la configuración, verifique la distribución de pines de su placa ESP32 DevKitC exacta, mantenga las señales GPIO dentro de los límites de 3.3 V, nunca aplique 5 V a las entradas del ESP32, desconecte la alimentación antes de cambiar el cableado y use fuentes externas adecuadas para relés, motores o cargas, compartiendo GND solo cuando el esquema lo requiera.

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

Botón local

ESP32 BLE

Paquete advertising

LED de estado

Móvil escáner

Flujo conceptual: configuración local, emisión BLE y lectura desde el móvil.

Ruta de validación

Código fuente

PlatformIO build

Flash

Monitor serie

Resumen conceptual de las herramientas usadas para comprobar el proyecto ESP32 publicado.

Requisitos previos

Antes de comenzar este tutorial, asegúrese de tener listo lo siguiente:
* Software: Visual Studio Code con la extensión PlatformIO IDE instalada.
* Conocimientos: Familiaridad básica con la sintaxis de C++, conceptos de GPIO del ESP32 (PWM y Entradas Digitales) y navegación por la interfaz de PlatformIO.
* Herramientas: Un cable micro-USB o USB-C (dependiendo de su variante específica de ESP32 DevKitC) capaz de transferir tanto energía como datos.

Materiales

Para completar este proyecto, necesitará exactamente este modelo de dispositivo y los componentes que lo acompañan:
* ESP32 DevKitC + servo SG90 + interruptor de límite + panel web local (El panel se implementa por software).
* ESP32 DevKitC V4: El microcontrolador central que proporciona capacidades WiFi, generación de PWM y alojamiento del servidor web.
* Micro Servo SG90: Un servomotor estándar de engranajes de plástico de 9g para accionar la ventilación del prototipo.
* Micro interruptor de límite (SPDT o SPST): Un interruptor mecánico con un brazo de palanca utilizado para detectar cuándo la ventilación está completamente cerrada.
* Cables puente (Jumper): Hembra-Hembra o Macho-Hembra dependiendo de la configuración de su protoboard.
* Protoboard (Opcional pero recomendada): Para conexiones seguras entre el ESP32 y los componentes periféricos.

Configuración y conexión

El cableado adecuado es crítico. El ESP32 funciona con una lógica de 3.3V, pero el servo SG90 requiere 5V para un funcionamiento confiable. Utilizaremos el pin 5V (o VIN) en el ESP32 DevKitC, que extrae energía directamente de la conexión USB.

El interruptor de límite se conectará utilizando la resistencia pull-up interna del ESP32. Esto significa que solo necesitamos conectar el interruptor entre el pin GPIO y Tierra (Ground). Cuando el interruptor no está presionado, el pin lee HIGH. Cuando se presiona, se conecta a Tierra y lee LOW.

Tabla de asignación de pines

ComponentePin del componente / Color del cablePin del ESP32 DevKitCDescripción
Servo SG90Marrón (o Negro)GNDTierra común (Ground)
Servo SG90Rojo5V / VINFuente de alimentación de 5V
Servo SG90Naranja (o Amarillo)GPIO 13Señal de control PWM
Interruptor de límiteComún (COM)GNDTierra común (Ground)
Interruptor de límiteNormalmente Abierto (NO)GPIO 14Entrada Digital (Activo LOW)

Nota sobre el cableado del interruptor de límite: Asegúrese de usar el terminal Normalmente Abierto (NO). El circuito se completa (el pin cambia a LOW) solo cuando la ventilación presiona físicamente el brazo del interruptor.

Código validado

El proyecto requiere dos archivos dentro de la estructura de su proyecto PlatformIO: el archivo de configuración (platformio.ini) y el archivo de código fuente principal (src/main.cpp).

platformio.ini

Este archivo configura el entorno de compilación, especifica el framework del ESP32 y descarga automáticamente la biblioteca del servo requerida.

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
; We use the widely supported ESP32Servo library by Kevin Harrington
lib_deps = 
    madhephaestus/ESP32Servo @ ^3.0.5

src/main.cpp

Este archivo contiene la lógica completa para el WiFi SoftAP, el servidor web, el panel HTML y el bucle de control electromecánico.

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>

// Pin Definitions
const int SERVO_PIN = 13;
const int LIMIT_SWITCH_PIN = 14;

// Global Objects
Servo ventServo;
WebServer server(80);

// State Variables
int currentAngle = 0;
String ventState = "Closed";

// WiFi Access Point Credentials
const char* ssid = "ESP32-Greenhouse";
const char* password = "password123"; // Minimum 8 characters

// HTML Dashboard (Stored in Flash Memory)
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Greenhouse Vent Controller</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; background-color: #f4f7f6; }
        h1 { color: #2c3e50; }
        .status { font-size: 1.5em; margin: 20px 0; padding: 10px; background: #fff; border-radius: 8px; display: inline-block; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        .btn { padding: 15px 30px; font-size: 1.2em; margin: 10px; cursor: pointer; border: none; border-radius: 5px; color: white; transition: background 0.3s; }
        .btn-open { background-color: #27ae60; }
        .btn-open:hover { background-color: #2ecc71; }
        .btn-close { background-color: #c0392b; }
        .btn-close:hover { background-color: #e74c3c; }
    </style>
    <script>
        function sendCommand(action) {
            document.getElementById("status-text").innerText = "Moving...";
            fetch('/' + action)
                .then(response => response.text())
                .then(state => {
                    document.getElementById("status-text").innerText = state;
                })
                .catch(err => {
                    document.getElementById("status-text").innerText = "Error connecting to ESP32";
                });
        }

        function updateStatus() {
            fetch('/status')
                .then(response => response.text())
                .then(state => {
                    document.getElementById("status-text").innerText = state;
                });
        }

        // Poll status every 5 seconds to keep dashboard synced
        setInterval(updateStatus, 5000);
    </script>
</head>
<body onload="updateStatus()">
    <h1>Greenhouse Vent Controller</h1>
    <div class="status">Current Status: <strong id="status-text">Loading...</strong></div>
    <br>
    <button class="btn btn-open" onclick="sendCommand('open')">Open Vent (90&deg;)</button>
    <button class="btn btn-close" onclick="sendCommand('close')">Close Vent (0&deg;)</button>
</body>
</html>
)rawliteral";

// --- Motor Control Functions ---

void openVent() {
    Serial.println("Command Received: Open Vent");
    ventState = "Opening...";

    // Smooth sweep to 90 degrees
    for (int pos = currentAngle; pos <= 90; pos++) {
        ventServo.write(pos);
        currentAngle = pos;
        delay(15); 
    }
// ...

🔒 Este contenido es premium. Con la suscripción mensual (7 días gratis) podrás desbloquear el material didáctico completo y el pack PDF listo para imprimir.🔓 Desbloquéalo — 7 días gratis
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>

// Pin Definitions
const int SERVO_PIN = 13;
const int LIMIT_SWITCH_PIN = 14;

// Global Objects
Servo ventServo;
WebServer server(80);

// State Variables
int currentAngle = 0;
String ventState = "Closed";

// WiFi Access Point Credentials
const char* ssid = "ESP32-Greenhouse";
const char* password = "password123"; // Minimum 8 characters

// HTML Dashboard (Stored in Flash Memory)
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Greenhouse Vent Controller</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; background-color: #f4f7f6; }
        h1 { color: #2c3e50; }
        .status { font-size: 1.5em; margin: 20px 0; padding: 10px; background: #fff; border-radius: 8px; display: inline-block; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        .btn { padding: 15px 30px; font-size: 1.2em; margin: 10px; cursor: pointer; border: none; border-radius: 5px; color: white; transition: background 0.3s; }
        .btn-open { background-color: #27ae60; }
        .btn-open:hover { background-color: #2ecc71; }
        .btn-close { background-color: #c0392b; }
        .btn-close:hover { background-color: #e74c3c; }
    </style>
    <script>
        function sendCommand(action) {
            document.getElementById("status-text").innerText = "Moving...";
            fetch('/' + action)
                .then(response => response.text())
                .then(state => {
                    document.getElementById("status-text").innerText = state;
                })
                .catch(err => {
                    document.getElementById("status-text").innerText = "Error connecting to ESP32";
                });
        }

        function updateStatus() {
            fetch('/status')
                .then(response => response.text())
                .then(state => {
                    document.getElementById("status-text").innerText = state;
                });
        }

        // Poll status every 5 seconds to keep dashboard synced
        setInterval(updateStatus, 5000);
    </script>
</head>
<body onload="updateStatus()">
    <h1>Greenhouse Vent Controller</h1>
    <div class="status">Current Status: <strong id="status-text">Loading...</strong></div>
    <br>
    <button class="btn btn-open" onclick="sendCommand('open')">Open Vent (90&deg;)</button>
    <button class="btn btn-close" onclick="sendCommand('close')">Close Vent (0&deg;)</button>
</body>
</html>
)rawliteral";

// --- Motor Control Functions ---

void openVent() {
    Serial.println("Command Received: Open Vent");
    ventState = "Opening...";

    // Smooth sweep to 90 degrees
    for (int pos = currentAngle; pos <= 90; pos++) {
        ventServo.write(pos);
        currentAngle = pos;
        delay(15); 
    }

    ventState = "Open (90 degrees)";
    Serial.println("Vent is fully open.");
}

void closeVent() {
    Serial.println("Command Received: Close Vent");
    ventState = "Closing...";

    // Smooth sweep towards 0 degrees, monitoring the limit switch
    for (int pos = currentAngle; pos >= 0; pos--) {
        // Check if limit switch is pressed (Active LOW)
        if (digitalRead(LIMIT_SWITCH_PIN) == LOW) {
            Serial.println("Limit switch triggered! Mechanical zero reached.");
            currentAngle = pos; // Update current angle to actual stopped position
            ventState = "Closed (Limit Switch Triggered)";
            return; // Exit the loop immediately
        }

        ventServo.write(pos);
        currentAngle = pos;
        delay(15);
    }

    ventState = "Closed (0 degrees reached without switch)";
    Serial.println("Vent closed to 0 degrees.");
}

// --- Web Server Handlers ---

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

void handleOpen() {
    openVent();
    server.send(200, "text/plain", ventState);
}

void handleClose() {
    closeVent();
    server.send(200, "text/plain", ventState);
}

void handleStatus() {
    server.send(200, "text/plain", ventState);
}

// --- Main Setup and Loop ---

void setup() {
    Serial.begin(115200);
    Serial.println("\n--- ESP32 Greenhouse Vent Controller ---");

    // Initialize Limit Switch with internal pull-up
    pinMode(LIMIT_SWITCH_PIN, INPUT_PULLUP);

    // Initialize Servo
    // SG90 requires a 50Hz PWM signal. Standard pulse width is 500us to 2400us.
    ventServo.setPeriodHertz(50);
    ventServo.attach(SERVO_PIN, 500, 2400);

    // Initial calibration: Ensure vent is closed on startup
    Serial.println("Performing initial calibration...");
    closeVent();

    // Setup WiFi Access Point
    Serial.println("Starting WiFi Access Point...");
    WiFi.softAP(ssid, password);
    IPAddress IP = WiFi.softAPIP();
    Serial.print("AP IP address: ");
    Serial.println(IP);

    // Bind Web Server Routes
    server.on("/", handleRoot);
    server.on("/open", handleOpen);
    server.on("/close", handleClose);
    server.on("/status", handleStatus);

    // Start Web Server
    server.begin();
    Serial.println("HTTP server started.");
}

void loop() {
    // Listen for incoming HTTP requests
    server.handleClient();

    // Small delay to yield to the underlying RTOS tasks
    delay(2);
}

Compilar, flashear y ejecutar

Para compilar el código y subirlo a su ESP32 DevKitC, use la CLI de PlatformIO Core. Puede abrir un nuevo terminal en VS Code (Terminal -> New Terminal) y ejecutar los siguientes comandos.

Tabla de comandos

AcciónComandoPropósito
Compilarpio runCompila el código C++ y descarga la biblioteca ESP32Servo.
Flashearpio run --target uploadSube el firmware compilado al ESP32 conectado.
Monitorizarpio device monitorAbre el monitor serie para ver la IP y los registros de depuración.

Flujo de trabajo

  1. Conecte el ESP32 DevKitC a su computadora a través de USB.
  2. Abra el terminal de la CLI de PlatformIO.
  3. Ejecute pio run para verificar que no haya errores de sintaxis y que la biblioteca se descargue correctamente.
  4. Ejecute pio run --target upload para flashear el dispositivo. Nota: En algunas placas DevKitC más antiguas, es posible que deba mantener presionado el botón «BOOT» cuando el terminal muestre Connecting....
  5. Ejecute pio device monitor para observar la secuencia de inicio y recuperar la dirección IP del SoftAP.

Validación paso a paso

Siga estos puntos de control agrupados para verificar que el sistema opera de acuerdo con el objetivo.

1. Inicialización de hardware

  • Acción: Observe el servo inmediatamente después de presionar el botón EN (Reset) en el ESP32.
  • Observación esperada: El servo debería intentar moverse hacia los 0 grados.
  • Condición de aprobación: El monitor serie muestra «Performing initial calibration…» seguido de la finalización de la rutina closeVent().

2. Conectividad de red

  • Acción: Abra la configuración WiFi en su teléfono inteligente o computadora portátil y busque redes.
  • Observación esperada: Una red llamada ESP32-Greenhouse es visible.
  • Condición de aprobación: Puede conectarse a la red utilizando la contraseña password123.

3. Accesibilidad al panel (dashboard)

  • Acción: Abra un navegador web y navegue a http://192.168.4.1.
  • Observación esperada: El panel «Greenhouse Vent Controller» se carga con dos botones y un indicador de estado.
  • Condición de aprobación: El indicador de estado obtiene correctamente el estado actual (por ejemplo, «Closed (Limit Switch Triggered)» o «Closed (0 degrees reached without switch)»).

4. Accionamiento de bucle abierto (Apertura)

  • Acción: Haga clic en el botón «Open Vent (90°)» en el panel.
  • Observación esperada: El servo realiza un barrido suave hacia la posición de 90 grados. El estado del panel se actualiza a «Moving…» y luego a «Open (

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué se construirá según el texto?




Pregunta 2: ¿Cuál es el propósito de integrar un interruptor de límite en el sistema?




Pregunta 3: ¿Qué problema físico previene la seguridad de bucle cerrado?




Pregunta 4: ¿Qué demuestra el control climático automatizado en este caso de uso?




Pregunta 5: ¿Cómo opera el ESP32 dentro de la red local?




Pregunta 6: ¿Cuál es la latencia de control directo desde el teléfono inteligente?




Pregunta 7: ¿Qué tipo de conexión a internet requiere el sistema para funcionar?




Pregunta 8: ¿Qué técnica se utiliza para la gestión de estado asíncrona?




Pregunta 9: ¿Qué ventaja ofrece la gestión de estado asíncrona al servidor web?




Pregunta 10: ¿Qué tipo de comandos evita el uso del interruptor de límite?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio