Intermedio35 min

Regador automático con ESP32 + Bloomit

Armá un regador que mide la humedad del suelo, enciende la bomba solo cuando la planta lo necesita y registra cada riego en Bloomit. Código completo, diagrama y precauciones.

Hardware necesario

  • ESP32 DevKit V1
  • Sensor de humedad YL-69 / FC-28 (o capacitivo v1.2)
  • Módulo relay 1 canal 5V con optoacoplador
  • Bomba sumergible DC 5V (~120L/h)
  • Fuente externa 5V 1A
  • Mangueras de silicona Ø5mm
  • Recipiente/tacho para el agua
  • Protoboard y cables jumper

Qué vas a armar

Este tutorial extiende el tutorial de sensor de humedad con ESP32 agregando la parte actuadora: cuando la humedad baja del umbral que configurás, el ESP32 activa un relay que enciende la bomba durante unos segundos, espera el cooldown y luego registra el riego en Bloomit.

El ciclo completo es el siguiente: cada 30 minutos el ESP32 lee la humedad del suelo. Si el valor cae por debajo de HUMIDITY_THRESHOLD y pasaron al menos 30 minutos desde el último riego (cooldown), activa el relay, la bomba corre PUMP_RUN_MS milisegundos, y luego se registra en Bloomit con /sense (lectura de humedad) y /actuate (evento de riego). Si la humedad está bien, solo envía la lectura y vuelve a dormir.

Si todavía no completaste el tutorial anterior, te conviene hacerlo primero: la configuración de WiFi, la calibración del sensor y el registro del device token son idénticos. Acá nos enfocamos en lo nuevo: el relay, la bomba y la lógica de decisión.

⚠️ Precauciones de seguridad

Leé esto antes de conectar cualquier cosa.

  • Nunca alimentes la bomba desde el USB del ESP32. El pin 5V del ESP32 en modo USB entrega como máximo 500 mA; una bomba de 120L/h arranca con picos de 800 mA o más. El regulador se quema. Usá siempre una fuente externa.
  • GND común obligatorio. El ESP32, el relay y la fuente externa tienen que compartir el mismo GND. Sin GND común el relay puede no responder o comportarse de forma impredecible.
  • El relay debe tener optoacoplador. Aísla el circuito de control (3.3V del ESP32) del circuito de carga (bomba + fuente). Los módulos relay baratos sin opto pueden generar picos de voltaje que dañan el microcontrolador.
  • Probá el relay sin la bomba conectada primero. Cargá el sketch, abrí el Monitor Serial y verificá que el relay clickea en el momento correcto antes de conectar la carga real.
  • Electrónica lejos del agua. Elevá el ESP32 y el relay al menos 20 cm sobre el nivel del recipiente. Un salpicado puede cortocircuitar todo. Usá canaletas o cajas plásticas con tapa.
  • Este tutorial es solo para 5V DC. Si en algún momento considerás usar 220V AC para una bomba más potente, necesitás un relay certificado, caja cerrada y protección GFCI. No está cubierto aquí y no es para principiantes.

Diagrama de conexión

GND railESP32DevKit V1VIN/5V3.3VGNDGPIO 34GPIO 26GPIO 2Sensor HumedadYL-69 / FC-28SondasVCCGNDAORelay 1CH5V c/optoacopladorRELAYCOMNONCVCCINGNDCOMNOFuente5V / 1AEXTERNA+Bomba5V DC~120L/h+220ΩLED3.3V sensor5V relayControl relaySeñal analógicaCarga (bomba)GND
ESP32ComponentePin destinoCable
VIN / 5VMódulo relayVCCNaranja
GNDMódulo relayGNDNegro
GPIO 26Módulo relayINVioleta
GPIO 34Sensor YL-69AO (Analog Out)Amarillo
3.3VSensor YL-69VCCRojo
GNDSensor YL-69GNDNegro
GPIO 2Resistencia 220Ω →Ánodo LED (+)Azul
Fuente 5V (+)RelayCOM (terminal carga)Verde
RelayBomba (+)NO (normalmente abierto)Verde
Bomba (−)Fuente 5V (−)GND railNegro

Nota sobre active-low: la mayoría de los módulos relay de 5V con optoacoplador se activan con LOW en el pin IN y se desactivan con HIGH. El código usa esa convención. Si tu relay funciona al revés (active-high), invertí los valores en la función waterPlant().

Obtener el device token

Si ya completaste el tutorial anterior y tenés un token, podés reutilizarlo. Si no, registrá el dispositivo desde bloomit.app o via API:

# 1. Login → obtenés el userToken
curl -X POST https://user.api.bloomit.app/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"tu@email.com","password":"tupassword"}'

# 2. Registrar dispositivo → obtenés el deviceToken
curl -X POST https://device.api.bloomit.app/register \
  -H "Authorization: Bearer <userToken>" \
  -H "Content-Type: application/json" \
  -d '{"name":"ESP32 Regador Balcón","metadata":{"location":"balcon"}}'

# Respuesta: { "deviceToken": "eyJhbGci...", "deviceId": "abc123" }
# Guardá el deviceToken — lo pegás en el sketch

Código completo

Copiá este sketch en Arduino IDE. Reemplazá SSID, PASSWORD y DEVICE_TOKEN con tus datos. Ajustá HUMIDITY_THRESHOLD según la planta (ver sección siguiente).

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// ── Configuración WiFi y Bloomit ───────────────────────
const char* SSID         = "tu_red_wifi";
const char* PASSWORD     = "tu_password";
const char* DEVICE_TOKEN = "eyJhbGci..."; // tu deviceToken de bloomit.app

// ── Pines ──────────────────────────────────────────────
const int SENSOR_PIN = 34;  // ADC1 — solo lectura analógica
const int RELAY_PIN  = 26;  // GPIO 26 — control relay (active-low)
const int LED_PIN    = 2;   // GPIO 2 — LED indicador integrado

// ── Calibración del sensor ────────────────────────────
// Medí el valor en seco (sensor en aire) y en húmedo (sensor sumergido).
const int SENSOR_DRY = 4095;  // valor en aire → 0 % humedad
const int SENSOR_WET = 1500;  // valor en agua → 100 % humedad

// ── Parámetros de riego ───────────────────────────────
const int HUMIDITY_THRESHOLD  = 30;         // regar si humedad < 30 %
const unsigned long PUMP_RUN_MS  = 5000UL;  // cuántos ms corre la bomba
const unsigned long COOLDOWN_MS  = 1800000UL; // 30 min mínimo entre riegos
const unsigned long LOOP_MS      = 1800000UL; // 30 min entre lecturas

// ── Estado global ─────────────────────────────────────
unsigned long lastWateringMs = 0; // millis() del último riego

// ── Ayudante LED ──────────────────────────────────────
void blink(int times, int onMs, int offMs) {
  for (int i = 0; i < times; i++) {
    digitalWrite(LED_PIN, HIGH); delay(onMs);
    digitalWrite(LED_PIN, LOW);  delay(offMs);
  }
}

// ── Setup ──────────────────────────────────────────────
void setup() {
  Serial.begin(115200);

  pinMode(LED_PIN,   OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);

  // Relay apagado desde el arranque (active-low → HIGH = apagado)
  digitalWrite(RELAY_PIN, HIGH);

  // Señal de inicio: 2 parpadeos lentos
  blink(2, 500, 300);

  Serial.print("Conectando a WiFi");
  WiFi.begin(SSID, PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" OK — IP: " + WiFi.localIP().toString());

  // WiFi listo: 3 parpadeos rápidos
  blink(3, 100, 100);
}

// ── Lectura del sensor ────────────────────────────────
int readHumidity() {
  // Promediamos 5 lecturas para estabilizar el valor ADC
  long sum = 0;
  for (int i = 0; i < 5; i++) {
    sum += analogRead(SENSOR_PIN);
    delay(10);
  }
  int raw = (int)(sum / 5);
  int humidity = map(raw, SENSOR_DRY, SENSOR_WET, 0, 100);
  return constrain(humidity, 0, 100);
}

// ── Acción de riego ───────────────────────────────────
void waterPlant() {
  Serial.println("Regando...");
  digitalWrite(LED_PIN,   HIGH); // LED fijo durante el riego
  digitalWrite(RELAY_PIN, LOW);  // activa relay (active-low → bomba ON)
  delay(PUMP_RUN_MS);
  digitalWrite(RELAY_PIN, HIGH); // desactiva relay → bomba OFF
  digitalWrite(LED_PIN,   LOW);
  Serial.println("Riego finalizado.");
}

// ── Envío de lectura a Bloomit (/sense) ───────────────
bool sendSensor(int humidity) {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.reconnect();
    delay(3000);
    return false;
  }

  HTTPClient http;
  http.begin("https://device.api.bloomit.app/sense");
  http.addHeader("Authorization", "Bearer " + String(DEVICE_TOKEN));
  http.addHeader("Content-Type", "application/json");

  StaticJsonDocument<128> doc;
  doc["sensorType"] = "soil_humidity";
  doc["value"]      = humidity;

  String body;
  serializeJson(doc, body);

  int code = http.POST(body);
  http.end();

  Serial.printf("POST /sense → %d | humedad: %d%%\n", code, humidity);
  return code == 200;
}

// ── Registro del riego en Bloomit (/actuate) ─────────
bool sendActuate() {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.reconnect();
    delay(3000);
    return false;
  }

  HTTPClient http;
  http.begin("https://device.api.bloomit.app/actuate");
  http.addHeader("Authorization", "Bearer " + String(DEVICE_TOKEN));
  http.addHeader("Content-Type", "application/json");

  StaticJsonDocument<128> doc;
  doc["action"] = "turn_on";
  doc["value"]  = 100; // intensidad 100 % — bomba encendida al máximo

  String body;
  serializeJson(doc, body);

  int code = http.POST(body);
  http.end();

  Serial.printf("POST /actuate → %d\n", code);
  return code == 200;
}

// ── Loop principal ────────────────────────────────────
void loop() {
  // Reconectar WiFi si se cayó
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi caído — reconectando...");
    WiFi.reconnect();
    delay(5000);
  }

  // Leer humedad (1 parpadeo = midiendo)
  blink(1, 200, 0);
  int humidity = readHumidity();
  Serial.printf("Humedad: %d%%\n", humidity);

  unsigned long now = millis();
  bool cooldownOk   = (now - lastWateringMs) >= COOLDOWN_MS;
  bool needsWater   = humidity < HUMIDITY_THRESHOLD;

  if (needsWater && cooldownOk) {
    // Regar, registrar y actualizar el timestamp
    waterPlant();
    lastWateringMs = millis();

    bool okActuate = sendActuate();
    if (!okActuate) {
      // LED 1 s = error al registrar actuación
      digitalWrite(LED_PIN, HIGH); delay(1000); digitalWrite(LED_PIN, LOW);
    }
  } else if (needsWater && !cooldownOk) {
    Serial.println("Humedad baja pero cooldown activo — esperando.");
  }

  // Siempre enviar la lectura de humedad
  bool okSensor = sendSensor(humidity);
  if (okSensor) {
    blink(3, 100, 100); // 3 rápidos = dato enviado OK
  } else {
    digitalWrite(LED_PIN, HIGH); delay(1000); digitalWrite(LED_PIN, LOW);
  }

  // Esperar hasta el próximo ciclo (30 minutos)
  delay(LOOP_MS);
}

Señales del LED

  • 2 parpadeos lentos (500ms)
    ArranqueEl sketch inició correctamente
  • 3 parpadeos rápidos (100ms) al inicio
    WiFi OKLa red WiFi está conectada
  • LED fijo durante varios segundos
    RegandoLa bomba está activa — no interrumpas
  • 1 parpadeo (200ms)
    Leyendo sensorCapturando la lectura analógica
  • 3 parpadeos rápidos tras lectura
    Envío exitosoEl dato llegó a Bloomit (HTTP 200)
  • LED 1 segundo fijo
    Error HTTPNo se pudo enviar — se reintenta en el próximo ciclo
  • LED apagado entre ciclos
    Cooldown / esperaEl ESP32 está en delay — todo normal

Elegir el umbral correcto

El valor de HUMIDITY_THRESHOLD depende de la planta. Bloomit aplica rangos recomendados por especie cuando la asignás desde la app:

  • Cactus y suculentas: 10–25 % — riegan muy poco, esperan que el sustrato esté casi seco.
  • Plantas tropicales (pothos, calathea): 40–60 %.
  • Tomate y vegetales: 60–80 % — prefieren sustrato húmedo constante.
  • Helechos: más de 50 % — no toleran que el sustrato se seque.

Antes de configurar el riego automático, hacé al menos un día de observación en modo solo lectura: cargá el sketch con HUMIDITY_THRESHOLD = 0 (nunca va a regar), mirá los datos en Bloomit durante 24 horas y fijate cuánto baja la humedad naturalmente en tu tierra. Con eso vas a tener el rango real de tu maceta, no el teórico del sensor.

Troubleshooting

  • El relay clickea pero la bomba no arranca

    La fuente no tiene corriente suficiente. Probá con una fuente de 2A. También verificá la polaridad de la bomba (los cables rojo/negro pueden estar invertidos en algunas unidades chinas).

  • El sensor da valores fuera de rango o inconsistentes

    En sensores resistivos (YL-69), las sondas metálicas se oxidan en pocas semanas de uso continuo. El oxido aumenta la resistencia y distorsiona la lectura. Considerá migrar a un sensor capacitivo v1.2 que dura mucho más.

  • La bomba no se detiene o el relay no responde como esperás

    Puede ser que tu módulo relay sea active-high en lugar de active-low. Intercambiá los valores: donde el código pone LOW, poné HIGH, y viceversa. Verificá también si el LED de tu módulo enciende al recibir LOW o HIGH.

  • Error "HTTP -1" en el monitor serial

    El cable USB alimenta el ESP32 pero cuando arranca la bomba hay un pico de corriente que hace caer el voltaje de 3.3V y el ESP32 se reinicia o pierde el stack de red. La fuente externa para la bomba es obligatoria, no opcional.

  • El WiFi se corta justo al momento de regar

    Pico de corriente del motor al arrancar. Soldá un capacitor electrolítico de 470µF (o más) en paralelo entre VIN y GND del ESP32. Esto actúa como buffer ante los picos de demanda.

¿Te trabaste en algún paso?

Sumate al grupo de Telegram de Bloomit: ahí te ayudamos a resolver errores de cableado, compilación o conexión. Hay gente armando los mismos proyectos que vos.

Unirme al grupo

Bloomit

¿Todo funcionando? Abrí el panel

Registrá tu cuenta gratis, conectá el dispositivo y los datos aparecen en tiempo real. Hardware Bloomit listo para usar en bloomit.com.ar.