IoT de estar por casa: conectando un sensor con la Onesait Platform (parte 2)

Header IoT DHT22

Como decíamos la semana pasada, en el verano uno se puede aburrir un poco y darle por montar un panel de mando que muestre la temperatura de las habitaciones obtenidas mediante sensores IoT. Lo normal, vamos.

Ya hemos hablado de qué componentes necesitamos para montar el tinglado, y ya tenemos preparadas tanto la ontología como el servicio API REST que servirá para grabar los datos medidos en la ontología, así que sin más preámbulos pasemos a la parte divertida: enganchar los chismes.

Para no hacerlo muy largo, en la siguiente parte explicaré cómo generar el Dashboard, sus Gadgets y cómo preparar los DataSources para filtrar la información de la ontología.

Enganchar el sensor

Como lo que vamos a hacer es bastante sencillo, no vamos a necesitar ni placa de prototipado ni nada por el estilo, aunque si queréis usarla, adelante.

En primer lugar vamos a ver qué pines tiene el módulo DHT22:

IoT DHT22 Module

Como vemos, tiene tres patitas (por defecto el sensor tiene cuatro, siendo una de ellas sin funcionalidad (Not Connected Pin)), siendo cada una de ellas de izquierda a derecha en la imagen:

  1. VCC: pin de alimentación, marcado con el símbolo «+».
  2. Datos: el canal de salida de datos, etiquetado como «out».
  3. GND: el de tierra, marcado con el símbolo «»

Lo siguiente que hay que preguntarse es, ¿con cuánto se alimenta el sensor? Pues según la documentación con sus especificaciones técnicas, el sensor trabaja entre 3.3 y 6 voltios.

DHT22 - Power Supply

Puesto que el módulo con la que vamos a trabajar, el ESP32-WROOM-32D, tiene pines de alimentación tanto de 3.3V como de 5V, podremos usar cualquiera de los dos.

Veamos el diagrama del módulo (este concretamente es el que tengo yo, el DevKit V4):

Pincha sobre la imagen para verla muy, muy grande.

Como podréis ver, tenemos dos pines de alimentación disponibles; el de 3.3V situado arriba del todo a la izquierda, y el de 5V situado abajo a la izquierda. De tomas de tierra tenemos tres (marcados como «GND»).

Ambos serían válidos, y todo dependerá de cómo vayáis a alimentar la placa al final. En mi caso usaré el puerto micro USB como fuente de alimentación, por lo que el pin de 5V me queda disponible para alimentar al sensor DHT22.

Del tema del pin de datos, en muchos tutoriales se indica que se conecte al pin correspondiente al GPIO2; sin embargo, a la hora de cargar el código en el módulo WROOM si se está usando el pin VCC (el de 5V) el compilado no funciona según el modo en el que se encuentre la placa (ya hablaremos de esto después), por lo que he usado el GPIO4 (está dos por encima del GPIO2).

Por tanto las conexiones quedarán de este modo:

Módulo DHTMódulo ESP32-WROOM-32D
Pin+5V
PinoutGPIO4
PinGND
IoT Pin Connections
En la imagen, el cable azul lleva al pin +, el cable blanco al pin -, y el cable morado al pin GPIO4.

Para continuar, enchufaremos por el puerto micro USB el módulo ESP32-WROOM-32D a algún ordenador.

Preparar el entorno de desarrollo

Si sois noveles en todo esto, lo primero que os diría es que os descargarais el IDE de Arduino (también podéis hacerlo online, pero mejor en nuestro escritorio).

Por supuesto se pueden usar otras soluciones como Visual Studio Code y sus extensiones (ésta o esta otra, por ejemplo), pero para simplificar las cosas tiraremos de lo más básico; si queréis hacerlo vía VS, todo vuestro.

Una vez instalado, si enredamos en las opciones de selección de placa desde el menú Tools > Board, veremos que en el listado de placas disponibles no aparece la nuestra; únicamente tropecientas versiones de placas Arduino (es lo que tiene…).

Para que aparezca nuestra placa, tenemos que irnos a las propiedades del IDE, en el menú File > Preferences, y en la parte inferior donde pone eso de «Additional Boards Manager URLs», incluir el siguiente JSON:

https://dl.espressif.com/dl/package_esp32_index.json
IoT IDE Boards JSON
Si no se visualiza bien, pincha en la imagen para verla a mayor tamaño.

Si ya tuviésemos otra URL introducida aquí, metemos una coma y a continuación añadidos el JSON.

Hecho esto, volveremos al menú de Tools > Boards y seleccionaremos la opción de «Boards Manager». En la ventana que saldrá, tendremos que indicar que buscamos algo llamado «esp32», lo que filtrará los contenidos y saldrá la opción que nos interesa.

IoT Board Manager esp32

Bueno, pues la instalamos y con paz, que tarda un poco. Cuando haya terminado, podremos escoger nuestra placa desde el menú de Tools > Board, y ahí veremos un nuevo menú desplegable llamado «ESP32 Arduino», en donde podremos escoger nuestro modelo de placa, que para la que estoy usando sería «ESP32 Dev Module».

IoT Select Board

Cargando las librerías

Antes de ponernos con el pico y pala, hay que identificar qué librerías vamos a necesitar, para instalarlas y eso.

Así como listado rápido, necesitaremos una para el sensor de temperatura y humedad, otra para el Wifi, otra para conectarnos a un cliente HTTP, y otra para crear estructuras JSON.

Estas librerías las cargaremos en el IDE desde el menú Sketch > Include Library, y ahí seleccionaremos la opción de «Manage Libraries…». Nos saldrá una ventana similar a la de las placas, donde tendremos que buscar e instalar las siguientes librerías:

Nota: siempre os podéis bajar los archivos ZIP de estas librerías e instalarlos manualmente.

Bien, pues cuando esté todo listo, metámonos en harina.

Picando código

En primer lugar, crearemos un proyecto nuevo y lo salvaremos con un nombre reconocible. Hecho esto, cargaremos las librerías que vamos a utilizar.

// Load the necessary libraries
#include "DHT.h"
#include "WiFi.h"
#include "HTTPClient.h"
#include "ArduinoJson.h"

A continuación, vamos a definir cual es el tipo de sensor DHT que vamos a utilizar, y el GPIO que recibirá los datos. En caso de que estéis usando un sensor o módulo DHT11 o DHT12 de estrangis en este tutorial (que se puede teniendo en cuenta que el orden de las patitas es diferente), aquí es donde lo indicaríais.

// Point the GPIO
#define DHTPIN 4

// Set the DHT sensor type between DHT11, DHT12 or DHT22
#define DHTTYPE DHT22

// Initialize the DHT sensor
DHT dht(DHTPIN, DHTTYPE);

Con esto tendríamos definido ya lo que es el sensor y listo para solicitarle lecturas. Seguidamente vamos a configurar el Wifi para escapar de la placa. Aquí únicamente tenemos que indicar a qué red Wifi deseamos conectarnos, y cual es su clave.

// Set the name and password of the Wifi network
const char* ssid = "NETWORK_NAME";
const char* password =  "NETWORK_PASSWORD";

Ni que decir tiene que hay que cambiar lo de «NETWORK_NAME» y «NETWORK_PASSWORD» que está entrecomilla por el nombre y contraseña de la red. Ni que decir tiene.

El siguiente paso será incluir la información de conexión con la Plataforma; es decir, indicar la URL del servicio API REST y el token (el troncho alfanumérico) de identificación.

// Set the Onesait Platform endpoint and the identification token
const char* opEndpoint = "https://lab.onesaitplatform.com/api-manager/server/api/v1/NAME_OF_YOUR_ENDPOINT_MINE_IS_arduinoDHT22Endpoint";
const char* opToken = "USER_TOKEN_SEE_PART_1_FOR_MORE_INFORMATION";

Pues lo principal ya está. Ahora sólo queda definir un par (o tres) de propiedades.

En primer lugar hay que decidir cada cuanto tiempo queremos mandar datos a la ontología. El sensor mide, recordemos, con una frecuencia de 2 segundos, pero mandar un dato cada dos segundos es un poco excesivo, podríamos decir. Así que vamos a decir un valor más coherente, un minuto (60 segundos, vamos).

Luego, si recordamos el esquema de ontología que creamos, una de las propiedades correspondía al ID del sensor, y otro a su localización. Esto se debía a que podemos estar usando más de un sensor (como es el caso), y así a la hora de filtrar contenidos, nos es útil. Para poder reutilizar el código de un modo más sencillo, definimos esto aquí, para que sea fácil de actualizar posteriormente.

Por tanto añadiremos estas propiedades comentadas al código:

// Set the device ID & location
const char* deviceId = "esp32-wroom-32D_01";
const char* deviceLocation = "Dinning Room";

// Set the waiting time between data reads in seconds
int readTime = 60000;

Bueno, pues se acabaron las definiciones, y ahora pasamos a crear la configuración del programa. Aquí vamos a configurar el serial monitor para poder hacer pruebas, conectarnos al Wifi y empezar a leer los datos del sensor, ya que si no nos conectamos a la red, para qué vamos a estar midiendo (ya que no vamos a hacer nada en local con estos datos).

Como en casos anteriores, he dejado el código comentado para explicar un poco qué es cada cosa, que muchas veces ayuda a entender el proceso.

void setup() {
  // Set the serial monitor channel to listen
  Serial.begin(9600);

  // Connect to the Wifi network
  WiFi.begin(ssid, password);
 
  // Wait until the WiFi connection is ready
  while (WiFi.status() != WL_CONNECTED) {
    delay(5000);
    Serial.println("Connecting to the WiFi network... please stand by.");
  }
 
  // Notify when it has been able to connect successfully
  Serial.println("Connected to the network.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  
  // Notify that the DHT22 sensor is initializing
  Serial.println("Starting DHT22 sensor reading...");

  // Start to read temperature and humidity
  dht.begin();
}

Importante: hay veces que el módulo ESP32 no es capaz de conectarse a una red Wifi definida, o que se conecta para posteriormente desconectarse pasado un tiempo (minutos, horas, etc.), todo ello relacionado con el error AUTH_FAIL. Este es un error un poco random, y hay a quien no le pasa nunca, siempre, o de vez en cuando. Lo que sí parece claro es que es problema del router al que nos conectamos.

En mi caso, la solución que he encontrado es indicar una IP estática a la hora de conectarse a la red Wifi. Si queréis hacerlo, tendréis que definir debajo de «const char* password», por ejemplo, estas cuatro nuevas constantes:

// Set the static IP address
IPAddress staticIP(192, 168, 1, 12);

// Set the router IP
IPAddress gateway(192, 168, 1, 1);

// Set the subnet IP
IPAddress subnet(255, 255, 255, 0);

// Use Google DNS
IPAddress dns(8, 8, 8, 8);

Para el IP, indicad la dirección que os interese. En mi caso tengo reservados una serie de puertos como estáticos en el router, así que le asigné el 12 a la placa que se me desconectaba.

Seguidamente, y después de la línea de «WiFi.begin(ssid, password)», añadid la siguiente línea:

// Configure the connection with custom IPs
WiFi.config(staticIP, gateway, subnet, dns);

Hecho esto, a la hora de conectarnos a la red Wifi se conectará a dicha IP siempre.

Para terminar, definiremos el código que se reproducirá continuamente en la placa. En los comentarios os describo el sentido de cada parte del código.

void loop() {
  // Time between reads
  delay(readTime);

  // Get the humidity
  float humidity = dht.readHumidity();
  // Get the temperature as Celsius (the default)
  float temperature = dht.readTemperature();
  // Get the temperature as Fahrenheit (isFahrenheit = true)
  float fahrenheit = dht.readTemperature(true);

  // Check if any reads failed and exit early (to try again).
  if (isnan(humidity) || isnan(temperature) || isnan(fahrenheit)) {
    Serial.println("Something has... failed while reading from the DHT sensor.");
    return;
  }


  // Check if the Wifi connection is working
  if(WiFi.status() == WL_CONNECTED) {
    // Notify the user
    Serial.println("Sending data...");

    // Make the connection to the API REST Endpoint on the Onesait Platform
    HTTPClient http;
    http.begin(opEndpoint);
    http.addHeader("Content-Type", "application/json");
    http.addHeader("X-OP-APIKey", opToken);

    // Create the JSON
    StaticJsonDocument<200> json;

    // Set the JSON properties
    JsonObject ontologyInstance = json.to<JsonObject>();
    JsonObject arduinoDHT22 = ontologyInstance.createNestedObject("arduinoDHT22");

    // Add the specific data to each JSON property
    arduinoDHT22["deviceId"] = deviceId;
    arduinoDHT22["location"] = deviceLocation;
    arduinoDHT22["temperature"] = temperature;
    arduinoDHT22["humidity"] = humidity;

    // Set a string to store the JSON serialized
    String string2send;

    // Serialize the JSON, and show it in the serial monitor
    serializeJson(ontologyInstance, string2send);
    Serial.println(string2send);

    // Post the JSON in the Endpoint
    int httpCode = http.POST(string2send);

    // Get the payload posted
    String payload = http.getString();

    // Show the returning code from the API REST and show what data has been uploaded in the ontology
    Serial.println("HTTP Return Code: ");
    Serial.println(httpCode);
    Serial.println("HTTP Response Body: ");
    Serial.println(payload);

    // Close the connection
    http.end();
  }
}

He incluido la opción de medir la temperatura en grados Fahrenheit por si alguno le interesa obtener ese dato más que en grados Celsius (y porque en todos los tutoriales que hay por ahí vienen ambos datos, todo hay que decirlo).

Pues ya estaríamos. Con este código conseguimos recuperar el dato de temperatura y humedad cada minuto, asociarlo a un objeto JSON que incluye el ID del sensor y su localización, y mandar ese JSON hasta la Plataforma para introducir los datos en una ontología. Dicho así no parece para tanto, ¿verdad?

A continuación os dejo el código limpio (sin comentarios ni serials), por si queréis usarlo. He dejado comentadas las opciones que permiten conectarse a una IP estática. Si lo necesitáis, descomentad las líneas e indicar un IP válido.

Recordad también de cambiar el nombre y contraseña de la red Wifi, así como la URL del Endpoint, el token de usuario y el ID y localización de vuestro sensor, que sino os saldrán cosas rarunas después.

#include "DHT.h"
#include "WiFi.h"
#include "HTTPClient.h"
#include "ArduinoJson.h"

#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

const char* ssid = "NETWORK_NAME";
const char* password =  "NETWORK_PASSWORD";
//IPAddress staticIP(192, 168, XXX, YYY);
//IPAddress gateway(192, 168, 1, 1);
//IPAddress subnet(255, 255, 255, 0);
//IPAddress dns(8, 8, 8, 8);
const char* opEndpoint = "https://lab.onesaitplatform.com/api-manager/server/api/v1/NAME_OF_YOUR_ENDPOINT_MINE_IS_arduinoDHT22Endpoint";
const char* opToken = "USER_TOKEN_SEE_PART_1_FOR_MORE_INFORMATION";
const char* deviceId = "esp32-wroom-32D_01";
const char* deviceLocation = "Dinning Room";
int readTime = 60000;

void setup() {
  WiFi.begin(ssid, password);
  //WiFi.config(staticIP, gateway, subnet, dns);

  while (WiFi.status() != WL_CONNECTED) {
    delay(5000);
  }

  dht.begin();
}

void loop() {
  delay(readTime);

  float humidity = dht.readHumidity();
  float temperature = dht.readTemperature();
  float fahrenheit = dht.readTemperature(true);

  if (isnan(humidity) || isnan(temperature) || isnan(fahrenheit)) {
    return;
  }

  if(WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    http.begin(opEndpoint);
    http.addHeader("Content-Type", "application/json");
    http.addHeader("X-OP-APIKey", opToken);

    StaticJsonDocument<200> json;
    JsonObject ontologyInstance = json.to<JsonObject>();
    JsonObject arduinoDHT22 = ontologyInstance.createNestedObject("arduinoDHT22");

    arduinoDHT22["deviceId"] = deviceId;
    arduinoDHT22["location"] = deviceLocation;
    arduinoDHT22["temperature"] = temperature;
    arduinoDHT22["humidity"] = humidity;

    String string2send;
    serializeJson(ontologyInstance, string2send);

    int httpCode = http.POST(string2send);

    String payload = http.getString();

    http.end();
  }
}

Pero no se vayan todavía, aún hay más. Tenemos listo el código, de acuerdo, pero aun tenemos que subirlo a la placa. Normalmente con Arduino esto es tan sencillo como darle al icono de la flechita y listo, pero en el caso de esta placa no, porque tiene… un par de modos de inicio.

Por defecto, si intentamos subir código a la placa, no nos va a dejar. Para poder hacerlo, hay que mantener pulsado el botón «Boot» y entonces subir el código.

Como esto es un poco molesto, existe un combo para activar el modo descarga por defecto, y que nos permita subir el código directamente:

  1. Pulsar el botón «Boot» y mantenerlo pulsado.
  2. Pulsar el botón «EN».
  3. Soltar el botón «EN».
  4. Soltar el botón «Boot».

Ya sea que mantengamos pulsado el botón, hagamos el combo o no necesitéis hacer nada (a mi me pasó con una de las placas), subamos el código a la placa. Si todo va correctamente, deberá aparecer un mensaje final similar al que vemos a continuación:

IoT Arduino Hard Reset

Si habéis cargado la versión del código con los serial, podemos abrir el Serial Monitor, ajustar el canal de baudios a lo que tengamos definido (9600 para el ejemplo) y ver qué nos sale: en primer lugar debería salirnos un mensaje indicando que se está intentando conectar a la red Wifi. Si lo logra, saldrá otro mensaje indicando el IP con el que se conecta (que si lo hemos fijado como estático antes tendría que ser el definido) y, pasado el minuto de espera del loop, veremos aparecer un mensaje indicando que se está realizando la medición, la respuesta del servidor -que si es correcta será un 200- y el objeto que se ha cargado en la ontología.

IoT Arduino Serial Monitor

En caso de que usemos el código en limpio, o no nos apetezca mirar en el monitor, nos iremos hasta la Plataforma, y en el menú de Herramientas > Herramientas de consulta, elegiremos la ontología que estemos usando para almacenar los datos (que en mi caso era arduinoDHT22) y realizaremos la siguiente consulta:

SELECT * FROM arduinoDHT22

Si como esperamos y deseamos todo funciona como es de esperar, deberán aparecernos los registros existentes en la ontología; el primero de todos deberá ser el del POST manual que realizamos desde Swagger en la primera entrada, y todos los siguientes serán los que llegarán desde nuestro cacharro IoT:

Query Tool - arduinoDHT22

Está chulo, ¿eh? Pues si hemos llegado aquí, sabed que ya tenéis conectado un sensor IoT a la Plataforma, la cual recibe datos periódicamente, con los que podréis interactuar y hacer lo que os de la gana.

Ya para ir terminando, me gustaría recalcar un detalle, y es que si tenemos un par de sensores funcionando las 24 horas del día durante un año, a medida por minuto, estamos hablando de 1.051.200 registros al año, que no es poco.

Por tanto os diría que, si no vais a necesitar dichos datos a una semana o un mes visto, estaría bien ir limpiando las ontologías a fechas previas a dicho límite. ¿Que cómo se puede hacer esto? Dejadme deciros que FlowEngine es vuestro amigo: con un proceso persistente que vaya borrando registros previos a la fecha que indiquéis.

Si no sabéis cómo, ya os lo contaremos en algún tutorial… De momento, os recomiendo este cursillo sobre DataFlow que tenemos publicado en el blog, que seguro que os puede ayudar.


Pues esto es todo por el momento. Espero que os haya parecido interesante y que hayáis podido conseguirlo vosotros también, ya que no vamos a decir que sea un camino de rosas.

En la próxima entrada veremos cómo generar un Gadget que nos muestre la información de temperatura y humedad en tiempo real (es decir, cada minuto) en el Dashboard, y el DataSource que lo va a alimentar.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *