Proyectos IoT

Proyecto DIY IoT: Construye una maceta inteligente de bajo coste

María Hernández
- 11 min read
Enviar por correo electrónico

Hace unas semanas decidí comprar una suculenta para el escritorio de mi oficina sólo por los beneficios que pueden aportar al medio ambiente. Por si no lo sabes, cultivar suculentas o cactus puede beneficiarte de múltiples maneras, ayudan a purificar el aire, mejoran la humedad de los espacios y añaden oxígeno fresco al ambiente

Después de unos días de tener a Lana (Sí, la llamé Lana 💕) en mi escritorio, empecé a sentir que le faltaba algo. Como fabricante apasionado y desarrollador de IoT en Ubidots, decidí crear una"maceta inteligente" para Lana utilizando nuestras herramientas. Después de unos días de duro trabajo y muchos problemas de impresión, finalmente, terminé con este resultado tan chulo, y estaba deseando compartir con vosotros mi proceso 🙆‍♀️💚:

Este proyecto está inspirado en uno anterior creado por Paul a Maker 👨‍💻 (Impresión 3D, Electrónica, Software) al que sigo desde hace meses, y puedo decir que he aprendido muchos trucos y consejos de él. Si buscas inspiración para tus proyectos de electrónica, deberías seguirle, créeme.

Personalmente, me gusta mucho encontrar mi propia inspiración siguiendo proyectos de alguien más, de todas formas, siempre encontraré la manera de agregar mi propio estilo, dar créditos, y por supuesto, compartir el resultado final para que otras personas como yo también se inspiren. 🤓 🤓

Este post contiene un paso a paso para mi proyecto, así como todos los recursos necesarios para replicar el"Smart Planter".

Requisitos

Paso a paso

  1. Impresión 3D
  2. Cableado
  3. Carcasa
  4. Programación NodeMCU ESP8266
  5. Dashboard - Control y vigilancia
  6. Resumen

1. Impresión 3D

Mis habilidades para modelar piezas en 3D son algo limitadas (estoy trabajando en ello para crear más proyectos utilizando la impresora 3D), pero existe una comunidad abierta llamada Thingiverse que nos hace la vida más fácil. Es una próspera comunidad de diseño para descubrir, hacer y compartir cosas imprimibles en 3D. Después de mirar muchos diseños de jardineras, decidí imprimir la siguiente:

Haga clic aquí para descargar el diseño - thing:2857938

Me gustó mucho este diseño porque ya era una jardinera IoT, y la lámpara era un accesorio bonito y genial. Básicamente, este modelo encajaba a la perfección con lo que tenía en mente desde el principio:

  • Lámpara para mando a distancia.
  • Espacio para colocar componentes electrónicos para añadir algunos sensores.

Sin embargo, sabía que el espacio para la electrónica no era suficiente para todas las funcionalidades que quería incluir. Para solucionarlo, imprimí 2 veces la pieza inferior:

  • Pieza blanca: Para colocar el NodeMCU ESP8266, los sensores y los cables.
  • Pieza transparente: Para colocar el anillo NeoPixel.

Teniendo como resultado la obra maestra que se muestra a continuación:

NOTA: El anillo NeoPixel no es obligatorio para este proyecto, es sólo un accesorio que lo hace más bonito. También puede utilizar sólo una pieza.

2. Cableado

De acuerdo con el firmware proporcionado en esta guía, consulte el diagrama y la tabla siguientes para establecer una conexión adecuada entre los componentes utilizados.

3. Carcasa

Con las conexiones de cables adecuadas ya establecidas, coloque todos los componentes dentro de la jardinera como se muestra a continuación:

Como puedes notar, coloqué el NodeMCU en una PCB Breadboard con 10 cabezales en ángulo recto. La idea era establecer la conexión con los sensores y ahorrar algo de espacio para la electrónica. Para ser honesto, podría haber hecho un mejor trabajo organizando los cables. Sin embargo, la idea era crear una PCB personalizada para la jardinera, en un punto, todo estaría empotrado. Además, taladré la pieza blanca para cruzar los cables del anillo NeoPixel a través de ella. Para terminar, pegué el anillo NeoPixel apuntando hacia abajo en la parte inferior, luego, pegué la pieza transparente a la blanca para difuminar la luz. Al final obtuve este impresionante resultado:

4. Programación de NodeMCU ESP8266

1. Para poder trabajar con la plataforma ESP8266 en el IDE Arduino, tendrá que instalar la plataforma ESP8266 utilizando el Administrador de placas Arduino preconfigurado. Si no está familiarizado con la adición de una placa con el IDE Arduino, consulte este artículo para obtener orientación adicional.

2. Con la plataforma ESP8266 instalada, seleccione el dispositivo ESP8266 con el que está trabajando. En este caso, estamos trabajando con un "NodeMCU 1.0". Para seleccionar tu placa desde el IDE Arduino, selecciona Herramientas > Placa "Módulo ESP8266 Genérico".

3. Descargue e instale el UbidotsMQTTESP8266 biblioteca. Para una explicación detallada de cómo instalar bibliotecas utilizando el IDE de Arduino, consulte esta guía.

4. Pega el código siguiente en el IDE de Arduino. A continuación, asigna tu Ubidots únicos TOKENSSID (Nombre WiFi) y contraseña de la red disponible.

NOTA: Recuerde asignar los pines correctos para los componentes utilizados en caso de que utilice una conexión diferente a la proporcionada anteriormente.

Repositorio GitHub

/*
RGB Smart Planter integrated with Ubidots for Monitoring & Control.

This code works for:

    1) Read two sensors: DHT11, and Soil Moisture.
    2) Publish sensors readings to Ubidots.
    3) Subscribe to multiple variables for remote control.

Libraries required:

- Ubidots ESP8266 MQTT - (https://github.com/ubidots/ubidots-mqtt-esp)
- Adafruit NeoPixel - (https://github.com/adafruit/Adafruit_NeoPixel)
- DHT - (https://github.com/adafruit/DHT-sensor-library)

Made by: Maria Hernández - IoT Developer Advocate @ Ubidots
Revision: José García - Development & Support Manager @ Ubidots

/****************************************
 * Include Libraries
 ****************************************/
#include <Adafruit_NeoPixel.h>
#include <stdio.h>
#include <map>
#include "DHT.h"
#include "UbidotsESPMQTT.h"

/****************************************
 * Define Pins
 ****************************************/
#define LIGHTPIN D1     // Digital pin for Led Lamp.
#define DHTPIN D5       // Digital pin for DHT sensor.
#define NEOPIXELSPIN D6 // Digital pin for NeoPixel Ring.
#define MOISTUREPIN A0  // Analog pin for Moisture Sensor.

/****************************************
 * Define Constants
 ****************************************/
#define TOKEN "BBFF-xxxxxxxxxx" // Assign your Ubidots TOKEN.
#define WIFINAME "xxxxxxxxxx"   // Assign your SSID.
#define WIFIPASS "xxxxxxxxxx"   // Assign your WiFi Password.
#define DEVICE "planter"        // Ubidots Device Label.
#define VAR_PUB_1 "temperature" // Ubidots Variables' label for publishing data.
#define VAR_PUB_2 "humidity"
#define VAR_PUB_3 "soil-moisture"
#define VAR_PUB_4 "heat-index"
#define VAR_SUB_1 "light-1" // Ubidots Variables' label for subscribing to data; \
                            // These variables have to be created at Ubidots.
#define VAR_SUB_2 "light-2"
#define NUMPIXELS 12 // 12 bit NeoPixel Ring
// Uncomment whatever type you're using
#define DHTTYPE DHT11 // DHT 11
//#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
//#define DHTTYPE DHT21   // DHT 21 (AM2301)

typedef enum
{
  red,
  green,
  blue,
  yellow,
  white,
  black
} NeoPixelColor;

//   R,   G,   B
uint8_t myColors[][6] = {{250, 0, 0},                             // Red.
                         {0, 255, 0},                             // Green.
                         {0, 0, 255},                             // Blue.
                         {255, 255, 0},                           // Yellow.
                         {255, 255, 255},                         // White.
                         {0, 0, 0}};                              // Black.
const uint8_t numberOfVariables = 2;                              // Number of variables for subscription.
char *variableLabels[numberOfVariables] = {VAR_SUB_1, VAR_SUB_2}; // Variables' label for subscription.
float value;                                                      // Store incoming value.
int lastValue;
bool bottomLight;                         // flag to control conditions for the bottom light.
unsigned long initTime;                   // Store the init time.
const long SECONDS_TO_RECONNECT = 180000; // Period to reconnect MQTT connection.

// Comparison functor to map functions.
struct cmp_str
{
  bool operator()(char const *a, char const *b) const
  {
    return strcmp(a, b) < 0;
  }
};

// Map function declaration.
typedef std::function<void()> FunctionType;
typedef std::map<const char *, FunctionType, cmp_str> mapTopicSubscription;

/****************************************
 * Define Instances
 ****************************************/
Ubidots client(TOKEN);
Adafruit_NeoPixel pixels(NUMPIXELS, NEOPIXELSPIN, NEO_GRB + NEO_KHZ800);
DHT dht(DHTPIN, DHTTYPE);
mapTopicSubscription ubiSubTopic;

/****************************************
 * Main Functions
 ****************************************/
void setup()
{
  initTime = millis(); // Save the init time
  Serial.begin(115200);
  pinMode(LIGHTPIN, OUTPUT); // Declare pin mode
  // Defines the mapped functions to handle the subscription event.
  ubiSubTopic[VAR_SUB_1] = &subscriptionHandler1;
  ubiSubTopic[VAR_SUB_2] = &subscriptionHandler2;
  client.ubidotsSetBroker("industrial.api.ubidots.com"); // Sets the broker properly for the
                                                         // business account.
  client.setDebug(true);                                 // Pass a true or false bool value to activate debug messages.
  client.wifiConnection(WIFINAME, WIFIPASS);             // Establish WiFi connection.
  client.begin(callback);
  dht.begin();    // Initializes DHT sensor.
  pixels.begin(); // Initializes NeoPixel Ring.
  pixels.clear(); // Set all pixel colors to 'off'.
  // Establishes subscription with variables defined.
  client.ubidotsSubscribe(DEVICE, VAR_SUB_1);
  client.ubidotsSubscribe(DEVICE, VAR_SUB_2);
}

void loop()
{
  // Re-establishes subscription with variables defined when connection is lost or every 3 minutes.
  if (!client.connected() || abs(millis() - initTime) > SECONDS_TO_RECONNECT)
  {
    initTime = millis();
    client.reconnect();
    client.ubidotsSubscribe(DEVICE, VAR_SUB_1);
    client.ubidotsSubscribe(DEVICE, VAR_SUB_2);
  }

  client.reconnect();

  // Reading temperature, humidity and soil moisture values.a
  float humidity = dht.readHumidity();
  float temperature = dht.readTemperature();
  int soilMoisture = analogRead(MOISTUREPIN);
  // Compute heat index in Celsius (isFahreheit = false).
  float heatIndexC = dht.computeHeatIndex(temperature, humidity, false);

  // Check if any reads failed and exit early (to try again).
  if (isnan(humidity) || isnan(temperature))
  {
    Serial.println(F("Failed to read from DHT sensor!"));
  }

  // Controls NeoPixel's colors based on the temperature values.
  if (bottomLight)
  {
    if (inRange(temperature, 0, 16))
      colorWipe(blue, 50);
    if (inRange(temperature, 16, 21))
      colorWipe(green, 50);
    if (inRange(temperature, 21, 26))
      colorWipe(yellow, 50);
    if (inRange(temperature, 26, 40))
      colorWipe(red, 50);
  }

  // Adds variables to be published to Ubidots.
  client.add(VAR_PUB_1, temperature);
  client.add(VAR_PUB_2, humidity);
  client.add(VAR_PUB_3, soilMoisture);
  client.add(VAR_PUB_4, heatIndexC);

  // Publishes all variables added into the device defined.
  client.ubidotsPublish(DEVICE);
  client.loop();

  delay(1000);
}

/****************************************
 * Subscription Functions
 ****************************************/

// Function to be executed when var_sub_1 change its status.
void subscriptionHandler1()
{
  if (value == 1)
  {
    Serial.println("Planter Lamp turned ON.");
    digitalWrite(LIGHTPIN, HIGH);
  }
  else
  {
    Serial.println("Planter Lamp turned OFF.");
    digitalWrite(LIGHTPIN, LOW);
  }
};

// Function to be executed when var_sub_2 change its status.
void subscriptionHandler2()
{
  if (value != lastValue)
  {
    if (value == 1)
    {
      Serial.println("Planter bottom light turned ON.");
      for (int i = 0; i < 3; i++)
      {
        colorWipe(red, 50);
        colorWipe(green, 50);
        colorWipe(blue, 50);
      };
      colorWipe(white, 200);
      bottomLight = true;
    }
    else
    {
      Serial.println("Planter bottom light turned OFF.");
      colorWipe(white, 50);
      colorWipe(black, 200);
      bottomLight = false;
    }
  }
  lastValue = value;
};

/****************************************
 * Auxiliar Functions
 ****************************************/
// Return an int with the length of a char
int strLen(char *s)
{
  int l = 0;
  while (*s != '\0')
  {
    s++;
    l++;
  }
  return (l);
}

// Callback to handle subscription
void callback(char *topic, byte *payload, unsigned int length)
{
  char *variableLabel = (char *)malloc(sizeof(char) * 30);
  getVariableLabelTopic(topic, variableLabel); // Saves the variable label.
  value = btof(payload, length);               // Saves the value of the variable subscribed.
  executeCases(variableLabel);                 // Executes the function handler for the
                                               // variable subscribed.
  free(variableLabel);                         // Free memory.
}

// Parse the topic received to extract the variable label.
void getVariableLabelTopic(char *topic, char *variableLabel)
{
  sprintf(variableLabel, "");
  for (int i = 0; i < numberOfVariables; i++)
  {
    char *resultLv = strstr(topic, variableLabels[i]);
    if (resultLv != NULL)
    {
      uint8_t len = strlen(resultLv);
      char result[100];
      uint8_t i = 0;
      for (i = 0; i < len - 3; i++)
      {
        result[i] = resultLv[i];
      }
      result[i] = '\0';
      snprintf(variableLabel, strLen(result) + 1, "%s", result);
      break;
    }
  }
}

// Cast from an array of chars to float value.
float btof(byte *payload, unsigned int length)
{
  char *demo_ = (char *)malloc(sizeof(char) * 10);
  for (int i = 0; i < length; i++)
  {
    demo_[i] = payload[i];
  }
  return atof(demo_);
}

// Executes the respective "Subscription Function" based on the value received.
void executeCases(char *variableLabel)
{
  if (ubiSubTopic.find(variableLabel) != ubiSubTopic.end())
  {
    mapTopicSubscription::iterator i = ubiSubTopic.find(variableLabel);
    (i->second)();
  }
}

// Fills NeoPixel ring pixels one after another with color.
void colorWipe(NeoPixelColor color, int wait)
{
  int r, g, b;

  r = myColors[color][0];
  g = myColors[color][1];
  b = myColors[color][2];

  for (int i = 0; i < pixels.numPixels(); i++)
  {
    pixels.setPixelColor(i, r, g, b);
    pixels.show();
    delay(wait);
  }
}

// Verifies if the value received is in the expected range
bool inRange(float x, int low, int high)
{
  return ((x - low) > 0 && (high - x) >= 0);
}

5. Verifique su código dentro del IDE de Arduino. Para ello, en la esquina superior izquierda de nuestro IDE Arduino verás el icono"Marca de verificación"; selecciónalo para verificar tu código.

6. Cargue el código en su "NodeMCU 1.0". Para ello, elige el icono dela "flecha hacia la derecha" situado junto al icono dela "marca de verificación".

7. Para verificar la conectividad del dispositivo y los datos enviados, abra el monitor serie seleccionando el icono"lupa" en la esquina superior derecha del IDE Arduino para ver los registros de conectividad, así como las respuestas del servidor Ubidots.

8. Tras unos segundos de inicialización, el código proporcionado creará automáticamente un nuevo dispositivo en su cuenta de Ubidots. Vaya a la sección de dispositivos de su cuenta de Ubidots; debería ver los nuevos dispositivos creados automáticamente:

Entre en el dispositivo "Plantador" y vea las variables configuradas transmitiendo los datos:

9. A continuación, crea dos nuevas variables raw para establecer la suscripción y controlar las luces a distancia. Para ello, haga clic en "Añadir Variable > Variable Raw" y asigne la etiqueta de variable definida en el código. Para el código de ejemplo proporcionado, las etiquetas de las variables a crear son "luz-1" y "luz-2".

Con las variables creadas correctamente, deberías tener el siguiente resultado:

5. Dashboard - Control y supervisión

Con todo ya integrado, es hora de presentar los datos de los dispositivos en un Dashboard. Además, el dashboard servirá también para controlar el estado de ambas lámparas.

1. Para crear su primer dashboard, vaya a la Dashboard (Datos → Cuadros de mando). A continuación, seleccione el icono más (+) en la parte superior derecha , luego seleccione el tipo de widget deseado. Debería poder crear cuadros de mando como el siguiente:

Más información sobre Ubidots Dashboards: Branding de aplicaciones: Estilos personalizados para sus cuadros de mando y widgets

Ubidots es muy intuitivo para cualquier usuario a la hora de administrar sus datos, permitiendo a cualquiera personalizarlo tanto como quiera; en caso de que quieras saber más, te recomiendo que eches un vistazo a las siguientes guías:

2. Ubidots soporta eventos ya integrados para permitirle enviar Eventos, Alertas y Notificaciones basadas en los datos de sus sensores y actuadores.

Consulta el tipo de eventos que se admiten y aprende a organizar cada uno de ellos:

  1. Notificaciones por correo electrónico
  2. Notificaciones por SMS
  3. Eventos Webhook - más información
  4. Notificaciones de Telegram
  5. Notificaciones de Slack - más información
  6. Notificaciones de llamadas de voz - más información
  7. Notificación de vuelta a la normalidad - más información
  8. Notificaciones de geocercas - más información

6. Resumen

Después de un par de horas de creación y codificación,(y un montón de cafés), di vida a este Smart Planter:

Como mencioné al principio, hice esto porque no sólo quería una nueva decoración de escritorio, sino también, sentí que le faltaba algo a Lana, quería inventar mi propia forma de comunicarme con ella a través de IoT y datos. En caso de que quieras otras formas de comunicarte con tus plantas puedes escucharlas usando el MIDI Sprout. Esto sí que mola, ¿verdad? 🤩

Esto es sólo la V0 del Smart Planter y hay algunas cosas que quiero mejorar,como el diseño, la organización de la electrónica con una PCB personalizada, y la integración con otros servicios como Google Assistance, Alexa, y Twitter.

Espero que les haya gustado tanto como a mí. Si tienes algún comentario o pregunta sobre el proyecto, no dudes en ponerte en contacto conmigo. Estoy totalmente abierto a comentarios constructivos para mejorar este tipo de proyectos y compartirlos con vosotros. 😁💚

Más proyectos IoT útiles: