¿Cuántas veces se te han quemado los almácigos por una helada de madrugada? Entre mayo y agosto, en buena parte de Chile la mínima cae bajo cero sin que nadie lo anuncie, y cuando lo notas el daño ya está hecho. Este proyecto ataca justamente ese problema: un aviso visual que se pone azul cuando el pronóstico de esta noche trae helada, más un mensaje directo a tu celular para que alcances a cubrir el huerto.

La idea original es de Adafruit y funciona sin escribir una sola línea de código, apoyándose en su plataforma Adafruit IO. Nosotros la rehicimos de otra manera por una razón muy concreta que verás en la próxima sección. Al terminar vas a tener un ESP32 que consulta el pronóstico de tu comuna cada 30 minutos, una tira WS2812 que cambia de color según el riesgo, un aviso por Telegram, y un DHT22 midiendo lo que realmente está pasando dentro del invernadero.

Por qué no copiamos el proyecto original tal cual

Panel de configuración de WipperSnapper dentro de Adafruit IO

El proyecto de Adafruit usa WipperSnapper, un firmware que convierte cualquier placa con WiFi en un dispositivo IoT configurable desde el navegador. Es elegante: cargas el firmware, la placa se registra sola en tu cuenta y agregas componentes desde una interfaz web. Nada de compilar.

El problema aparece en la letra chica. Para leer el pronóstico del tiempo, ese proyecto usa el Weather Power Up de Adafruit IO Plus, y ese power up es de pago: la suscripción cuesta unos US$99 al año. Sin ella el proyecto simplemente no funciona.

Tarjeta de suscripción anual a Adafruit IO Plus

A eso súmale que en Chile la placa recomendada (una QT Py ESP32-S3) no se consigue fácil y que pagar una suscripción en dólares por leer la mínima de mañana es difícil de justificar cuando existen APIs meteorológicas abiertas y gratuitas.

Placa QT Py ESP32-S3, el microcontrolador que usa el proyecto original

Nuestra versión reemplaza esas dos piezas: en vez de Adafruit IO Plus usamos Open Meteo, una API meteorológica gratuita y sin llave de acceso; en vez de la QT Py usamos un ESP32 DevKit de 30 pines, que es la placa más común del catálogo chileno. El resultado hace lo mismo, sin costo mensual, y de paso aprendes a consumir una API REST desde el ESP32 en lugar de arrastrar bloques en una interfaz web.

El hardware y cómo se conecta

El circuito es corto. Todo trabaja a 5 V salvo las señales de datos, que salen a 3.3 V desde el ESP32.

Componente Pin del ESP32 Nota
WS2812 (DIN) GPIO 4 dato serial de la tira
WS2812 (VCC / GND) 5V / GND consumo alto si subes el brillo
DHT22 (DATA) GPIO 15 necesita pull up de 10 kΩ a 3.3 V
DHT22 (VCC / GND) 3V3 / GND también funciona a 5 V

Dos detalles técnicos que conviene entender antes de conectar:

El WS2812 espera 5 V en su línea de datos, pero el ESP32 entrega 3.3 V. En la práctica la mayoría de los módulos funciona igual, porque el umbral lógico del WS2812B suele quedar justo por debajo. Si ves parpadeos o colores erráticos, la causa casi siempre es esa: la solución barata es alimentar la tira con 4.5 V (un diodo 1N4007 en serie con los 5 V) para bajar el umbral, o intercalar un conversor de nivel.

El DHT22 no responde sin resistencia de pull up. Si tu lectura sale siempre nan, revisa esa resistencia de 10 kΩ entre DATA y 3.3 V antes de sospechar del código. Además, el DHT22 no acepta más de una lectura cada 2 segundos: si lo consultas más rápido te devuelve el valor anterior en caché.

Las librerías y la configuración inicial

Instala desde el Library Manager del IDE de Arduino: Adafruit NeoPixel, DHT sensor library, Adafruit Unified Sensor y ArduinoJson (versión 7 o superior). Después declara todo lo que el resto del programa va a necesitar:

C++
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Adafruit_NeoPixel.h>
#include <DHT.h>

const char* WIFI_SSID = "TU_RED_WIFI";
const char* WIFI_PASS = "TU_CLAVE_WIFI";

// Coordenadas de tu huerto. Ejemplo: Santiago, Chile.
const char* LATITUD  = "-33.45";
const char* LONGITUD = "-70.66";

// Bajo esta temperatura minima consideramos que hay riesgo de helada.
const float UMBRAL_HELADA = 2.0;

// Credenciales del bot de Telegram (ver seccion "Aviso al celular").
const char* TELEGRAM_TOKEN   = "123456789:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const char* TELEGRAM_CHAT_ID = "987654321";

#define PIN_NEOPIXEL 4
#define NUM_PIXELS   8
Adafruit_NeoPixel tira(NUM_PIXELS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);

#define PIN_DHT  15
DHT dht(PIN_DHT, DHT22);

Las coordenadas del ejemplo son de Santiago. Cámbialas por las de tu terreno: cualquier mapa te da latitud y longitud con un clic derecho. El UMBRAL_HELADA en 2 °C no es un error de tipeo. los pronósticos entregan la temperatura del aire a 2 metros del suelo, y a nivel de suelo suele hacer entre 2 y 4 grados menos. Si esperas a que el pronóstico marque 0 °C, tus almácigos ya se congelaron.

Consultar el pronóstico sin pagar suscripción

Open Meteo entrega pronóstico horario y diario para cualquier coordenada del mundo, sin registro ni API key. Pedimos un solo dato, temperature_2m_min, para los próximos dos días:

C++
float minimaPronosticada() {
  WiFiClientSecure cliente;
  cliente.setInsecure();   // Open-Meteo es publico: no necesitamos validar la CA

  String url = String("https://api.open-meteo.com/v1/forecast")
             + "?latitude="  + LATITUD
             + "&longitude=" + LONGITUD
             + "&daily=temperature_2m_min"
             + "&timezone=America%2FSantiago"
             + "&forecast_days=2";

  HTTPClient http;
  http.begin(cliente, url);

  int codigo = http.GET();
  if (codigo != 200) {
    Serial.printf("La API respondio %d\n", codigo);
    http.end();
    return NAN;
  }

  JsonDocument doc;
  DeserializationError error = deserializeJson(doc, http.getStream());
  http.end();

  if (error) {
    Serial.print("No pude leer el JSON: ");
    Serial.println(error.c_str());
    return NAN;
  }

  // indice 0 = hoy, indice 1 = manana. Nos interesa la noche que viene.
  return doc["daily"]["temperature_2m_min"][1].as<float>();
}

Tres decisiones de este bloque que vale la pena explicar:

  • setInsecure(): Open Meteo responde por HTTPS y el ESP32 tendría que validar el certificado con la CA raíz. Como el dato es público y de solo lectura, saltarse la validación no expone nada. Si el proyecto manejara credenciales sí habría que cargar el certificado.
  • deserializeJson(doc, http.getStream()): parsear directo desde el stream evita copiar toda la respuesta a un String en RAM. Con respuestas chicas da lo mismo, pero es la costumbre correcta.
  • Devolver NAN en vez de un número: así el loop distingue "no hay helada" de "no pude averiguarlo", que son situaciones muy distintas. Un aviso que falla en silencio es peor que no tener aviso.

El aviso al celular con un bot de Telegram

Telegram es la vía más simple de notificación gratuita: no cobra, no necesita servidor y funciona con una sola petición HTTP. Para obtener las credenciales, habla con @BotFather dentro de Telegram, usa /newbot y guarda el token que te entrega. Después escríbele cualquier cosa a tu propio bot y abre https://api.telegram.org/bot<TU_TOKEN>/getUpdates en el navegador: ahí aparece tu chat_id.

C++
void avisarPorTelegram(const String& mensaje) {
  WiFiClientSecure cliente;
  cliente.setInsecure();

  HTTPClient http;
  http.begin(cliente, String("https://api.telegram.org/bot")
                      + TELEGRAM_TOKEN + "/sendMessage");
  http.addHeader("Content-Type", "application/json");

  JsonDocument cuerpo;
  cuerpo["chat_id"] = TELEGRAM_CHAT_ID;
  cuerpo["text"]    = mensaje;

  String payload;
  serializeJson(cuerpo, payload);

  int codigo = http.POST(payload);
  Serial.printf("Telegram respondio %d\n", codigo);
  http.end();
}

El programa completo

Con las piezas anteriores en su lugar, el resto es la lógica del semáforo:

C++
void pintar(uint8_t r, uint8_t g, uint8_t b) {
  for (int i = 0; i < NUM_PIXELS; i++) {
    tira.setPixelColor(i, tira.Color(r, g, b));
  }
  tira.show();
}

void setup() {
  Serial.begin(115200);
  dht.begin();
  tira.begin();
  tira.setBrightness(60);   // 60/255 alcanza de sobra y no encandila de noche
  tira.show();

  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.print("Conectando a WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" listo");
}

bool alertaEnviada = false;

void loop() {
  float minima = minimaPronosticada();
  float ambiente = dht.readTemperature();

  if (isnan(minima)) {
    pintar(40, 20, 0);              // ambar: no hubo respuesta del pronostico
  } else if (minima <= UMBRAL_HELADA) {
    pintar(0, 0, 255);              // azul: hay riesgo de helada esta noche
    if (!alertaEnviada) {
      String texto = "Helada esta noche: minima de " + String(minima, 1)
                   + " C. Cubre los almacigos.";
      if (!isnan(ambiente)) {
        texto += " Ahora en el invernadero hay " + String(ambiente, 1) + " C.";
      }
      avisarPorTelegram(texto);
      alertaEnviada = true;
    }
  } else {
    pintar(0, 60, 0);               // verde: noche tranquila
    alertaEnviada = false;          // se rearma para la proxima noche fria
  }

  delay(30UL * 60UL * 1000UL);      // consultamos cada 30 minutos
}

Fíjate en la variable alertaEnviada. Sin ella, el ESP32 te mandaría un mensaje de Telegram cada 30 minutos durante toda la noche fría. Con ella, avisa una vez y recién vuelve a armarse cuando el pronóstico deja de indicar helada. Es el tipo de detalle que separa un proyecto que usas de uno que silencias a los tres días.

Pruebas antes de dejarlo instalado

No esperes a la primera helada real para descubrir que algo falla. Baja UMBRAL_HELADA a un valor absurdamente alto (por ejemplo 30.0) y sube el código: la tira debería ponerse azul y llegarte el Telegram en segundos. Después déjalo en el valor real.

Para verificar la lectura del DHT22, abre el monitor serial a 115200 baudios y compárala con cualquier termómetro. Si difiere más de 1 °C, probablemente el sensor está tomando calor de la propia placa: aléjalo unos centímetros con cables más largos.

Variantes y mejoras

  • Registro histórico en microSD: agrega un módulo microSD por SPI y guarda cada lectura con su hora. Al cabo de un invierno tendrás la curva real de temperaturas de tu terreno, que sirve mucho más que cualquier pronóstico regional para decidir qué plantar y cuándo.
  • Alerta con dos umbrales: en vez de azul o verde, usa amarillo entre 2 y 5 °C y azul bajo 2 °C. Son tres líneas más de código y te da margen para reaccionar la noche anterior.
  • Sumar presión barométrica: reemplaza el DHT22 por un BME280 y registra también la presión. Una caída rápida de presión durante la tarde suele anticipar noches despejadas, que son justamente las de mayor riesgo de helada por radiación.
  • Conexiones firmes con bornera: si el equipo va a vivir a la intemperie, los cables dupont se sueltan. Una placa de bornera atornillable como la del proyecto original resuelve el problema.

Placa de bornera atornillable para conectar componentes externos

Personalización para Chile

Todo lo necesario está en el catálogo de MechatronicStore, con stock local:

  • ESP32 con WiFi y Bluetooth ESP WROOM-32, Tipo C (SKU X2-10V2). $7.990 CLP
  • Módulo RGB LED de 8 bits 5050 WS2812 (SKU GN3-11). $1.990 CLP
  • Módulo Sensor de Temperatura y Humedad DHT22 (SKU G-021). $4.700 CLP
  • Breadboard / Protoboard mediana 400 puntos (SKU C-301). $2.690 CLP
  • Cables macho hembra 40 piezas 20cm (SKU C-412). $1.990 CLP
  • Cable USB Tipo C a USB Tipo A 1mt (SKU B-101). $2.190 CLP
  • Transformador Cargador Fuente de Alimentación 5V 2A (SKU B-001). $3.490 CLP

Total aproximado: $25.040 CLP, una sola vez y sin suscripciones.

Sobre las equivalencias: el proyecto original usa una QT Py ESP32-S3 de Adafruit (US$12,50, difícil de conseguir acá). El ESP32 DevKit de 30 pines cumple exactamente la misma función. mismo chip Espressif, mismo WiFi, más pines disponibles. a menos de $8.000 pesos. El NeoPixel integrado de la QT Py lo reemplazamos por el módulo WS2812 de 8 bits, que además se ve mucho mejor desde lejos. Y la suscripción de US$99 al año simplemente no la necesitas: Open Meteo es gratis.

Recursos

Inspirado en el proyecto de Brent Rubell y los Ruiz Brothers para Adafruit. Versión chilena con componentes en stock local en MechatronicStore, sin suscripciones de pago.