Objetivo y caso de uso
Qué construirás: Un monitor no invasivo para lavadoras utilizando un ESP32 y un sensor de vibración SW-420 que detecta cuándo termina un ciclo de lavado. Utiliza lógica de tiempo de espera basada en flancos (edge-based) para activar una alarma local con zumbador/LED y una alerta HTTP por WiFi con latencia de subsegundos.
Por qué es importante / Casos de uso
- Evita olvidar la ropa: Proporciona notificaciones inmediatas cuando la máquina se detiene, evitando el moho y la necesidad de volver a lavar.
- Optimiza instalaciones compartidas: Reduce los tiempos de espera y los viajes innecesarios a la lavandería en residencias estudiantiles o edificios de apartamentos.
- Monitorización no invasiva: Se adapta de forma segura a electrodomésticos antiguos «no inteligentes» al depender de vibraciones externas en lugar de circuitos de alto voltaje.
- Lógica de máquina de estados basada en flancos: Enseña lógica de antirrebote (debouncing) y de tiempo de espera (timeout) para manejar las pausas normales de los electrodomésticos (ej. ventanas de inactividad de 3-5 minutos entre ciclos de lavado, enjuague y centrifugado).
Resultado esperado
- Un prototipo funcional con ESP32 que detecta de manera fiable los estados de vibración y filtra las paradas falsas.
- Alerta audiovisual local mediante un zumbador piezoeléctrico y un LED de estado.
- Una notificación HTTP activada por WiFi entregada en < 500ms cuando la máquina completa oficialmente su ciclo.
Audiencia: Aficionados al IoT, estudiantes de electrónica y entusiastas de la domótica; Nivel: Principiante a Intermedio
Arquitectura/flujo: Sensor SW-420 → ESP32 (Máquina de estados con antirrebote y tiempo de espera) → Piezo/LED local y alerta HTTP POST por WiFi
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: 3 apartados, 3 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 el montaje, verifica el esquema de pines (pinout) de tu placa ESP32 DevKitC exacta, mantén las señales de los GPIO dentro de los límites de 3.3 V, nunca apliques 5 V a las entradas del ESP32, desconecta la alimentación antes de cambiar el cableado y utiliza 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: detección de humedad, decisión local y aviso al usuario.
Ruta de validación
Resumen conceptual de las herramientas usadas para comprobar el proyecto ESP32 publicado.
Requisitos previos
Antes de comenzar este proyecto, asegúrate de tener listo lo siguiente:
* Entorno de software: Visual Studio Code (VS Code) con la extensión PlatformIO IDE instalada.
* Conocimientos básicos de C++: Familiaridad con variables, sentencias if/else y la función millis() para retardos no bloqueantes.
* Acceso a la red: Una red WiFi de 2.4GHz con credenciales de SSID y contraseña conocidas.
* Controladores USB: Los controladores USB a UART CP210x o CH34x instalados en tu computadora (dependiendo de tu variante específica de ESP32 DevKitC) para permitir la comunicación serial y el flasheo.
Materiales
Necesitarás los siguientes componentes exactos para este montaje:
* ESP32 DevKitC: El microcontrolador central, que proporciona tanto la lógica GPIO como capacidades WiFi de 2.4GHz integradas.
* Sensor de vibración SW-420: Un módulo de vibración digital que cuenta con un comparador de voltaje LM393 y un potenciómetro integrado para ajustar el umbral.
* Zumbador piezoeléctrico (Piezo buzzer): Un módulo de zumbador piezoeléctrico activo (suena continuamente cuando recibe una señal digital HIGH).
* LED de estado: Un LED estándar de 5mm (de cualquier color, ej. azul o verde) emparejado con una resistencia limitadora de corriente de 220Ω.
* Suministros de prototipado: Una placa de pruebas (breadboard) estándar, varios cables puente (jumper) macho-macho y macho-hembra, y un cable micro-USB para programación y alimentación.
Configuración/Conexión
El sensor SW-420 emite una señal digital: permanece en LOW cuando está quieto, y emite pulsos HIGH cuando la vibración supera el umbral establecido por su potenciómetro integrado. El ESP32 leerá este pin digital.
Realiza las conexiones de acuerdo con el siguiente mapeo. Asegúrate de que tu ESP32 esté desconectado de la alimentación USB mientras realizas el cableado.
| Componente | Pin del componente | Pin del ESP32 DevKitC | Notas |
|---|---|---|---|
| Sensor SW-420 | VCC | 3V3 | Alimenta el comparador LM393. |
| Sensor SW-420 | GND | GND | Tierra común. |
| Sensor SW-420 | DO (Salida Digital) | GPIO 13 | Envía pulsos HIGH durante la vibración. |
| Zumbador piezoeléctrico | VCC / + | GPIO 14 | Se pone en HIGH para hacer sonar la alarma. |
| Zumbador piezoeléctrico | GND / – | GND | Tierra común. |
| LED de estado | Ánodo (Pata larga) | GPIO 27 | Conectar mediante una resistencia de 220Ω. |
| LED de estado | Cátodo (Pata corta) | GND | Tierra común. |
Nota sobre la corriente del zumbador: Los zumbadores piezoeléctricos activos estándar consumen alrededor de 10-30mA, lo cual es seguro para manejar directamente desde un pin GPIO del ESP32 (máx. 40mA por pin). Si estás utilizando una sirena más grande o un zumbador de alta potencia, debes usar un transistor de conmutación (como un 2N2222) o un módulo de relé.
Código validado
El siguiente código implementa una máquina de estados no bloqueante. Para probar la lógica rápidamente, el tiempo de espera del ciclo se establece en 15 segundos. En una implementación en el mundo real, deberías aumentar esto a 3-5 minutos para tener en cuenta las pausas de remojo/vaciado en un ciclo de lavadora.
Crea un nuevo proyecto de PlatformIO para la placa esp32dev y reemplaza el contenido de los archivos generados con el código a continuación.
platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
src/main.cpp
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 <HTTPClient.h>
// ---------------------------------------------------------
// Configuration: WiFi & Webhook
// ---------------------------------------------------------
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
// For demonstration, we use httpbin.org to echo the GET request.
// In a real application, replace this with an IFTTT Webhook,
// Home Assistant endpoint, or custom API URL.
const char* WEBHOOK_URL = "http://httpbin.org/get?laundry=finished";
// ---------------------------------------------------------
// Configuration: Hardware Pins
// ---------------------------------------------------------
const int SW420_PIN = 13;
const int BUZZER_PIN = 14;
const int LED_PIN = 27;
// ---------------------------------------------------------
// Configuration: Timing & State Machine
// ---------------------------------------------------------
// Time required without vibration to consider the cycle "finished".
// Set to 15 seconds (15000ms) for testing.
// For real laundry machines, use 180000ms (3 minutes) or more.
const unsigned long CYCLE_TIMEOUT_MS = 15000;
enum MachineState {
STATE_IDLE,
STATE_RUNNING,
STATE_FINISHED
};
MachineState currentState = STATE_IDLE;
unsigned long lastVibrationTime = 0;
unsigned long lastAlertToggleTime = 0;
bool alertToggleState = false;
// Function prototypes
void connectToWiFi();
void sendWiFiAlert();
void handleAlertHardware();
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n--- Laundry Vibration WiFi Alert ---");
// Initialize pins
pinMode(SW420_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
// Ensure outputs are off initially
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
connectToWiFi();
Serial.println("System initialized. Waiting for vibration...");
}
void loop() {
unsigned long currentMillis = millis();
int vibrationDetected = digitalRead(SW420_PIN);
// 1. Read Sensor & Update Timers
if (vibrationDetected == HIGH) {
lastVibrationTime = currentMillis;
// If we were idle or already finished, a new vibration means a cycle is starting/restarting
if (currentState == STATE_IDLE || currentState == STATE_FINISHED) {
currentState = STATE_RUNNING;
Serial.println("STATUS: Machine is now RUNNING.");
// Ensure alert hardware is turned off when returning to running
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, HIGH); // Solid LED indicates running
}
}
// ...#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
// ---------------------------------------------------------
// Configuration: WiFi & Webhook
// ---------------------------------------------------------
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
// For demonstration, we use httpbin.org to echo the GET request.
// In a real application, replace this with an IFTTT Webhook,
// Home Assistant endpoint, or custom API URL.
const char* WEBHOOK_URL = "http://httpbin.org/get?laundry=finished";
// ---------------------------------------------------------
// Configuration: Hardware Pins
// ---------------------------------------------------------
const int SW420_PIN = 13;
const int BUZZER_PIN = 14;
const int LED_PIN = 27;
// ---------------------------------------------------------
// Configuration: Timing & State Machine
// ---------------------------------------------------------
// Time required without vibration to consider the cycle "finished".
// Set to 15 seconds (15000ms) for testing.
// For real laundry machines, use 180000ms (3 minutes) or more.
const unsigned long CYCLE_TIMEOUT_MS = 15000;
enum MachineState {
STATE_IDLE,
STATE_RUNNING,
STATE_FINISHED
};
MachineState currentState = STATE_IDLE;
unsigned long lastVibrationTime = 0;
unsigned long lastAlertToggleTime = 0;
bool alertToggleState = false;
// Function prototypes
void connectToWiFi();
void sendWiFiAlert();
void handleAlertHardware();
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n--- Laundry Vibration WiFi Alert ---");
// Initialize pins
pinMode(SW420_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
// Ensure outputs are off initially
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
connectToWiFi();
Serial.println("System initialized. Waiting for vibration...");
}
void loop() {
unsigned long currentMillis = millis();
int vibrationDetected = digitalRead(SW420_PIN);
// 1. Read Sensor & Update Timers
if (vibrationDetected == HIGH) {
lastVibrationTime = currentMillis;
// If we were idle or already finished, a new vibration means a cycle is starting/restarting
if (currentState == STATE_IDLE || currentState == STATE_FINISHED) {
currentState = STATE_RUNNING;
Serial.println("STATUS: Machine is now RUNNING.");
// Ensure alert hardware is turned off when returning to running
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, HIGH); // Solid LED indicates running
}
}
// 2. Evaluate State Machine
if (currentState == STATE_RUNNING) {
// Check if the timeout has elapsed since the last detected vibration
if (currentMillis - lastVibrationTime > CYCLE_TIMEOUT_MS) {
currentState = STATE_FINISHED;
Serial.println("STATUS: Cycle FINISHED! Triggering alerts.");
digitalWrite(LED_PIN, LOW); // Turn off solid LED
sendWiFiAlert();
}
}
// 3. Handle Hardware Outputs based on State
if (currentState == STATE_FINISHED) {
handleAlertHardware();
} else if (currentState == STATE_IDLE) {
// Optional: Pulse LED slowly to show system is alive but idle
digitalWrite(LED_PIN, (currentMillis / 1000) % 2 == 0);
}
// Small delay to prevent tight-loop debouncing issues
delay(50);
}
// ---------------------------------------------------------
// Helper Functions
// ---------------------------------------------------------
void connectToWiFi() {
Serial.print("Connecting to WiFi: ");
Serial.println(WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi connected.");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nFailed to connect to WiFi. Continuing offline.");
}
}
void sendWiFiAlert() {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
Serial.print("Sending HTTP GET to: ");
Serial.println(WEBHOOK_URL);
http.begin(WEBHOOK_URL);
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
} else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
http.end();
} else {
Serial.println("WiFi disconnected. Cannot send webhook alert.");
}
}
void handleAlertHardware() {
// Non-blocking toggle for Buzzer and LED (beep every 500ms)
unsigned long currentMillis = millis();
if (currentMillis - lastAlertToggleTime >= 500) {
lastAlertToggleTime = currentMillis;
alertToggleState = !alertToggleState;
digitalWrite(BUZZER_PIN, alertToggleState ? HIGH : LOW);
digitalWrite(LED_PIN, alertToggleState ? HIGH : LOW);
}
}
Comandos de Compilación/Flasheo/Ejecución
Utiliza la CLI de PlatformIO Core para compilar, subir y monitorizar el proyecto. Abre el terminal integrado en VS Code y ejecuta los siguientes comandos.
| Comando | Propósito |
|---|---|
pio run | Compila el código fuente en C++ y comprueba si hay errores de sintaxis. |
pio run --target upload | Flashea el firmware compilado en el ESP32 conectado a través de USB. |
pio device monitor | Abre el monitor serie para ver la salida de la consola del ESP32. |
Flujo de trabajo de ejecución:
1. Actualiza WIFI_SSID y WIFI_PASSWORD en src/main.cpp con las credenciales reales de tu red.
2. Conecta el ESP32 DevKitC a tu computadora mediante micro-USB.
3. Ejecuta pio run para verificar que el código se compila correctamente.
4. Ejecuta pio run --target upload para flashear el dispositivo. (Si la subida no comienza, mantén presionado el botón «BOOT» en el ESP32 hasta que aparezca el porcentaje de progreso de flasheo).
5. Ejecuta pio device monitor para observar la secuencia de arranque, la conexión WiFi y los estados del sensor.
Validación paso a paso
Sigue estos puntos de control para asegurarte de que tu prototipo funciona correctamente.
- Arranque y conexión WiFi
- Observación: Observa el monitor serie después de reiniciar el ESP32.
- Condición de éxito: La consola muestra «Connecting to WiFi…», seguido de «WiFi connected.» y una dirección IP asignada.
- Verificación de estado inactivo (Idle)
- Observación: Deja el sensor completamente quieto sobre tu escritorio.
- Condición de éxito: El monitor serie dice «System initialized. Waiting for vibration…» y el LED de estado parpadea lentamente (1Hz). No suena ningún zumbador.
- Detección de vibración (Estado de funcionamiento)
- Observación: Golpea suavemente el sensor SW-420 con tu dedo o golpea la mesa en la que descansa.
- Condición de éxito: El monitor serie imprime inmediatamente «STATUS: Machine is now RUNNING.» El LED de estado se enciende de forma fija.
- Finalización del ciclo (Tiempo de espera y alerta)
- Observación: Deja de golpear y espera exactamente 15 segundos (el
CYCLE_TIMEOUT_MS). - Condición de éxito: El monitor serie imprime «STATUS: Cycle FINISHED! Triggering alerts.» El LED y el zumbador comienzan a encenderse y apagarse cada 500ms.
- Observación: Deja de golpear y espera exactamente 15 segundos (el
- Verificación de Webhook/Red
- Observación: Inmediatamente después del tiempo de espera, observa la salida HTTP en el monitor serie.
- Condición de éxito: La consola imprime «Sending HTTP GET…» seguido de «HTTP Response code: 200». Esto confirma que el ESP32 alcanzó con éxito el servidor externo.
Solución de problemas
| Síntoma | Causa probable | Solución |
|---|---|---|
| Falla la conexión WiFi | Credenciales incorrectas o red de 5GHz. | Verifica el SSID/Contraseña. Asegúrate de que tu enrutador esté emitiendo en una banda de 2.4GHz (ESP32 no soporta 5GHz). |
| El sensor siempre se activa (Sin tiempo de espera) | La sensibilidad del SW-420 está ajustada demasiado alta. | Gira el potenciómetro azul del módulo SW-420 en sentido antihorario para reducir la sensibilidad hasta que el LED verde integrado se apague cuando esté quieto. |
| El sensor nunca se activa | La sensibilidad del SW-420 está ajustada demasiado baja. | Gira el potenciómetro en sentido horario hasta que se active fácilmente al golpear la mesa. |
| El zumbador no suena | Polaridad invertida o pin incorrecto. | Asegúrate de que la pata más larga (o la marca +) del zumbador vaya al GPIO 14, y la otra a GND. |
| El código de respuesta HTTP es -1 | Fallo de DNS o falta de internet. | Verifica que el ESP32 esté conectado a una red con acceso activo a internet, no solo a un enrutador local sin WAN. |
Mejoras
Una vez que el prototipo básico esté funcionando, considera estas mejoras para una implementación permanente:
Robustez del hardware:
* Carcasa y montaje: Coloca el ESP32 en una carcasa impresa en 3D. Fija imanes de neodimio en la parte posterior de la carcasa para que pueda adherirse de forma segura al chasis metálico de la lavadora, asegurando una excelente transferencia de vibración al SW-420.
* Zumbador pasivo / Audio: Cambia el zumbador activo por un zumbador pasivo. Luego puedes usar las capacidades PWM del ESP32 (ledcWrite) para reproducir una melodía agradable (como el clásico timbre de un electrodoméstico) en lugar de un pitido áspero.
Software y lógica:
* Tiempos de espera realistas: Aumenta CYCLE_TIMEOUT_MS a 180,000 (3 minutos) para tener en cuenta las fases de remojo y vaciado donde una lavadora se queda temporalmente completamente quieta.
* Integración MQTT: Reemplaza la solicitud HTTP GET básica con un cliente MQTT (usando la biblioteca PubSubClient) para integrarlo sin problemas con Home Assistant, Node-RED u OpenHAB.
* Modo de suspensión profunda (Deep Sleep) / Bajo consumo: Si funciona con una batería LiPo, modifica el código para entrar en el modo de suspensión profunda del ESP32. Puedes usar el pin del SW-420 como un activador externo (external wake)
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.




