Arduino enviando datos (Parte 3)

Ricardo Vega

Ricardo Vega / 29 junio 2016

⏰ 8 minutos

Este post continúa el primer ejemplo sobre "Arduino enviando datos" que ya comenzamos en los dos anteriores posts. En ellos, habíamos introducido los conceptos claves y montado la estructura del servidor con Python.

Con todo lo que hemos hablado en estos posts previos, queda claro que, aunque esta serie se llama "Arduino enviando datos", éste sóla va a tener una función en este primer ejemplo:

Leer los datos de un sensor y enviarlos a un servidor

Para acotar un poco más esta función a nuestro ejemplo real, vamos a definir el sensor como el más que conocido por los lectores de este blog sensor de temperatura y humedad DHT11 (concretamente recogeremos sólo el valor de temperatura) y en cuanto al servidor, vamos a hacer una petición POST a http://URL-SERVIDOR:PORT/api/v1.0/temperature.

Lógicamente, para llevar a cabo esta tarea con nuestro Arduino, vamos a necesitar una conexión a red. En mi ejemplo, y como ya comenté en el post que introducía toda esta serie, usaré un Arduino UNO y una shield Ethernet. Si tu configuración es diferente, deberás modificar tu código para adaptarlo a ella.

Arduino REST

También vamos a necesitar la librería del sensor DHT y un par de extras nuevos que nos van a ayudar a montar la petición POST.

Arduino y REST

Por un lado vamos a usar la magnífica librería RestClient for Arduino (que nos obliga a usar también SPI y Ethernet). Gracias a esta librería vamos a poder montar de forma sencilla las peticiones GET y POST que necesitemos hacer. (Repito, en nuestro caso sólo va a ser POST por parte de Arduino).

La otra librería que vamos a emplear y que es ArduinoJSON. Aunque no es obligatorio, es muy extendido el uso del formato JSON para trabajar con REST. Ésta librería, nos ayudará precisamente a montar esta estructura de datos de forma sencilla, lo que sin duda es una gran ayuda.

Estas son nuestras herramientas. Veamos por tanto el código:

En primer lugar y como es costumbre, incluimos el código:

#include <Ethernet.h>
#include <SPI.h>
#include "DHT.h"
#include <ArduinoJson.h>
#include "RestClient.h"

A continuación, definimos una serie de variables globales que tienen que ver con la configuración del sistema:

#define DHTPIN 2          // Sensor Pin
#define DHTTYPE DHT11     // DHT 11
#define delayTime 300     // Time in seconds beetwen sendings
#define IP "192.168.1.6"  // Server IP
#define PORT 5000         // Server Port

Con ello podemos ya instanciar tanto el RestClient como el DHT:

DHT dht(DHTPIN, DHTTYPE);
RestClient client = RestClient(IP, PORT);

A continuación, hacemos todas las configuraciones necesarias en nuestro setup():

//Setup
void setup() {
  Serial.begin(9600);
  // Connect via DHCP
  Serial.println("connect to network");
  client.dhcp();
  dht.begin();
/*
  // Can still fall back to manual config:
  byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
  //the IP address for the shield:
  byte ip[] = { 192, 168, 2, 11 };
  Ethernet.begin(mac,ip);
*/
  Serial.println("Setup!");
}

Como ves en el comentario (extraído directamente de los ejemplos de RestClient), esta librería nos permite autoconfigurar la red con client.dhcp() pero también nos da la posibilidad de hacer la configuración por nosotros mismos con el código que viene comentado.

Como hablamos en nuestro anterior post, nuestra API está securizada. Existen diferentes métodos para llevar esto a cabo, pero en nuestro caso, vamos a usar HTTPBasic, mediante el cual, la autentificación se lleva a cabo mediante usuario:contraseña codificado como Base64.

Esta información debe ir en las cabeceras de la llamada POST que hagamos desde nuestro Arduino, pero la librería que he escogido admite la opción de "setear" valores en las cabeceras si así queremos. También especificaremos que el mensaje que estamos enviando es JSON:

client.setHeader("Authorization: Basic cmljdmVhbDoxMjM0==");
client.setHeader("Content-Type: application/json");

Otro punto especialmente interesante es cómo montamos el JSON que vamos a enviar. Suponiendo que tenemos un valor "t" con la temperara en formato Float hacemos lo siguiente:

char json[256];
JsonObject& root = jsonBuffer.createObject();
root["temperature"] = t;
root.printTo(json, sizeof(json));

Y ya tenemos en "json" un array de chars disponible para ser usado con RESTClient:

client.post("/api/v1.0/temperature", json, &response);

Con todo esto, el código completo, con algunas modificaciones extra para poder comprobar que estamos enviando a través del monitor Serie sería:

/* RestClient for sending JSON data sensor
 *
 * This implementation uses Ethernet Shield
 *
 * by Ricardo Vega (ricveal)
 */

#include <Ethernet.h>
#include <SPI.h>
#include "DHT.h"
#include <ArduinoJson.h>
#include "RestClient.h"

#define DHTPIN 2     // Pin de conexion del sensor
#define DHTTYPE DHT11   // DHT 11
#define delayTime 300  // Time in seconds beetwen sendings
#define IP "192.168.1.6" // Server IP
#define PORT 5000     // Server Port

DHT dht(DHTPIN, DHTTYPE);
RestClient client = RestClient(IP, PORT);

//Setup
void setup() {
  Serial.begin(9600);
  // Connect via DHCP
  Serial.println("connect to network");
  client.dhcp();
  dht.begin();
/*
  // Can still fall back to manual config:
  byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
  //the IP address for the shield:
  byte ip[] = { 192, 168, 2, 11 };
  Ethernet.begin(mac,ip);
*/
  Serial.println("Setup!");
}

String response;
void loop(){
  float t = dht.readTemperature();
  response = "";
  client.setHeader("Authorization: Basic cmljdmVhbDoxMjM0==");
  client.setHeader("Content-Type: application/json");
  StaticJsonBuffer<200> jsonBuffer;
  char json[256];
  JsonObject& root = jsonBuffer.createObject();
  root["temperature"] = t;
  root.printTo(json, sizeof(json));
  Serial.println(json);
  int statusCode = client.post("/api/v1.0/temperature", json, &response);
  Serial.print("Status code from server: ");
  Serial.println(statusCode);
  Serial.print("Response body from server: ");
  Serial.println(response);
  delay(delayTime*1000);
}

Como ves, recogemos el statusCode para comprobar cómo ha ido la petición. Si todo es correcto, deberíamos recoger un 200. Otro punto que queda por aclarar es ese "cmljdmVhbDoxMjM0==" que aparece en la autentificación. Como dije antes, es usuario:contraseña pasado a Base64. En mi caso, he usado ricveal:1234 y para hacer la conversión, he usado ésta herramienta online (pestaña enconde). Toda esta configuración es análoga a la que definimos en el anterior post en el lado Python.

Comprobaciones y conclusiones

Llegados a este punto, estamos en condiciones de probar todo el sistema. Borramos y volvemos a crear la base de datos como expliqué en el post anterior para asegurarnos que no existe ningún dato.

Arrancamos el servidor con "python api.py"

Enchufamos el Arduino al ordenador y controlamos el puerto serie.

Si todo va bien, empezaremos a ver cómo los datos que enviamos reciben un código 200. Dejamos actuar el sistema por unos minutos y desconectamos el Arduino para dejar de enviar datos.

Abrimos una terminal de nuestro equipo donde hemos ejecutado "api.py" y sin detenerlo, ejecutamos (en otra pestaña):

curl -u ricveal:1234 localhost:5000/api/v1.0/temperature/

Deberíamos ver los datos que anteriormente hemos introducido. También podemos comprobar un dato específico con:

curl -u ricveal:1234 localhost:5000/api/v1.0/temperature/2

Otra opción que tenemos y que tal vez te resulte más sencilla es introducir estas llamadas en tu navegador. El resultado debería ser el mismo (previamente a mostrar la información te solicitará los credenciales). Dirás, "¡tendrías que haber empezado por ahí!", pero realmente, acostumbrarse al uso de "curl" puede ser realmente interesante para pruebas más avanzadas. Eso sí, esta herramienta, por defecto, sólo esta disponible en UNIX (OSX y Linux) por lo que si trabajas con Windows tendrñas que usar el navegador o instalar Curl.

Como has visto, gracias a éste modo de trabajo tenemos disponible una forma sencilla de comunicar y almacenar los datos procedentes de diferentes sensores. Es más, creo que este modo de trabajo es interesante porque es muy sencillo de montar y te permite, a partir de aquí y con muy pocas modificaciones, añadir todos los sensores que tengas en tu casa, de modo que montar un sistema centralizado para almacenar esta información y visualizarla se convierte en una opción sencilla y asequible para el ámbito doméstico.

Como principales desventajas tenemos dos:

  • A medida que vamos añadiendo más y más dispositivos podemos saturar el sistema porque el servidor no es capaz de procesar todas las llamadas a la vez (tanto de lectura como de escritura). Debo dejar claro que estoy hablando de muchas llamadas (no 10^^).
  • Si el nodo central (servidor) falla, todo el sistema deja de almacenar.

Puedes pensar que la primera desventaja no te afecta porque no vas a montar un sistema suficientemente grande como para saturar el sistema. Tal vez tengas razón, pero debes pensar que si se satura, se debe a que no puede recibir más llamadas o lo que es lo mismo, toda la capacidad de cómputo está ocupada. Si con otros sistemas, puedes hacer más llamadas sin llegar a la saturación, también significa que requieren menos capacidad de cómputo por lo que a igualdad de llamadas, existen sistemas más eficientes.

Precisamente en próximos posts hablaremos de algunos de estos sistemas así como algunas configuraciones extra con las que podemos mejorar lo que hemos planteado en estos últimos 3 posts.

¡Un saludo y hasta la semana que viene! :)

Subscríbete a mi newsletter

Actualmente estoy llevando a cabo algunos cambios en mi Newsletter y no es posible apuntarse 😞

Ver ediciones pasadas