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-Greenhousetransmitido por el ESP32. - Un panel web responsivo accesible en
192.168.4.1para 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
Flujo conceptual: configuración local, emisión BLE y lectura desde el móvil.
Ruta de validación
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
| Componente | Pin del componente / Color del cable | Pin del ESP32 DevKitC | Descripción |
|---|---|---|---|
| Servo SG90 | Marrón (o Negro) | GND | Tierra común (Ground) |
| Servo SG90 | Rojo | 5V / VIN | Fuente de alimentación de 5V |
| Servo SG90 | Naranja (o Amarillo) | GPIO 13 | Señal de control PWM |
| Interruptor de límite | Común (COM) | GND | Tierra común (Ground) |
| Interruptor de límite | Normalmente Abierto (NO) | GPIO 14 | Entrada 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°)</button>
<button class="btn btn-close" onclick="sendCommand('close')">Close Vent (0°)</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);
}
// ...#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°)</button>
<button class="btn btn-close" onclick="sendCommand('close')">Close Vent (0°)</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ón | Comando | Propósito |
|---|---|---|
| Compilar | pio run | Compila el código C++ y descarga la biblioteca ESP32Servo. |
| Flashear | pio run --target upload | Sube el firmware compilado al ESP32 conectado. |
| Monitorizar | pio device monitor | Abre el monitor serie para ver la IP y los registros de depuración. |
Flujo de trabajo
- Conecte el ESP32 DevKitC a su computadora a través de USB.
- Abra el terminal de la CLI de PlatformIO.
- Ejecute
pio runpara verificar que no haya errores de sintaxis y que la biblioteca se descargue correctamente. - Ejecute
pio run --target uploadpara flashear el dispositivo. Nota: En algunas placas DevKitC más antiguas, es posible que deba mantener presionado el botón «BOOT» cuando el terminal muestreConnecting.... - Ejecute
pio device monitorpara 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-Greenhousees 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
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.




