Termómetro conectado online

From Wiki Makespace Madrid
Jump to: navigation, search

Termómetro conectado online[edit]

ESTADO: Versión Inicial

Miembros: User:Tumaku

English version


Objetivo[edit]

Publicar online la temperatura de un térmometro a través de los servidores en la nube de electricImp, Carriots y Ducksboard

Flow eImp carriots ducksboard.jpg


HW Temperature eImp.JPG

Motivación[edit]

Empezar a trabajar sobre un proyecto real con las muestras que electricImp ha donado a Makespace. Si se instala en el espacio, el proyecto permite consultar desde interner la temperatura real de Makespace Madrid

Antecedentes[edit]

Se asume que ya se ha creado una cuenta en electricImp y que se ha configurado el electricImp con los parámteros de la red WiFi a utilizar. Estos pasos se describen en Hola Mundo eImp

Métodos y técnicas utilizadas[edit]

En este proyecto seutilizan los siguientes componenetes o servicios:

  • Sensor de temperatura DHT11 [1]
  • Arduino Uno (o Pro Mini 5V)
  • Convertidor de nivel [2]
  • Dispositivo 'imp' de electricImp con cuenta gratuita y servicio en la nube [3]
  • Cuenta gratuita y servicio en la nube de carriots [4]
  • Cuenta gratuita y servicio en la nube de ducksboard [5]

Resumen[edit]

El proyecto publica online las lecturas de temperatura obtenidas de un sensor DHT11.

  • La lectura se obtiene por medio de un Arduino (se utiliza el Arduino porque la versión actual de electricImp no soporta el protocolo propietario del sensor DHT11). En la versión actual Arduino toma una medida cada minuto
  • La medida de temperatura se pasa a un dispositivo imp de electricImp para que lo envíe a un agente en el servidor de electricImp en internet
  • El agente de electricImp recibe la medida y se la pasa al servidor de Carriots a travñes de su API http REST. Se utiliza Carriots para poder mantener una serie histórica de todos los valores de temperatura medidos
  • Cuando el servidor de Carriots recibe una nueva medida de temperatura, un "listener" envía una petición http REST al servidor de Ducksboard para que actualice un tablero ("dashboard") en el que se muestran las últimas medidas recibidas

Detalles[edit]

1er Paso: Leer la temperatura desde Arduino y enviarla al dispositivo 'imp'[edit]

El sensor de temperatura DHT11 es muy sencillo de utilizar pero no es "compatible" con el HW del imp debido a su protocolo "propietario" para leer la temperatura y a su alimentación a 5V (el imp sólo funciona a 3.3V). En el propio foro de electricImp recomiendan utilizar un micro intermedio (arduino) para leer la temperatura y pasar esa lectura al imp a través de una conexión serie [6]

Para leer la temperatura del sensor DHT11 desde Arduino nos basamos en la librería y el skecth de Rob Tilliart en [7]. El enlace describe como instalar y utilizar la librería desde el IDE de Arduino.

Nota: La foto del sensor DHT11 en el enlace anterior muestra que tiene 4 PINes en su parte inferior. Sin embargo, el sensor montado sobre una pequeña place PCB vendido en DealeXtreme [8] sólo expone 3 PINes. La siguiente foto describe la función de cada uno de esots PINes:

DHT11.jpg

El sketch de Arduino simplemente lee la temperatura del pin DHT11PIN (Digital pin 2 of the Arduino) y envía el valor leído por medio de una conexión serie (Pines 7 y 8 de Arduino: SoftwareSerial eImpSerial(7, 8)). Tras envíar el valor de temperatura, se manda un código especial (byte 0xFC) para indicar al otro extremo de la conexión serie (el 'imp') el final del envío.

Tras leer la temperatura y enviarla al 'imp' a través de la conexión serie, el arduino espera un minuto (60000 milisegundos) hasta la siguiente lectura.

A continuación se incluye el sketch de Arduino completo:


/* 
Simple sketch to read Temperature from DHT11 sensor
Written by Javier Montaner @tumaku_ for Makespace Madrid 
http://www.makespacemadrid.org

Based on YourDuino.com Example Software Sketch
DHT11 Humidity and Temperature Sensor test
Credits: Rob Tillaart
http://arduino-direct.com/sunshop/index.php?l=product_detail&p=162
terry@yourduino.com */

#include <dht11.h>
#include <SoftwareSerial.h>

dht11 DHT11;

#define DHT11PIN 2
SoftwareSerial eImpSerial(7, 8); // RX, TX

void setup()   
{
   Serial.begin(9600);
   eImpSerial.begin(9600);
   Serial.println("DHT11 TEST PROGRAM ");
   Serial.print("LIBRARY VERSION: ");
   Serial.println(DHT11LIB_VERSION);
   Serial.println();
}

void loop()  
{
   int chk = DHT11.read(DHT11PIN);
   Serial.print("Temperature (oC): ");
   Serial.println((float)DHT11.temperature, 2);
   eImpSerial.print((float)DHT11.temperature, 2);
   eImpSerial.write(0xFC);
   delay(60000);
}

2º Paso: Conectar Arduino e 'imp' a través del conversor de tensión[edit]

'imp' trabaja a 3.3V mientras que Arduino lo hace a 5V. No se pueden conectar directamente pues se corre el riesgo de quemar el 'imp' con el exceso de voltaje generado por Arduino. La solución es conectarlos a través de un convertidor de tensión intermedio. Se ha elegido el conversor de Sparkfun [9] pero se podría utilizar cualquier otro componente similar. En la web de Sparkfun hay un tutorial explicando como funciona este componente y en particular hay un ejemplo para una conexión serie entre Arduino y electricImp, exactamente nuestro objetivo sección "Using the LLC for Serial". Realizar las conexiones tal como se indican en el dibujo pero tener en cuenta que nuestro Arduino está utilizando los pines 7 y 8 para la conexión serie con el 'imp'. Los cables que salen de los pines 0/RX0 (verde) y 1/TX0 (amarillo) deberán salir de los pines 7 y 8.

Importante: La conexión serie del Arduino utiliza los pines 7 y 8 (no los pines 0 y 1)


Esquema Sensor Temperatura con Arduino bb.png

3er Paso: Crear una cuenta y un dispositivo en el servidor de Carriots[edit]

Se utiliza Carriots en el proyecto como una plataforma en la nube que permite almecenar series históricas de medidas de sensores. De esta forma se puede revisar la evolución histórica de valores o realizar minería de datos. En nuestro caso se podría comprobar la evilución de la temperatura en el tiempo y por ejemplo correlacionarla con los valores de temperatura ambiental publicados por el Ayuntamiento de Madrid o AEMET.

Tras crear una cuenta en el servidor de Carriots [10], hay que crear un dispositivo que permita almacenar las medidas de temperatura del sensor DHT11. Para ello hay que ir al panel de control de Carriots [11]. En el menú de la izquierda de la página seleccionar dentro de "Device Management" la opción "Devices" [12]. En esta página se presentan todos los dispositivos creados por el usuario y se permite definir nuevos dispositivos pulsando el botón "New".

Seleccionando esta opción se muestra el formulario para crear un nuevo dispositivo. Aunque hay muchos parámetros, bastará con personalizar los siguientes valores:

  • Name: seleccionar un nombre único. Automáticamente se genera el deviceId o Id developer como "deviceName@username". Este valor es importante pues se utiliza al enviar datos desde este dispositivo a la plataforma de Carriots)
  • Type: Other
  • Sensor: Temperature
  • Enabled: Seleccionado (Carriots descarta las medidas recibidas del dispositivo si no está marcado como "Enabled")

CarriotsDevice.jpg

4º Paso: Recepción de la temperatura por el 'imp' y subida a la nube de Carriots[edit]

En el proyecto Hola Mundo eImp ya se explicó que electricImp permite programar tanto una aplicación local en el dispositivo 'imp' como un "agente" en la nube en el servidor de electricImp. Esta doble posibilidad facilita la implementación de servicios en Internet y la vamos a utilizar para enviar al servidor de Carriots los datos de temperatura recibidos desde Arduino. Se recomienda repasar la sección "2º Paso: Cargar la aplicación Hola Mundo" del proyecto Hola Mundo eImp antes de seguir con este paso.

EImpConsole.jpg

  • Hacer login en la página de electric imp para acceder al IDE
  • En el menú de navegación de la izquierda buscar el dispositivo 'imp' que estamos utilizando (puede que se encuentro dentro de la pestaña "New Devices" o dentro de la pestaña de alguno de los modelos que se hayan definido)
  • Pulsar sobre el icono de configuración a la derecha del dispositivo imp
  • Pulsar sobre la lista desplegable "Associated Model" y escribir el nombre del nuevo modelo que vamos a crear "Temperature Carriots". Pulsar "Save Changes"
  • El modelo "Temperature Carriots" aparecerá en el menú de navegación de la izquierda y pulsando sobre él se muetra el nombre de nuestro dispositivo. Directamente o pulsando sobre el nombre del dispositivo, la sección principal del IDE mostrará el editor de código donde vamos a codificar la aplicación
  • La aplicación utiliza código en el dispositivo y en el servidor. El código del servidor se incluiría en la caja llamada "Agent" y el código del dispositivo en "Device".
  • Copiar y pegar el siguente código en la caja "Device"
server.log("Device Started");
local s="";
local sSize=0;
arduino <- hardware.uart57;

function arduinoData() {
 local b = arduino.read();
 while(b != -1) {
   sSize++;
   if (sSize>10) {
      sSize=0;
      s="";
      server.log("reset string");
   }
   if (b == 0xFC) {
       agent.send("sendTemperature", s);
       server.log(s);
       sSize=0;
       s="";
   }
   else {s= s+b.tochar();}
   b = arduino.read();
 }
}

arduino.configure(9600, 8, PARITY_NONE, 1, NO_CTSRTS, arduinoData);

  • Copiar y pegar el siguente código en la caja "Agent"
const CARRIOTS_URL = "http://api.carriots.com/streams/";
const jsonStringA= "{\"protocol\":\"v1\",\"at\": \"now\",\"device\":\"your_device_id\",\"data\":{\"temp\":\"";
const jsonStringB="\"},\"checksum\":\"\"}";

function postTemperature(tempString) {
 local body = jsonStringA + tempString + jsonStringB;
 local request = http.post(CARRIOTS_URL, {"carriots.apiKey":"your_api_key"}, body);
 local resp = request.sendsync();
 server.log("HTTPResponse: " + resp.statuscode + " - " + resp.body + " " + tempString);
}

device.on("sendTemperature", postTemperature);

Nota: Sustituir en el código las cadenas your_device_id y your_api_key por los valores obtenidos en el paso anterior.

  • Pulsar el botón "Build and Run" en la parte superior del IDE. La aplicación se compilará y si hay errores se mostrarán en la consola. Si todo va bien, tras la compilación el servidor de electric imp cargará la aplicación en nuestro imp a través de internet y la conexión wifi (el imp debe estar encendido y conectado a la red wifi)
  • Una vez cargada la aplicación, se empezará a ejecutar. El dispositivo imp espera a recibir datos a través de la conexión serie con Arduino. Cunado recibe un dato de temperatura, lo envía al agente en el servidor de electricImp y añade un nuevo mensaje en la ventana Log del IDE. Al recepcionar esta nueva lectura, el agente de electricImp la reenvía al servidor de Carriots y añade un mensaje al Log del IDE indicando el resultado de este envío.
Explicación básica del código del dispositivo 'imp'[edit]

Recordamos que el código del 'imp' se ejecuta de forma secuencial empezando desde la primera línea (las funciones se definen con la palabra clave function y no forman parte del flujo secuencial).

Disclaimer: No soy experto en squirrel, el lenguaje de programación de electricImp, así que pido disculpas por la mala calidad y organización del código.

Se define una variable s para almacenar la cadena de caracteres recibida desde Arduino a través de la conexión serie. La longitud de esta cadena se almacena en sSize (seguro que squirrel permite trabajar con cadenas de caracteres de una forma más sencilla, pero no la he encontrado).

También se define una variable arduino que representa la conexión serie. Se utilizan los pines 5 y 7 del imp (uart57):

arduino <- hardware.uart57;

Esta variable se inicializa más abajo. Además de especificar los parámetros técnicos de la conexión serie, se define la función a la que se llamará en el imp cuando se reciban datos a través de la conexión serie: arduinoData().

arduino.configure(9600, 8, PARITY_NONE, 1, NO_CTSRTS, arduinoData);

Se puede encontrar más información sobre las conexiones serie en el imp en la documentación de electricImp [13]

La función arduinoData() es la encargada de escuchar la conexión serie con Arduino. Cuando se recibe algún byte por esa conexión, el dispositivo imp llama a esta función. arduinoData() lee los bytes recibidos y los va almacenando en la variable s como una cadena de carateres. Este proceso continúa hasta que se recibe el código 0xFC, que es el mismo valor que se ha definido en el sketch de Arduino para identificar el final de un envío de datos. Si se leen más de 10 bytes antes de recibir un código de final de envío, se resetea la cadena s pues se asume que se están recibiendo datos eróneos o ruido.

Cuando se recibe el código de fin de envío, la función arduinoData() envía la cadena almacenada en la variable s (el dato recibido desde Arduino) al agente que está corriendo en el servidor de electricImp en la nube:

agent.send("sendTemperature", s);

La función agent.send() tiene dos parámetros. El primero es el nombre del mensaje que se hará llegar al agente en el servidor (sendTemperature en este caso). El segundo es el objeto que se pasará como parámetro a la función del agente encargada de recibir ese mensaje. Para más información, consultar la documentación del API de electricImp en [14] y [15]./docs/api/agent/

Explicación básica del código del agente en el servidor de electricImp[edit]

El agente del servidor también se codifica en squirrel.

En este proyecto, el agente sólo actúa cuando recibe un mensaje enviado desde el dispositivo 'imp' asociado. Acabamos de ver que 'imp' utiliza la función agent.send() para enviar un mensaje al agente. En el lado del agente, hay que registrar qué función procesará este mensaje, para ello se utiliza la función device.on():

device.on("sendTemperature", postTemperature);

Esta función tiene dos parámetros. El primero es el nombre del mensaje (sendTemperature en este caso) y el segundo es la función del agente que se encargará de procesar este mensaje (postTemperature() en el ejemplo). El segundo parámetro que el dispositivo incluye al llamar a la función agent.send() es un objeto cuyo valor se pasa como parámetro a la función elegida en el agente.

Centrándonos en nuestro ejemplo, el dispositivo imp envía el mensaje sendTemperature y como parámetro la cadena recibida desde Arduino (una lectura de temperatura). El agente en el servidor recibe este mensaje y llama a la función postTemperature() pasándole como parámetro la cadena que generó Arduino (la lectura de la temperatura).

La función postTemperature() simplemente formatea una petición http POST según la espicificación del API de Carriots incluyendo en el BODY de la petición una cadena JSON con el valor de temperatura recibido desde el dispositivo. Esta petición añade una nueva medida al dispositivo en el servidor de Carriots.

Los detalles sobre cómo funciona la petición http se describen en la documentación del API de eletricImp http, http POST y http request.

El formato de la petición al servidor de Carriots está definido en su API. En el tutorial How to send a stream using cURL se explica en detalle como formatear la petición con las cabeceras y parámetros necesarios.

En nuestro caso, se debe realizar una petición http POST a la URL http://api.carriots.com/streams/. Se debe incluir una cabecera con el API key de carriots (el API key del usuario que creó el dispositivo en Carriots y que tiene derecho a enviar valores desde ese dispositivo). En el código del agente de electricImp se debe sustituir la cadena your_api_key por el valor real de la API key.

Para conocer la API key, hay que ir a la consola de Carriots y seleccionar la pestaña "MySettings" y después "My Account" en el menú superior de la pantalla. El valor de la API_key que debemos usar es el denominado Full Privileges Apikey.

Como BODY de la petición http, Carriots espera un objeto JSON. Su formato es el siguiente:

{
   "protocol":"v1",
   "at":"now",
   "device":"your_device_id",
   "data":{
        "temp":"21"
   },
   "checksum":""
}

El timestamp de la medida se indica en el parámetro at. En el ejemplo se utiliza la cadena now. Este valor indica a Carriots que almacene la medida con el valor de su reloj en el momento de recibir el mensaje.

En el código del agente de electricImp se debe sustituir la cadena your_device_id por el valor real de la identidad del dispositivo en Carriots (por ejemplo TempMSM@makespacemadrid).

Las medidas recibidas por el servidor de Carriots se pueden ver seleccionando la opción "Data streams" dentro de "Data management" en el menú de la izquierda del panel de control [16]

5º Paso: Crear una cuenta y un panel en el servidor de Ducksboard[edit]

Se utiliza Ducksboard en el proyecto como una plataforma en la nube que permite visualizar "en vivo" medidas de sensores sin necesidad de desplegar o codificar un servidor propio.

El primer paso es crear una cuenta en el servidor de Ducksboard [17] (la cuenta es gratuita por un mes pero luego hay que pagar una subscripción si se quiere mantener el servicio).

Tras crear la cuenta, el siguiente paso es personalizar un panel o "dashboard". Utilizaremos el "Main dashboard" que se genera por defecto al crear la cuenta. Vamos a añadir dos "widgets" o ventanas al dashboard. En una de ellas simplemente mostraremos el último valor de temperatura recibido. En el otro, mostraremos una lista o "timeline" de los últimos valores reportados.

Ducksboard.jpg

Para definir un nuevo widget, pulsar el botón "+" en la esquina superior izquierda del dashboard. En la siguiente página, pulsar "Show your own data" (en la parte inferior de la pantalla).

El primer widget se crea seleccionando la opción "Your own numbers" y después "Counters". Pulsar sobre el botón "add this widget". Este widget presenta un contador con el último valor recibido. De fondo, también se incluye una gráfica de la evolución histórica de los valores recibidos.

Ahora se pueden personalizar los parámetros de visualización del widget:

  • Name: La cadena de texto que se muestra para identificar la ventana del widget
  • Color: El color que se utiliza para mostrar el valor del sensor
  • Timeframe: La escala que se utiliza para represntar la gráfica de evolución del sensor (horas, días, semanas...)

Elegir los valores deseados y seleccionar "save preferences for this widget" en la parte de abajo de la pantalla.

Una vez creado el widget, Ducksboard permite definir alarmas, hacer "pull" de medidas, recibir medidas en modo "push" desde el dispositivo o un servidor,... En nuestro caso sólo necesitamos implementar la funcionalidad "push" (el servidor de Carriots se encargará de enviar a Ducksboard los nuevos datos de temperatura tan pronto como estén disponibles). Para ello es necesario que nos fijemos en dos valores disponibles en la pestaña "DATA&API" del widget:

  • PUSH DATA TO THIS URL: es la dirección a la que Carriots enviará los datos de temperatura
  • YOUR API KEY: valor que se incluye en la cabecera de la petición http e identifica al cliente que envía el dato

En el siguiente paso se utilizarán estos dos valores.

Ducksboard Widget.jpg

El segundo widget presentará el "timeline" de los últimos valores de temperatura recibidos en Ducksboard. En la patalla principal del "Main dashboard" volver a pulsar el botón "+" y luego "Show your own data". Ahora seleccionar "Your own text", elegir "Timelines" y en ella la opción "Size: 1 column/2 rows". Como el widget anterior, utilizaremos los valores PUSH DATA TO THIS URL y YOUR API KEY en el siguiente paso.

Se pueden cambiar algunos parámteros de la configuración del panel pulsando el icono de configuración (una rueda dentada) en la pantalla principal del panel. Por ejemplo se puede definir un nuevo fondo o cambiarle el nombre. Desde configuración también se puede compartir el dashboard en público a través de una URL (pestaña "SHARE THIS DASHBOARD" dentro de la opción "PREFERENCES FOR THIS DASHBOARD"). Ducksboard permite dar un nombre al enlace que va a generar y opcionalmente protegerlo con una contraseña. Utilizar el botón "COPY LINK" para obtener la URL pública del dashboard compartido.

6º Paso: Publicación de los datos de temperatura en Ducksboard desde el servidor de Carriots[edit]

En este último paso el servidor de Carriots publica los valores de temperatura en el panel de Ducksboard que se acaba de definir. Para ello, se utiliza el API http de Ducksboard.

La plataforma de Carriots permite definir "listeners", bloques de código que se ejecutan cuando suceden determinados eventos, como por ejemplo la recepción de una nueva medida de un sensor. Este código se ejecuta en la plataforma en la nube de Carriots y se programa en groovy, un lenguaje dinámico basado en la máquina virtual de Java (JVM). Básicamente un listener define una regla basada en una condición. Si pasa A entonces ejecuta B y, si no, ejecuta C.

Carriots listener.jpg

La documentación de Carriots incluye un tutorial detallado sobre cómo crear un listener [18]. Siguiendo este tutorial vamos a ver cómo enviar los valores de temperatura a Ducksboard. En la página de creación de un nuevo listener se definen los siguientes parámetros:

  • Name: el valor que queramos darle a este listener (por ejemplo "temperaturaDucksboard")
  • Description: explicación de lo que hace (por ejemplo "envío de valores de temperatura a Ducksboard")
  • Entity type: Device (este listener aplica sólo a nuestro dispositivo)
  • Id: seleccionar la identidad del dispositivo creado en el paso 3
  • Event: Event Data Received (el listener será llamado cada vez que se reciba un valor desde el dispositivo seleccionado)
  • Enabled: seleccionado (si no se marca, el listener no está activo y no se ejecutará)

La lógica del listener se descibe en los campos If, Then, Else. En nuestro ejemplo, siempre enviamos el valor recibido a Ducksboard sin hacer ningun chequeo (en un proyecto real convendría hacer un chequeo de formato). La condición IF es siempre cierta (true) y no hace falta definir una claúsula ELSE.

If expression:
        (true)
Then expression:
        import com.carriots.sdk.utils.BasicHttp;
        def basicHttp = new BasicHttp();
        def map= ["user" : "your_ducksboard_api_key", "password":"unused"];
        basicHttp.http_auth=map;
        //basicHttp.url ="https://push.ducksboard.com/values/296067";
        basicHttp.url ="your_ducksboard_PUSH_URL_counter_widget";
        basicHttp.verb ="POST";
        basicHttp.payload='{"timestamp": '+ context.envelope.at + ', "value": ' + context.data.temp + '}';
        basicHttp.send();

        def basicHttp2 = new BasicHttp();
        def map2= ["user" : "your_ducksboard_api_key", "password":"unused"];
        basicHttp2.http_auth=map;basicHttp2.url ="your_ducksboard_PUSH_URL_timeline_widget";
        basicHttp2.verb ="POST";
        Date d= new Date();
        basicHttp2.payload='{"value":{"title": "Temperatura: ' + context.data.temp  +'", "image": ' +
             '"https://app.ducksboard.com/static/img/timeline/green.gif" , "content":"Tiemstamp: '+ d.toString() +'"}}';
        basicHttp2.send();

El código de la claúsula Then simplemente envía dos peticiones http POST al servidor de Duckstore siguiendo el formato JSON de su API. La primera petición se envía al widget Counter y la segunda al widget Timeline. La doucmentación de Carriots incluye un tutorial para integrar Ducksboard que explica en detalle este código .

Importante: sustituir en el código los valores your_ducksboard_api_key, your_ducksboard_PUSH_URL_counter_widget, your_ducksboard_PUSH_URL_timeline_widget por los valores que se han obtenido en el paso 5.

Una vez creado el listener, cualquier lectura de temperatura recibida por Carriots desde electricImp (paso 4) será reenviada a los dos widgets de Ducksboard. Además la medida también quedará almacenada en el servidor de Carriots

Nota: Los datos de temperatura se podrían enviar directamente desde el agente de electricImp a los widgets de Ducksboard sin necesidad de pasar por el servidor de Carriots, pero en ese caso se perdería la serie histórica de medidas.

Enlaces[edit]

  • Ducksboard público del sensor de Temperatura utilizado para construir el ejemplo: [19]

Ducksboard TempMSM.jpg