Arquitectura para el monitoreo de huertos con IoT y Azure (Parte I - Envío de mensajes a Event Hub)

Antes de iniciar este articulo, quisiera citar el post de Max Debolí Azure MVP donde explica una arquitectura propuesta para escenarios de IoT, en este caso veremos el envío de telemetría de un Huerto a soluciones de IoT de Azure. Me parece interesante y funcional para muchos de los casos que se pueden dar en una empresa que esté adoptando este tipo de tecnologías.

https://mdeboli.wordpress.com/2016/09/20/arquitectura-para-el-monitoreo-de-huertos-iot-azure/

Ahora que tenemos un mejor entendimiento del escenario, vamos a desarrollar una sería de publicaciones, donde veremos como programar paso a paso está solución, enfocando en como hacer la comunicación con Azure y luego procesar los datos.


Arquitectura propuesta:
En este post nos enfocaremos en esta parte de la arquitectura: Azure Event Hub IoT, en donde vamos a desarrollar una aplicación de Node.js que va a correr dentro de una Raspberry y luego enviaremos mensajes a Azure Event Hub.
image

Por ultimo les comparto un video donde explica el escenario que estamos atacando, de esta manera tendremos un mejor contexto de la solución:


Crear servicio Azure Event Hubs
Vamos al portal de Azure https://portal.azure.com y luego agregamos un nuevo servicio: “Event Hubs”, después ingresamos los datos de creación de nuestro nuevo recurso.
1
image

Una vez que Azure a finalizado con la creación del recurso, vamos a Event Hubs y navegamos a las opciones de nuestro nuevo servicio:
3
Lo siguiente es crear como tal un “Event Hub” que es donde vamos a estar enviando nuestros mensajes de telemetría de los dispositivos de IoT:
image
En la sección de alta del servicio vamos a capturar la información del recurso dejando las particiones por default (cuatro particiones) para hacer lecturas en paralelo:
image
Una vez creado nuestro Event Hub, agregaremos el token de acceso al Event Hub (SAS key) para enviar mensajes a nuestro recurso:
image
En nuestro caso vamos a dar permisos para manejo total de recurso de Event Hub, eligiendo la opción de “Manage”
image
Al final, veremos el nuevo SAS key de la siguiente manera:
image
Estas llaves las agregaremos posteriormente en nuestra aplicación de Node.js para comunicarnos con el recurso de Event Hub que hemos creado.

Crear programa en Arduino para lectura de telemetría de humedad de tierra
En nuestro escenario de monitoreo de huertos es necesario obtener mediciones de humedad de la tierra para luego poder procesar estos datos y posteriormente enviarlos a Azure para su futuro manejo de información.
Para lograr esto, vamos a crear un programa en C para obtener las lecturas de los sensores de humedad conectados a nuestros Arduinos. Una vez que obtengamos la data, vamos a generar objetos JSON y los enviaremos a una Raspberry por USB.
Una vez que nuestra Raspberry obtenga la información de los Arduinos, este enviará mensajes a Event Hub de la telemetría generada por los sensores.

Requisitos para programar en Arduino:
  1. Arduino IDE
  2. JSON Object Library
  3. YL-69 Library

Una vez que tengamos todos los requisitos para nuestro ambiente de desarrollo, agregaremos el siguiente código, el cual hará la comunicación con los sensores de humedad, y generará el objeto JSON para luego enviarlos por USB:

#include <ArduinoJson.h>

class HumiditySensor
{
   public:
     HumiditySensor(int sensorPin);
     void ConfigurePin();
     void Read();
     int sensorValue;
    
   private:
     int _sensorPin;
};
HumiditySensor::HumiditySensor(int sensorPin)
{
   this->_sensorPin = sensorPin;
   this->sensorValue = 0;
}
void HumiditySensor::ConfigurePin()
{
   pinMode(this->_sensorPin, INPUT);
}
void HumiditySensor::Read()
{
   this->sensorValue = analogRead(this->_sensorPin);
}

HumiditySensor sensorTest1(A0);
HumiditySensor sensorTest2(A1);
HumiditySensor sensorTest3(A2);
HumiditySensor sensorTest4(A3);
HumiditySensor sensorTest5(A4);
HumiditySensor sensorTest6(A5);

void OnRequestData()
{
   StaticJsonBuffer<250> jsonBuffer;
   JsonObject& jsonObj = jsonBuffer.createObject();
   jsonObj["did"] = "ARD01";
   JsonArray& sensorData = jsonObj.createNestedArray("sd");

  //a0 sensor
   JsonObject& sensorA0Data = sensorData.createNestedObject();
   sensorA0Data["pid"] = "A0";
   sensorA0Data["sm"] = "yl-69";
   sensorA0Data["h"] = sensorTest1.sensorValue;
   jsonObj.printTo(Serial);
   Serial.println();
  
   //a1 sensor
   JsonObject& sensorA1Data = sensorData.createNestedObject();
   sensorA1Data["pid"] = "A1";
   sensorA1Data["sm"] = "yl-69";
   sensorA1Data["h"] = sensorTest2.sensorValue;
   jsonObj.printTo(Serial);
   Serial.println();
  
   //a2 sensor
   JsonObject& sensorA2Data = sensorData.createNestedObject();
   sensorA2Data["pid"] = "A2";
   sensorA2Data["sm"] = "yl-69";
   sensorA2Data["h"] = sensorTest3.sensorValue;
   jsonObj.printTo(Serial);
   Serial.println();
  
   //a3 sensor
   JsonObject& sensorA3Data = sensorData.createNestedObject();
   sensorA3Data["pid"] = "A3";
   sensorA3Data["sm"] = "yl-69";
   sensorA3Data["h"] = sensorTest4.sensorValue;
   jsonObj.printTo(Serial);
   Serial.println();
  
   //a4 sensor
   JsonObject& sensorA4Data = sensorData.createNestedObject();
   sensorA4Data["pid"] = "A4";
   sensorA4Data["sm"] = "yl-69";
   sensorA4Data["h"] = sensorTest5.sensorValue;
   jsonObj.printTo(Serial);
   Serial.println();
  
   //a5 sensor
   JsonObject& sensorA5Data = sensorData.createNestedObject();
   sensorA5Data["pid"] = "A5";
   sensorA5Data["sm"] = "yl-69";
   sensorA5Data["h"] = sensorTest6.sensorValue; 
   jsonObj.printTo(Serial);
   Serial.println();
}

void setup() {
   Serial.begin(9600);
   sensorTest1.ConfigurePin();
   sensorTest2.ConfigurePin();
   sensorTest3.ConfigurePin();
   sensorTest4.ConfigurePin();
   sensorTest5.ConfigurePin();
   sensorTest6.ConfigurePin();
}

void loop() {
   //read potentionmeter value
   sensorTest1.Read();
   sensorTest2.Read();
   sensorTest3.Read();
   sensorTest4.Read();
   sensorTest5.Read();
   sensorTest6.Read();

  OnRequestData();
   
   delay(1000);
}

El código que acabamos de agregar básicamente lo que hace es obtener las lecturas del sensor analógico YL-69 cada segundo en un loop infinito. Una vez que obtiene la lectura, se agrega a una estructura JSON y luego agregamos información útil al objeto, como el PIN de donde se obtuvo la lectura y el tipo de sensor que se tiene en ese PIN.

Probar correcto funcionamiento de sensores y Arduino
Para probar la comunicación de los sensores, vamos a dar clic en “Tools –> Serial Monitor” para ver la información que se esta enviando a nuestros puertos analógicos:
image
Si la comunicación es correcta, vamos a ver la siguiente información en el monitor:
10

Cargar programa a dispositivos Arduino
El siguiente paso es agregar el programa a los Arduinos. Para esto vamos a elegir la opción de “Sketch –> Upload” en el IDE de Arduino:
11
Al finalizar, veremos el siguiente mensaje:
12
Este es el ultimo paso para configurar nuestros Arduinos con el programa de obtención de telemetría de los sensores de humedad.

Crear modulo de envío de mensajes a Azure Event Hubs en Node.js
Uno de los componentes claves en esta arquitectura, es el envío de mensajes a Azure Event Hubs. Para esto vamos a crear un modulo en Node.js para luego incluirlo en el programa principal de la aplicación, el cual se encargará de leer la telemetría de los Arduinos y generar los mensajes que se enviarán a Azure.

Los requisitos para el desarrollo de este modulo son los siguientes:
  1. Raspbian
  2. Node.js (6.x) o superior
  3. azure-event-hubs (npm)
  4. eventhubs-js (npm)
Agregamos el siguiente código a nuestra aplicación:

azureEventHub.js

"use strict"

var eventHub = require("eventhubs-js")
var EventHubClient = require('azure-event-hubs').Client;
var client;
var azureEventHub = {
     _eventHub_connectionString: "your-azure-event-hub-primary-connectionString",
     _notification_hub_name: "huerto-eventhub",  
     init: function () {

        // Create connection with Azure Event Hub
         client = EventHubClient.fromConnectionString(this._eventHub_connectionString, this._notification_hub_name)
         client.open()           
             .then(function () {     
                 // Init sender with receiver group Default
                 return client.createReceiver('$Default', '0', { startAfterTime: Date.now() })
             })
             .then(function (rx) {

                // Listening for errors
                 rx.on('errorReceived', function (err) { console.log(err); });

                // Listening for new messages in consumer group
                 rx.on('message', function (message) {
                     var body = message.body;
                     console.log(body);
                 })
             });
     },
     sendMessage: function (msg) {

        client.open()
             .then(function () {
                 // Init sender with consumer group Default
                 return client.createSender('0');
             })
             .then(function (tx) {

                // Listening for errors
                 tx.on('errorReceived', function (err) {
                     console.log(err);
                 });

                // Send menssage to Azure Event Hub
                 tx.send({ message: msg });
             });
     }
};

module.exports = azureEventHub;


Crear programa de Node.js en Raspberry para obtener lectura y envió de telemetría de Arduinos a Azure
En este paso comunicaremos nuestros Arduinos con una Raspberry y enviaremos la data de telemetría a Azure.
La manera en que vamos a realizar esto, es leyendo los puertos USB de la Raspberry con un modulo llamado “serialport” el cual podemos obtener de NPM, y después generaremos el mensaje que enviaremos a Azure.
 
Los requisitos para el desarrollo de esta aplicación son los siguientes:
  1. Raspbian
  2. Node.js (6.x) o superior
  3. serialport (npm)
  4. node-uuid (npm)

Para obtener la información de los Arduinos, vamos a crear un modulo, el cual tendrá la función de exponer los eventos de lectura e inicialización de cada uno de los puertos USB disponibles en la Raspberry. A su vez nos permitirá comunicarnos con Event Hub de Azure y enviar los mensajes de telemetría que nos esta llegando de los dispositivos de IoT.

Agregamos el siguiente código a nuestro programa:

serialPortReader.js

"use strict"

// Dependencies
var events = require("events"),
     uuid = require("node-uuid"),
     serialport = require("serialport"),
     azureEventHub = require("../../AzureEventHub/azureEventHub.js"),
     SerialPort = serialport.SerialPort;

// Default values
var defaultOptions = {
     // Default Raspberry ports info
     port: "/dev/ttyACM0",   
     // Default optimal value for baudrate
     baudrate: 9600,
     // Iteration time
     milliseconds: 1000
};

var SerialPortReader = function (options) {

    //Set default values
     options = (options == undefined) ? {} : options;
     this.baudrate = options.baudrate ? options.baudrate : defaultOptions.baudrate;
     this.milliseconds = options.milliseconds ? options.milliseconds : defaultOptions.milliseconds;
     this.serialPortList = serialport;
     this.switchRead = false;

    //Init Connection with Event Hub
     azureEventHub.init();
};

SerialPortReader.prototype = new events.EventEmitter;

SerialPortReader.prototype.init = function (port) {

    // Init arduino usb ports for lecture
     var self = this;
     self.switchRead = true;

    // Set arduino lecture events
     self.sp = new SerialPort(port, {
         baudrate: this.baudrate,
         parser: serialport.parsers.readline("\r\n")
     });

    // Read arduino data
     self.read();
};

SerialPortReader.prototype.read = function () {

    // Subscribe to data event listener
     var self = this;
     self.sp.on("data", arduinoReader.onData);

    // Subscribe to error event listener
     self.sp.on("error", function (error) {
         self.emit("serialPortError", error);
     });
};

// Close port event on demand
SerialPortReader.prototype.close = function () {
     var self = this
     self.sp.close();
     self.emit("close", "serial port closed")
};

var arduinoReader = {

    //Get data from arduino
     onData: function (data) {

        // Try to parse arduino data
         var self = this;
         var dataObj = arduinoReader.tryToParseJson(data);           
        
         if (dataObj) {

            // Parse arduino data array and send messages to Azure Event Hub
             dataObj.sd.map(function (measure) {
                 // Message JSON
                 var msg = {
                     // Message ID
                     uuid: uuid.v4(),
                     // Device ID from Arduino
                     did: dataObj.did,       
                     // Time
                     tt: new Date().toISOString(),
                     // Sensor Pin ID
                     pid: measure.pid,
                     // Humedad
                     h: measure.h,
                     // Device name
                     sm : measure.sm
                 };

                //Send menssage to Event Hub
                 azureEventHub.sendMessage(msg);
                
             });
         };
     },
     tryToParseJson : function (jsonString) {
         var j;
         try {
             j = JSON.parse(jsonString);
         } catch (e) {
             return undefined;
         }
         return j;
     }
};

module.exports = SerialPortReader;

Una vez que creemos el modulo de lectura, vamos a comunicarnos desde la aplicación principal al modulo para iniciar con la lectura y envío de mensajes:

app.js

"use strict"

// Dependencies
var SerialPortReader = require("./modules/serialPortReader.js");

// Init with default configuration values
var arduino = new SerialPortReader();

// Serial port init function
function startSerialRead(port) {
     arduino.init(port.comName);
}

// Init all usb ports available
arduino.serialPortList.list(function (error, ports) {
     ports.forEach(startSerialRead);
});

arduino.on("serialPortError", function (error) {
     console.log(error);
});

arduino.on("close", function (data) {
     console.log(data);
});

arduino.on("error", function (error) {
     console.log(error);
});


Felicidades!!!

Hasta este punto hemos logrado comunicar nuestros dispositivos de IoT con Azure Event Hubs!

En resumen, conectamos sensores de humedad de tierra analógicos a cada uno de los PIN de los Arduinos, los cuales contienen un programa de C que se encarga de obtener las lecturas, para luego enviarlas por USB a una Raspberry. Gracias a la magia de Node.js, podemos obtener los datos que se envían por USB desde una Raspberry y luego enviarlas a Azure para su futuro procesamiento.


Siguientes pasos:

  • Crear y configurar servicio de Stream Analytics para procesamiento de datos masivos en tiempo real

Comments

Popular posts from this blog

Configurar y desplegar una Web API en Azure App Service Environment

Despliegue de contenedores Docker a Azure Web Apps

Patrones de diseño para aplicaciones de alta disponibilidad en Azure - Resilient Applications (Parte I: Retry Policy)

Conectar .NET Web API con Azure API Management

Configurar trigger de Integración Continua en VSTS para ejecutar una tarea de compilación al subir cambios en Source Control