Vamos a construir un sistema de riego automático con Arduino controlado por voz a través de Alexa.
Gracias a todo el contenido que muchas personas comparten en Internet, he podido introducirme en el mundo del IoT. Quiero agradecer con mi humilde contribución, la generosidad de todos aquellos que ayudan a los demás compartiendo su conocimiento.
¿En qué consiste el proyecto?
La mejor forma de aprender en esto del IoT es marcarse un reto y empezar a buscar soluciones, investigando y leyendo sobre todas las materias que necesitas para llevarlo a cabo.
El reto que vamos a abordar, es el de construir un sistema de riego automático para unas jardineras. El sistema consiste en unos sensores de humedad de suelo, un dispositivo ESP8266 y una pequeña bomba de agua que se acciona cuando la humedad del suelo es muy baja.
El sistema está conectado a Internet a través de WiFi, de forma que podamos consumir un servicio MQTT con el que poder controlar los parámetros básicos del sistema de forma remota. Además escribimos en una base de datos de ThingSpeak todas las lecturas de los sensores de humedad para poder visualizar en todo momento el estado del sistema de riego.
Finalmente configuramos Alexa para poder accionar el riego y pararlo en cualquier momento o incluso usar las skills de Alexa para programar el horario y la duración de los períodos de riego.
Los sensores de humedad son muy poco fiables y tienen muchas variaciones en las medidas. Esto complica enormemente encontrar el valor correcto con el que determinar cuando la tierra no tiene humedad para activar la bomba de agua. Un valor muy alto implicará que la bomba no se activará nunca y un valor demasiado bajo podría llevarnos a que la bomba riegue demasiado.
Para evitarlo, usamos 4 sensores distribuidos por la jardinera de forma que calculamos la media de las 4 medidas. Para tomar las medidas de cada sensor, realizamos a su vez varias lecturas cada pocos milisegundos para minimizar el efecto del ruido de este tipo de sensores.
La lectura de la humedad del suelo varía incluso en función de la humedad relativa del aire o en función de la incidencia del sol en la tierra. Esto también dificulta encontrar el valor correcto de humedad en el que se debe disparar la bomba de agua. Fijar este valor en el código es bastante tediosa, ya que cada vez que tengas que cambiarlo te obliga a compilar y cargar de nuevo el código en el ESP8266.
La solución a este problema la encontramos usando un servidor MQTT. En mi caso he usado Adafruit por ser gratuito. Implemento un feed para definir el límite de humedad por encima del cual se debe accionar la bomba de agua y otro para implementar el tiempo de riego medido en segundos. Con estos dos valores puedo controlar el funcionamiento de todo el sistema.
Componentes
Empecemos por recopilar los componentes que necesitamos para desarrollar el sistema:
- 1 módulo ESP8266 (aliexpress / amazon)
- 1 relé (aliexpress / amazon)
- 1 multiplexor CD74HC4067 (aliexpress / amazon)
- 4 Sensores de humedad (aliexpress / amazon). Os recomiendo este tipo de sensor, hay otros que tienen dos patillas que se estropean muy rápido.
- 1 bomba de agua (aliexpress / amazon)
- 1 placa de islas para hacer las conexiones (aliexpress / amazon)
- 1 transformador AC/DC de 5V (en mi caso es reciclado de un electrodoméstico viejo)
- Combo de Amazon (1 Canal 5V Relé Módulo + Sensor de Humedad del Suelo + Mini Bomba de Agua DC 3V 5V + 1M Tubería de Agua de PVC)
Adicionalmente a los componentes electrónicos, necesitaremos tubo de riego, conectores, goteros y un depósito de agua.
Diseño
En el siguiente diseño esquemático puedes ver las conexiones del sistema. La alimentación a 5V está proporcionada por un cargador reciclado.
Aunque el diseño es sencillo, es aconsejable probar por partes el circuito.
Antes de usar el multiplexor, puedes probar el diseño configurando un solo sensor de humedad, que deberá ir en la única entrada analógica que dispone el ESP8266.
Configuración de la conexión con ThingSpeak
ThingSpeak™ es un servicio de plataforma de análisis de IoT que le permite agregar, visualizar y analizar flujos de datos en tiempo real en la nube. ThingSpeak proporciona visualizaciones instantáneas de los datos publicados por sus dispositivos en ThingSpeak. Con la capacidad de ejecutar código MATLAB® en ThingSpeak, puede realizar análisis y procesamiento en línea de los datos a medida que ingresan. ThingSpeak se usa a menudo para la creación de prototipos y la prueba de concepto de sistemas IoT que requieren análisis.
Empecemos con la creación del canal:
- Una vez que te hayas registrado, ve a la sección My Channels para crear una canal nuevo.
2. Escribe el nombre que quieras para tu canal junto con una pequeña descripción. A continuación indican los campos que vamos a rellenar desde nuestro sistema de riego. Es importante que mantengas estos nombres. En el caso de que quieras cambiarlos, deberás ajustar el código en consecuencia.
3. Una vez creado el canal, ve a la sección API Keys y copia los valores del Channel ID y de la Write API Key. Los necesitarás para configurar el código más adelante.
// Variables para definir la conexión con ThingSpeak
unsigned long myChannelNumber = 1234567; //Código de canal de Things Speak
const char * myWriteAPIKey = "API_KEY"; // Indicar aquí el código de escritura de ThingSpeak
4. Por último, ve a la sección privada para componer la visualización de los datos según tus preferencias.
Configuración de la conexión con Adafruit IO
Para poder manejar nuestro sistema de riego, vamos a usar un servidor MQTT publicado con Adafruit.
Vamos a definir dos variables, una a la que llamaremos humedad y que nos permitirá configurar el nivel de humedad con el que queremos activar la bomba. Recuerda que valores altos, indican una humedad más baja.
La otra variable, que llamaremos tiempo_de_riego, la usaremos para poder configurar el tiempo de riego antes de parar la bomba y volver a consultar los valores de humedad. Esto veréis que os dará mucho juego para poder configurar el riego como queráis. Es importante que no pongas un tiempo de riego muy elevado para evitar que se queme la bomba.
Una vez que tengas creadas las dos variables (las variables, se llaman Feed en Adafruit), busca la información acerca del MQTT Key, que lo necesitarás también en el código.
Una vez tengas las cadenas de MQTT Key, pulsa sobre el icono de la llave sobre fondo amarillo para activar el token de acceso seguro. Recuerda mantener estos valores en un lugar seguro. Copia de esta pantalla, el username y la active key.
Por último configura un dashboard para poder configurar las dos variables de humedad y tiempo_de_riego de forma fácil.
Truco: Puedes hacerte un acceso directo a este dashboard en tu móvil para poder ajustar los valores del sistema de riego cuando quieras.
Con esto lo tendremos todos para probar el código. Lo que haremos será suscribirnos a estos dos canales MQTT de forma que nuestro sistema lea los valores de configuración para humedad y tiempo de riego.
Código
A continuación tienes el código completo, pero recuerda que es mejor que vayas montándolo por partes, para asegurarte que todo va encajando bien, el hardware y las conexiones con ThingSpeak y Adafruit.
#include <ESP8266WiFi.h>; // Para la conexión WiFi
#include "ThingSpeak.h" // Para enviar los datos a ThingSpeak
#include <DHT.h>
#include "Adafruit_MQTT.h" // Para conectar con
#include "Adafruit_MQTT_Client.h"
// Definición de parámetros para servidor MQTT de Adafruit
#define AIO_SERVER "io.adafruit.com"
#define AIO_SERVERPORT 1883
#define AIO_NAME "AIO_NAME" // Sustituir por vuestra cuenta de Adafruit IO
#define AIO_KEY "/feeds/humedad"
#define AIO_PASS "AIO_PASS" // Sustituir por la password de Adafruit IO
#define MAXSUBSCRIPTIONS 5
WiFiClient client;
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_NAME, AIO_PASS);
Adafruit_MQTT_Subscribe limite_humedad = Adafruit_MQTT_Subscribe(&mqtt, "JacoboAriza/feeds/humedad");
Adafruit_MQTT_Subscribe tiempo_de_riego = Adafruit_MQTT_Subscribe(&mqtt, "JacoboAriza/feeds/tiempo_de_riego");
Adafruit_MQTT_Publish humedad_mqtt = Adafruit_MQTT_Publish(&mqtt, "JacoboAriza/feeds/humedad/get");
Adafruit_MQTT_Publish riego_mqtt = Adafruit_MQTT_Publish(&mqtt, "JacoboAriza/feeds/tiempo_de_riego/get");
// Usuarios y claves para la conexión WiFi
char ssid[] = "NOMBRE_WIFI_01"; //SSID - Red WiFi a la que me conecto
char pass[] = "PASSWORD_WIFI_01"; // Passowrd de la red WiFi
String ssis2 = "NOMBRE_WIFI_02"; //SSID - Red WiFi a la que me conecto
String pass2 = "PASSWORD_WIFI_02"; // Passowrd de la red WiFi
String ssid3 = "NOMBRE_WIFI_03"; //SSID - Red WiFi a la que me conecto
String pass3 = "PASSWORD_WIFI_03"; // Passowrd de la red WiFi
// Variables para definir la conexión con ThingSpeak
unsigned long myChannelNumber = 1234567; //Código de canal de Things Speak
const char * myWriteAPIKey = "API_KEY"; // Indicar aquí el código de escritura de ThingSpeak
// Variables a definir según la configuración deseada
const int numero_de_sensores = 4; // Número de sensores de humedad
const int numero_de_medidas = 10; // Número de medidas para hacer la media
int motorPin = 14;
int limite_medida = 900;
int tiempo_de_riego_maximo = 60;
int entrada_sensor_humedad = A0; // Entrada analógica con las entradas de los sensores de humedad.
int multiplexor[4] = {5,4,0,2};
int sensores[4][numero_de_sensores] = {{0,0,0,0},{1,0,0,0},{0,1,0,0},{1,1,0,0}};
float medidas[numero_de_sensores];
String texto = "";
float value_1 = 0;
float valor = 0;
String value = "";
// --------------------------------------------------------------------------------------------- //
// Descripción: Parametrización inicial //
// --------------------------------------------------------------------------------------------- //
void setup() {
pinMode(entrada_sensor_humedad, INPUT);
pinMode(motorPin, OUTPUT);
pinMode(multiplexor[0], OUTPUT);
pinMode(multiplexor[1], OUTPUT);
pinMode(multiplexor[2], OUTPUT);
pinMode(multiplexor[3], OUTPUT);
Serial.begin(9600);
digitalWrite(motorPin, HIGH);
WiFi.mode(WIFI_STA);
ThingSpeak.begin(client);
internet();
mqtt.subscribe(&limite_humedad);
mqtt.subscribe(&tiempo_de_riego);
}
// --------------------------------------------------------------------------------------------- //
// Descripción: Función principal //
// --------------------------------------------------------------------------------------------- //
void loop() {
// Nos conectamos al servidor MQTT donde tenemos los parámetros de configuración que regulan el riego:
// 1. Límite de humedad para accionar la bomba.
// 2. Número de segundos en los que se activa la bomba en cada ciclo.
MQTT_connect();
// Este bloque de código sirve para forzar que la lectura posterior siempre se realice.
Serial.println ("Leyendo variables...");
if (! humedad_mqtt.publish(0)) {
Serial.println(F("Failed"));
} else {
Serial.println(F("OK!"));
}
// Leemos el último valor publicado
Adafruit_MQTT_Subscribe * subscription;
while (subscription = mqtt.readSubscription(5000)) {
if (subscription == &limite_humedad)
{
Serial.print("Límite: ");
texto = ((char *) limite_humedad.lastread);
limite_medida = texto.toInt();
Serial.println(texto);
}
}
// Este bloque de código sirve para forzar que la lectura posterior siempre se realice.
if (! riego_mqtt.publish(0)) {
Serial.println(F("Failed"));
} else {
Serial.println(F("OK!"));
}
while (subscription = mqtt.readSubscription(5000)) {
if (subscription == &tiempo_de_riego)
{
Serial.print("Tiempo de riego: ");
texto = ((char *) tiempo_de_riego.lastread);
tiempo_de_riego_maximo = texto.toInt();
Serial.println(texto);
}
}
// Ping al servidor para mantener la conexión mqtt activa
if (!mqtt.ping())
{
mqtt.disconnect();
}
Serial.println("___________");
// Comenzamos con las lecturas de los sensores y con el cálculo de cada medida
float medida = 0;
float media_de_las_medidas = 0;
for (int i = 0; i < numero_de_sensores; i++){
for (int j = 0; j < 4; j++){
digitalWrite(multiplexor[j],sensores[i][j]); // Escribimos la codificación asociada a cada sensor para solicitar la medida al multiplexor.
}
medida = 0; // inicializamos la medida
for (int m = 0; m < numero_de_medidas; m++){ // Para establecer cada medida, hacemos 10 lecturas y calculamos la media
valor = analogRead(entrada_sensor_humedad);
medida = medida + valor;
}
medida = medida / numero_de_medidas;
media_de_las_medidas = media_de_las_medidas + medida;
medidas[i] = medida; // vamos escribiendo en la matriz de medidas todas los valores calculados
Serial.print("i:");
Serial.print(i);
Serial.print(", valor:");
Serial.println(medida);
delay(2000);
}
// Esperamos 8 segundos antes de subir los datos a ThingSpeak, de forma que no saturemos al servidor.
delay(8000);
subir_datos(medidas);
// Calculamos la medida de las medidas de los sensores
media_de_las_medidas = media_de_las_medidas / numero_de_sensores;
// A continuación verificamos las variables definidas en el servidor MQTT
Serial.print ("Verificando límite de humedad. Límite de medida: ");
Serial.println (limite_medida);
// Si la media de las medidas es mayor que el límite fijado en el servidor MQTT, accionamos la bomba por el tiempo definido.
if (media_de_las_medidas > limite_medida){
Serial.print ("Iniciando el riego durante ");
Serial.print (tiempo_de_riego_maximo);
Serial.println (" segundos...");
digitalWrite(motorPin, LOW); // Activamos el relé que acciona la bomba.
delay(tiempo_de_riego_maximo*1000);
}
digitalWrite(motorPin, HIGH); // Desactivamos el relé de la bomba.
if (WiFi.status() != WL_CONNECTED){ // Si el módulo se desconecta de la red WiFi, lo intentamos de nuevo.
internet();
}
}
// --------------------------------------------------------------------------------------------- //
// Descripción: Función para conectarse a la red WiFi //
// Nota: Durante las pruebas observé desconexiones frecuentes. Por este motivo he añadido la //
// opción de ir alternando entre dos de las redes WiFi que tengo en casa. Por favor ajustar //
// esta función según las necesidades particulares de cada uno. //
// --------------------------------------------------------------------------------------------- //
void internet()
{
int intentos = 0;
int intercambia = 0;
Serial.println("Iniciando conexión: ");
if (WiFi.status() != WL_CONNECTED)
{
while (WiFi.status() != WL_CONNECTED)
{
WiFi.begin(ssid, pass);
delay(5000);
Serial.print(".");
if (intercambia == 0){
char ssid[] = "MIWIFI_2G_TJYY_EXT"; //SSID - Red WiFi a la que me conecto
char pass[] = "DR32Uy6T"; // Passowrd de la red WiFi
intercambia = 1;
}else{
char ssid[] = "devolo-029"; //SSID - Red WiFi a la que me conecto
char pass[] = "WYKPYBLHGUASQNFA"; // Passowrd de la red WiFi
intercambia = 0;
}
}
Serial.println("");
Serial.println("WiFi conectado");
}
}
// --------------------------------------------------------------------------------------------- //
// Descripción: Función para subir los datos al servidor de ThingSpeak. Subimos las 4 medidas //
// de los sensores de humedad más la media de dichas medidas. //
// --------------------------------------------------------------------------------------------- //
void subir_datos(float medidas[numero_de_sensores]){
int canal = 0;
float media = 0;
media = 0;
for (int i=0; i < numero_de_sensores; i++){ // En los canales del 1 al 4 almacenamos las medidas de los sensores
canal = i+1;
media = media + medidas[i];
ThingSpeak.setField(canal,medidas[i]);
}
media = media / numero_de_sensores;
ThingSpeak.setField(5,media); // Calculamos la media de las medidas
// Hacemos un primer intento de subir los datos. Al usar una cuenta gratuita tenemos limitación sobre la velocidad a la que podemos subir información.
// En el caso de que falle, probamos pasados 2 segundos.
int mensaje = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
if (mensaje == 200){
Serial.println("Canal actualizado correctamente.");
}
else{
delay(2000);
if (mensaje == 200){
Serial.println("Canal actualizado correctamente.");
}
else{
Serial.println("Problema actualizando el canal. Código de error HTTP " + String(mensaje));
}
}
}
// --------------------------------------------------------------------------------------------- //
// Descripción: Función para conectarse al servicio MQTT de Adafruit IO //
// --------------------------------------------------------------------------------------------- //
void MQTT_connect()
{
int8_t ret;
// Si ya estamos conectados, salimos de la función.
if (mqtt.connected())
{
return;
}
Serial.print("Conectándose al servidor MQTT... ");
uint8_t retries = 3; // definimos un número máximo de 3 intentos
while ((ret = mqtt.connect()) != 0)
{
Serial.println(mqtt.connectErrorString(ret));
Serial.println("Reintentando la conexión con el servidor MQTT en 5 segundos...");
mqtt.disconnect();
delay(5000); // espera de 5 segundos
retries--;
if (retries == 0)
{
// basically die and wait for WDT to reset me
while (1);
}
}
Serial.println("MQTT conectado!");
}
Montaje
Así es como queda finalmente:
El montaje de las cajas estancas es gracias a Ángel y a su impresora 3D. Os animo a visitar su página que contiene proyectos muy interesantes sobre Arduino y con la Alhambra FPGA.
Configuración de la conexión con Alexa
Ya lo tenemos todo funcionando y ahora, ¿por qué no conectar nuestro sistema con Alexa? De esa forma podremos controlar por voz nuestro sistema de riego.
Para ello necesitaremos configurar un pequeño servicio con IFTTT y con el a su vez podremos configurar las skills de Alexa según nuestras necesidades. Yo lo utilizo para poder regar cuando lo necesite simplemente diciendo «Alexa, riega las plantas». También para configurar el riego de forma automática en los días que quiera, por la duración que necesite y a la hora deseada.
Básicamente nos conectaremos con Alexa a nuestro servidor MQTT definido en Adafruit IO a través de IFTTT, de forma que cambiemos el valor de humedad con el que se activa el riego. Por ejemplo, bajando el nivel de humedad a 0, estaremos asegurando que la bomba se active y empieza a regar. Del mismo modo, cuando queremos que deje de regar, enviaremos al servidor MQTT un valor alto de humedad y nuestra bomba se parará.
Vamos primero a configurar el servicio IFTTT activando la integración con Alexa y con Adafruit:
Publicamos dos applets, uno para iniciar el riego y otro para pararlo. Estos dos applet los podrás utilizar después desde Alexa para definir las skills.
Los dos applets siguen el siguiente esquema. Si en Alexa dices una frase en particular, entonces enviamos un valor al servicio MQTT humedad definido en Adafruit:
Enviamos el valor 0 para empezar a regar:
Y el valor 900 para dejar de regar:
Una vez lo tengas conectado solo tendrás que definir la rutina que quieras:
Consejos
Durante las pruebas, me he encontrado muchos problemas. Uno de ellos aunque ahora me parece obvio es el de los vasos comunicantes. Después de cada ciclo de riego empecé a observar que el agua del depósito se vaciaba muy deprisa y siempre que comprobaba los niveles me encontraba el depósito medio vacío. Empecé a sospechar que podría ser algún problema con el código y que se me quedaba en algunas situaciones el motor encendido.
Pero no, simplemente cuando dejaba de activar la bomba, la goma de riego estaba completamente llena y según el dichoso principio de los vasos comunicantes, el agua tendía a equilibrar los niveles. Cosa que significaba que el depósito se me vaciaba justo hasta el nivel del gotero que se encontraba a mayor altura.
Es decir la bomba, aunque esté apagada no corta la conexión con el tubo del riego. ¿Cómo lo he resuelto? He colocado esta conexión en el circuito de riego, entre el depósito y las jardineras, ubicada a una altura mayor que el nivel máximo del depósito. Regulando el gotero para que salgan apenas unas gotas (lo he colocado justo encima de una de las jardineras y así no desperdicio agua), consigo romper la estanqueidad del circuito y problema resuelto.
Otro de los problema ha sido con las cajas estancas. Al estar selladas con silicona, cuando hace demasiado calor, el aire que contienen se calienta mucho y se expande, llegando a romper algunas de las juntas. Para este problema te recomiendo aplicar dos medidas: la primera es no usar una pistola de silicona caliente, sino algún otro material que resista mejor los cambios de temperatura, como silicona apta para exteriores o cualquier otro pegamento que puedas encontrar en el mercado.
La segunda medida es pintar de blanco las carcasas, de esa forma reduces mucho el calor, sobre todo si el color del hilo utilizado en la impresora 3D es de algún color muy oscuro.
Después de varios meses, es posible que el agua pudiera generar algún tipo de algas. Aprovecha cuando tengas el bidón vacío para limpiarlas, ya que pueden llegar a atorar la bomba.
Por otro lado, es posible que el cable de la bomba sea muy corto y te quede alguna soldadura sumergida debajo del agua. En este caso, si que puedes usar una pistola de silicona líquida para sellar la soldadura.
Referencias
Os dejo algunas referencias que he consultado en Internet para ir componiendo el proyecto, cachito a cachito:
https://learn.adafruit.com/adafruit-io-basics-esp8266-arduino/arduino-io-library
https://learn.adafruit.com/adafruit-io-basics-esp8266-arduino/arduino-io-library
Guía para conectar tu ESP8266 con Google Assistant usando IFTTT (solectroshop.com)
Intro to Adafruit_MQTT | MQTT, Adafruit IO & You! | Adafruit Learning System
MQTT using AdafruitIO and ESP8266 | LEARN @ CIRCUITROCKS
MQTT using AdafruitIO and ESP8266 | LEARN @ CIRCUITROCKS
Conclusiones finales
En este proyecto hemos aplicado muchos conceptos que no es fácil encontrar juntos. Hablamos de conexión de un dispositivo Arduino a través de Internet, envío de información para su explotación en ThingSpeak, conexión con un servidor MQTT y control por voz a través de Alexa.
Aquí os dejo el fruto (nunca mejor dicho) de este proyecto, un montón de cosas aprendidas y una pequeña cosecha de tomates cultivados en casa. Rico, rico.