¿Qué aprenderá?

¿Qué hará?

¿Cuáles son los prerrequisitos?

En este tutorial verá cómo se implementa la capa de aplicación del sistema IoT REMA a través de microservicios y cómo se implementa el procesamiento de eventos. Las palabras "servicios" y "microservicios" se usarán de forma intercambiable a lo largo del documento. La tabla que sigue muestra los servicios, clasificados por tipo (ya sea de lógica o de interfaz), y los requisitos que implementa cada servicio. Este tutorial profundiza los servicios de lógica y habla muy brevemente de la interfaz ya que esto se abordará en otro recurso.

Tipo de servicio

Nombre del servicio

Requisitos que implementa

Lógica

Registro de dispositivos

Dar de alta el dispositivo IoT de un usuario que se conecta al sistema por primera vez

Recepción de datos

Recibir y almacenar los datos recolectados por dispositivos registrados

Control de actuadores

Enviar órdenes a los dispositivos IoT a partir del procesamiento de eventos, el envío se hace a través del Bróker MQTT

Interfaz gráfica y seguridad

Visualización de datos

  • Mostrar los datos recolectados por los dispositivos IoT de cada usuario, en tiempo cercano al real.
  • Mostrar los datos agregados por localización geográfica en un mapa.

Autenticación

Validar las credenciales de los usuarios para permitir el acceso a la interfaz gráfica

Tabla 1. Servicios y requisitos que implementan

La figura que sigue muestra el despliegue de los servicios anteriormente mencionados y del resto de la arquitectura en AWS. Note que cada servicio tendrá acceso a una única base de datos (patrón Single Database). La comunicación entre los dispositivos y la capa de aplicación es posible a través de un Bróker MQTT. Esta comunicación se da en dos sentidos (llamada "lazo cerrado"): 1) de los dispositivos al servicio de recepción de datos para enviarle muestras de datos; y 2) del servicio de control a los dispositivos para enviarle órdenes que los actuadores deben ejecutar. Adicionalmente, están los servicios de visualización y autenticación que reciben peticiones desde el navegador del usuario.

Diagrama de despliegue de la arquitectura de microservicios

Figura 1. Diagrama de despliegue de la arquitectura de microservicios

Django

Los servicios están implementados en Django, un framework web basado en Python. Tanto el framework como el lenguaje de programación poseen varias herramientas útiles que se usan en el desarrollo de los servicios. Django permite crear un proyecto que contiene las configuraciones clave del sistema y dentro del proyecto permite crear aplicaciones que son los submódulos del proyecto. En este tutorial cada aplicación en Django va a implementar uno o más servicios y gracias a las configuraciones globales del proyecto estas aplicaciones acceden a la misma base de datos. Le recomendamos ver este video que explica la estructura interna de una aplicación Django.

Para más información sobre el framework Django puede visitar el sitio web de Django.

A lo largo de este tutorial, a partir del código explorado del proyecto de Django, se despliegan los diferentes servicios en servidores de AWS y se integran con el resto de capas del sistema IoT. Para esto se necesita desplegar la base de datos primero y luego el servidor del bróker MQTT, con sus respectivas configuraciones. Después, se configuran y se despliegan los servicios, uno en cada servidor de AWS, esto incluye la creación de usuarios al sistema. Por último, con la estación de medición IoT se realizan pruebas de funcionamiento de envío de información, visualización de los datos y recepción de alertas. Se cierra el tutorial motivando algunas reflexiones.

El código del proyecto de Django está en este este repositorio de Github. Puede clonar o descargar el proyecto para que examine el programa a fondo.

Al descargarlo y abrirlo en el ambiente de desarrollo puede observar la estructura del proyecto (figura XX), en el que se encuentran las siguientes carpetas: 1) IOTMonitoringServer (configuración global del proyecto); 2) control (que encapsula el microservicio de control de actuadores), 3) receiver (que encapsula los servicios de registro de dispositivos y recepción de datos); y 4) viewer (que encapsula los servicios de visualización y autenticación).

IOTMonitoringServer/

IOTMonitoringServer

control

receiver

viewer

manage.py

A continuación se explica el código de la configuración global del proyecto así como de las funcionalidades más importantes implementadas en el resto de carpetas (estas son Aplicaciones Django).

IOTMonitoringServer

Dentro de la carpeta IOTMonitoringServer se encuentra el archivo settings.py como se muestra a continuación. En este archivo están las credenciales y datos de acceso a la base de datos. Adicionalmente, existen otras constantes donde se pueden definir las aplicaciones Django que usará el proyecto, los hosts permitidos, el modo debug, el idioma del proyecto, entre otros. En la Tabla 2 puede leer una breve descripción de las constantes del archivo.

IOTMonitoringServer/

IOTMonitoringServer

┃ ▶ static

┃ ▶ asgi.py

┃ ▶ settings.py

┃ ▶ urls.py

┃ ▶ wsgi.py

┃ ┗ __init__.py

settings.py

"""

Django settings for IOTMonitoringServer project.

Generated by 'django-admin startproject' using Django 4.0.3.

For more information on this file, see

https://docs.djangoproject.com/en/4.0/topics/settings/

For the full list of settings and their values, see

https://docs.djangoproject.com/en/4.0/ref/settings/

"""

import os

from pathlib import Path

from django.contrib import messages

# Build paths inside the project like this: BASE_DIR / 'subdir'.

BASE_DIR = Path(__file__).resolve().parent.parent

# Quick-start development settings - unsuitable for production

# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!

SECRET_KEY = 'django-insecure-bu+)8ft@9+qd*#e#f_s@wkyv2tmq+#!a^3j15h3kjk^jzksu0j'

# SECURITY WARNING: don't run with debug turned on in production!

DEBUG = True

ALLOWED_HOSTS = ["localhost", "ip.maquina.visualizador"]

...

Constante

Descripción

BASE_DIR

Ruta de la carpeta base del proyecto.

DEBUG

Booleano que indica si el proyecto está corriendo en desarrollo o producción.

ALLOWED_HOSTS

Lista de hosts habilitados para el consumo de los endpoints del proyecto.

INSTALLED_APPS

Aplicaciones internas y externas que se usan dentro del proyecto.

TEMPLATES

Configuraciones de las plantillas, entre ellas, rutas donde se ubican y los procesadores de contexto de las plantillas.

DATABASES

Configuración de los datos de conexión a la base de datos. EJ:

​​{

"default": {

"ENGINE": "django.db.backends.postgresql_psycopg2",

"NAME": "iot_data",

"USER": "dbadmin",

"PASSWORD": "uniandesIOT1234*",

"HOST": "localhost",

"PORT": "",

}

}

LANGUAGE_CODE

Especifica el idioma y la región del proyecto. Para español en Colombia el valor sería: "es-co".

PROJECT_ROOT

Ruta de la carpeta base de las configuraciones globales del proyecto.

STATIC_URL

URL donde se van a servir los archivos estáticos del proyecto como css, js e imágenes.

LOGIN_URL

URL donde se encuentra el login del proyecto.

LOGIN_REDIRECT_URL

Especifica la URL a donde se enviará al usuario cuando se autentique exitosamente.

LOGOUT_REDIRECT_URL

Especifica la URL a donde se enviará al usuario cuando salga de la sesión.

MQTT_HOST

Dirección del bróker MQTT.

MQTT_PORT

Puerto del bróker MQTT.

MQTT_USER

Nombre del usuario administrador suscriptor del bróker MQTT.

MQTT_PASSWORD

Contraseña del usuario administrador suscriptor del bróker.

MQTT_USER_PUB

Nombre del usuario administrador publicador del bróker MQTT.

MQTT_PASSWORD_PUB

Contraseña del usuario administrador publicador del bróker.

TOPIC

Tópico a suscribir que estará atento a todos los mensajes que se manden al bróker.

CA_CRT_FILE

Ruta del archivo ca.crt para la comunicación por TLS con el bróker MQTT.

Tabla 2. Listado de las constantes en settings.py, junto con su descripción

Aplicación Django receiver

En esta aplicación se implementan los servicios de registro de dispositivos y recepción de datos. Para esto en su archivo models.py se declaran los modelos que se usarán en dichos servicios. Dentro de este archivo están las entidades que se definieron en el modelo de datos del tutorial "Capa de Datos". Las entidades implementadas son City, State, Country, Location, Measurement, Station y Data. En particular, User está definida en el módulo nativo de autenticación (Auth) de Django. Las otras aplicaciones, Viewer y Control, usan los modelos que se definen en esta aplicación Django.

IOTMonitoringServer/

control

IOTMonitoringServer

receiver

┃ ▶ management

┃ ▶ migrations

┃ ▶ apps.py

┃ ▶ models.py

┃ ▶ mqtt.py

┃ ▶ utils.py

┃ ┗ __init__.py

viewer

manage.py

models.py

from django.db import models, IntegrityError

from django.db.models.fields import DateTimeField

from datetime import datetime, timedelta

from django.utils import timezone

from django.contrib.auth.models import User

from django.contrib.postgres.fields import ArrayField

from typing import Any, MutableMapping, Optional

import psycopg2

class City(models.Model):

name = models.CharField(max_length=50, unique=True, blank=False)

code = models.CharField(max_length=50, null=True)

def str(self):

return "{}".format(self.name)

class State(models.Model):

name = models.CharField(max_length=50, unique=False, blank=False)

code = models.CharField(max_length=50, null=True)

def str(self):

return "{}".format(self.name)

class Country(models.Model):

name = models.CharField(max_length=50, unique=False, blank=False)

code = models.CharField(max_length=50, null=True)

def str(self):

return "{}".format(self.name)

...

Por otro lado, el servicio que se muestra a continuación es el que está atento de la llegada de cualquier medición recolectada por los dispositivos IoT. Para esto necesita conectarse al bróker MQTT y suscribirse a todos los tópicos de datos medidos. Dentro de la carpeta receiver se ubica el archivo mqtt.py. En este archivo se realizan las operaciones necesarias para la conexión al bróker. El programa intenta primero conectarse al bróker con las constantes establecidas en la configuración global.

IOTMonitoringServer/

control

IOTMonitoringServer

receiver

┃ ▶ management

┃ ▶ migrations

┃ ▶ apps.py

┃ ▶ models.py

┃ ▶ mqtt.py

┃ ▶ utils.py

┃ ┗ __init__.py

viewer

manage.py

mqtt.py

from datetime import datetime

from . import utils

import json

import os

import ssl

import paho.mqtt.client as mqtt

from django.conf import settings

def on_message(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):

'''

Función que se ejecuta cada que llega un mensaje al tópico.

Recibe el mensaje con formato:

{

"variable1": mediciónVariable1,

"variable2": mediciónVariable2

}

en un tópico con formato:

pais/estado/ciudad/usuario

ej: colombia/cundinamarca/cajica/ja.avelino

Si el tópico tiene la forma de:

pais/estado/ciudad/usuario/mensaje

se salta el procesamiento pues el mensaje es para el dispositivo de medición.

A partir de esos datos almacena la medición en el sistema.

'''

try:

time = datetime.now()

payload = message.payload.decode("utf-8")

print("payload: " + payload)

payloadJson = json.loads(payload)

country, state, city, user = utils.get_topic_data(

message.topic)

user_obj = utils.get_user(user)

location_obj = utils.get_or_create_location(city, state, country)

for measure in payloadJson:

variable = measure

unit = utils.get_units(str(variable).lower())

variable_obj = utils.get_or_create_measurement(variable, unit)

sensor_obj = utils.get_or_create_station(user_obj, location_obj)

utils.create_data(

float(payloadJson[measure]), sensor_obj, variable_obj, time)

except Exception as e:

print('Ocurrió un error procesando el paquete MQTT', e)

...

Luego, si la conexión fue exitosa, se suscribe a todos los tópicos que contienen muestras de datos

. Para esto usa el patrón +/+/+/+/out que corresponde a ////out donde:

El patrón es especificado en la constante TOPIC se declara en la función on_connect() del archivo settings.py como se muestra a continuación:

mqtt.py

...

def on_connect(client, userdata, flags, rc):

print("Suscribiendo al tópico: " + settings.TOPIC)

client.subscribe(settings.TOPIC)

print("Servicio de recepcion de datos iniciado")

...

A continuación se observa el código de la función on_message() del archivo mqtt.py, cuya responsabilidad es procesar los mensajes recibidos. Esta función primero descodifica el mensaje MQTT que contiene tanto el payload (datos de temperatura y humedad) como el URI del tópico, ////out. A partir del URI se extrae el usuario y la localización del dispositivo. Se valida que exista el usuario y el dispositivo, si este último no existe se registra. Luego, el programa crea la medición según el tipo de variable que esté en el payload. Esta creación es la misma que está en el tutorial "Capa de Datos"; puede dirigirse a ese tutorial para una explicación más detallada.

mqtt.py

...

def on_message(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):

try:

time = datetime.now()

payload = message.payload.decode("utf-8")

print("payload: " + payload)

payloadJson = json.loads(payload)

country, state, city, user = utils.get_topic_data(

message.topic)

user_obj = utils.get_user(user)

location_obj = utils.get_or_create_location(city, state, country)

for measure in payloadJson:

variable = measure

unit = utils.get_units(str(variable).lower())

variable_obj = utils.get_or_create_measurement(variable, unit)

sensor_obj = utils.get_or_create_station(user_obj, location_obj)

utils.create_data(

float(payloadJson[measure]), sensor_obj, variable_obj, time)

...

Por su parte, la función on_disconnect busca reconectar la aplicación al bróker MQTT si se perdió la conexión, como se muestra a continuación.

mqtt.py

...

def on_disconnect(client: mqtt.Client, userdata, rc):

'''

Función que se ejecuta cuando se desconecta del broker.

Intenta reconectar al bróker.

'''

print("Desconectado con mensaje:" + str(mqtt.connack_string(rc)))

print("Reconectando...")

client.reconnect()

...

Por último, en el archivo utils.py (dentro de la carpeta receiver) están las funciones de las que hace uso la función on_message del archivo mqtt.py. Una breve descripción de las funciones que están en utils.py puede ser encontrada en la Tabla 3.

Nombre de la función

Descripción

get_coordinates(city, state, country)

Utiliza un API público (https://geocode.xyz/) para obtener la latitud y longitud de una ciudad ubicada en un estado y país.

get_topic_data(topic)

Extrae el país, estado, ciudad y usuario del tópico que le llega por parámetro. Retorna esos valores por separado.

get_user(username)

Trae el usuario indicado de la base de datos. Retorna el objeto del usuario.

get_or_create_location(city, state, country)

Intenta traer la locación geográfica a partir de los nombres de una ciudad, estado y país.

Si no existe, calcula las coordenadas de esa ubicación, la crea y la retorna.

get_or_create_station(user, location)

Intenta traer la estación con el usuario y la locación especificados. Si no existe la crea y la retorna.

get_or_create_measurement(name, unit)

Intenta traer la variable con el nombre y la unidad especificados. Si no existe la crea y la retorna.

create_data(value, station, measure, time)

Crea un nuevo dato con el valor, la estación y la variable especificados.

Hace las operaciones necesarias para insertarlo en la base de datos con el patrón Blob. Calcula el promedio, el mínimo y el máximo de los datos anteriores.

Tabla 3. Listado de las funciones en utils.py, junto con su descripción

Aplicación Django control

Este servicio es el encargado de procesar eventos y de acuerdo con unas reglas definidas comandar acciones en los actuadores de los dispositivos IoT. En el caso de este tutorial, la regla se especifica como sigue:

Condición

Acción

Si el promedio de las mediciones de una variable (por ej., temperatura, humedad), colectadas en la última hora, está por fuera de los límites definidos para esa variable

Mostrar un mensaje en la pantalla del dispositivo informando que los límites se han excedido

Tabla 4. Reglas para las acciones comandadas sobre los actuadores

Para enviar mensajes a los dispositivos, este servicio publica mensajes en un tópico al que un dispositivo está suscrito. El URI que escogimos para el tópico es muy parecido a la del tópico que contiene las mediciones, ////in, la única diferencia es que tiene el sufijo /in para indicar que a ese tópico llegan las órdenes para comandar los actuadores de los dispositivos.

El comportamiento específico de este servicio es el siguiente: un "cron" (servicio que se ejecuta de forma autónoma cada cierto tiempo, por ej., cada 10 minutos) calcula el promedio de las variables medidas en la última hora. Si el promedio se sale de los límites establecidos para esa variable específica, el servicio envía un mensaje al dispositivo para alertar a los usuarios sobre la situación. El mensaje enviado contiene la variable y los límites de esta.

En la implementación del servicio, bajo la carpeta control del proyecto de Django, se puede encontrar el archivo monitor.py cuyas funciones más importantes son:

IOTMonitoringServer/

control

┃ ▶ management

┃ ▶ migrations

┃ ▶ apps.py

┃ ▶ monitor.py

┃ ┗ __init__.py

IOTMonitoringServer

receiver

viewer

manage.py

monitor.py

...

def on_connect(client, userdata, flags, rc):

'''

Función que se ejecuta cuando se conecta al bróker.

'''

print("Conectando al broker MQTT...", mqtt.connack_string(rc))

def on_disconnect(client: mqtt.Client, userdata, rc):

'''

Función que se ejecuta cuando se desconecta del broker.

Intenta reconectar al bróker.

'''

print("Desconectado con mensaje:" + str(mqtt.connack_string(rc)))

print("Reconectando...")

client.reconnect()

def setup_mqtt():

'''

Configura el cliente MQTT para conectarse al broker.

'''

print("Iniciando cliente MQTT...", settings.MQTT_HOST, settings.MQTT_PORT)

global client

try:

client = mqtt.Client(settings.MQTT_USER_PUB)

client.on_connect = on_connect

client.on_disconnect = on_disconnect

if settings.MQTT_USE_TLS:

client.tls_set(ca_certs=settings.CA_CRT_PATH,

tls_version=ssl.PROTOCOL_TLSv1_2, cert_reqs=ssl.CERT_NONE)

client.username_pw_set(settings.MQTT_USER_PUB,

settings.MQTT_PASSWORD_PUB)

client.connect(settings.MQTT_HOST, settings.MQTT_PORT)

except Exception as e:

print('Ocurrió un error al conectar con el bróker MQTT:', e)

...

monitor.py

...

def start_cron():

'''

Inicia el cron que se encarga de ejecutar la función analyze_data cada minuto.

'''

print("Iniciando cron...")

schedule.every(5).minutes.do(analyze_data)

print("Servicio de control iniciado")

while 1:

schedule.run_pending()

time.sleep(1)

...

monitor.py

...

def analyze_data():

# Consulta todos los datos de la última hora, los agrupa por estación y variable

# Compara el promedio con los valores límite que están en la base de datos para esa variable.

# Si el promedio se excede de los límites, se envía un mensaje de alerta.

print("Calculando alertas...")

data = Data.objects.filter(

base_time__gte=datetime.now() - timedelta(hours=1))

aggregation = data.annotate(check_value=Avg('avg_value')) \

.select_related('station', 'measurement') \

.select_related('station__user', 'station__location') \

.select_related('station__location__city', 'station__location__state',

'station__location__country') \

.values('check_value', 'station__user__username',

'measurement__name',

'measurement__max_value',

'measurement__min_value',

'station__location__city__name',

'station__location__state__name',

'station__location__country__name')

alerts = 0

for item in aggregation:

alert = False

variable = item["measurement__name"]

max_value = item["measurement__max_value"] or 0

min_value = item["measurement__min_value"] or 0

country = item['station__location__country__name']

state = item['station__location__state__name']

city = item['station__location__city__name']

user = item['station__user__username']

if item["check_value"] > max_value or item["check_value"] < min_value:

alert = True

if alert:

message = "ALERT {} {} {}".format(variable, min_value, max_value)

topic = '{}/{}/{}/{}/in'.format(country, state, city, user)

print(datetime.now(), "Sending alert to {} {}".format(topic, variable))

client.publish(topic, message)

alerts += 1

print(len(aggregation), "dispositivos revisados")

print(alerts, "alertas enviadas")

...

Aplicación Django viewer

La aplicación de visualización sigue la arquitectura de un proyecto web común en Django. Es decir, en el archivo urls.py se encuentran las direcciones (paths) que aceptará el servidor web. En el proyecto están las siguientes:

IOTMonitoringServer/

control

IOTMonitoringServer

receiver

viewer

┃ ▶ migrations

┃ ▶ templates

┃ ▶ admin.py

┃ ▶ apps.py

┃ ▶ filters.py

┃ ▶ forms.py

┃ ▶ models.py

┃ ▶ tests.py

┃ ▶ urls.py

┃ ▶ utils.py

┃ ▶ views.py

┃ ┗ __init__.py

manage.py

urls.py

from django.urls import include, path

from . import views

urlpatterns = [

path('', views.index, name='index'),

path("", include("django.contrib.auth.urls")),

path('realtime-data/', views.realtime_data, name='realtime_data'),

path('map/', views.map_data, name='map'),

path('historic/', views.download_data, name='historical'),

path('users/', views.users, name='users'),

path('users/delete/', views.delete_user, name='delete_users'),

path("users/register/", views.register_request, name="register"),

path('variables/', views.variables, name='variables'),

path("variables/register/", views.register_variable_request,

name="register_variable"),

path('variables//', views.edit_variable, name='edit_variable'),

]

Cada una de estas URL ejecuta una vista del archivo views.py. En este archivo cada función retorna una plantilla de la carpeta viewer/templates/ con su respectiva información cargada (por ej., la plantilla home.html, que se muestra a continuación). La información para cada plantilla se carga con funciones del archivo utils.py, por ejemplo, la función get_last_week_data trae los últimos datos de temperatura y humedad y los preprocesa para que alimenten fácilmente la plantilla de HTML. Las vistas están anotadas con decoradores de Django que aseguran la autenticación del usuario y si es administrador o no (@login_required y @user_passes_test(lambda u: u.is_superuser)).

IOTMonitoringServer/

control

IOTMonitoringServer

receiver

viewer

┃ ▶ migrations

┃ ▶ templates

┃ ┃ ▶ registration

┃ ┃ ▶ users

┃ ┃ ▶ variables

┃ ┃ ▶ 400.html

┃ ┃ ▶ 403.html

┃ ┃ ▶ 404.html

┃ ┃ ▶ 500.html

┃ ┃ ▶ base.html

┃ ┃ ▶ base_nonavbar.html

┃ ┃ ▶ historical.html

┃ ┃ ▶ home.html

┃ ┃ ▶ login.html

...

home.html

{% extends 'base.html' %} {% load static %} {% block content %}

="m-5 p-5">

="text-center">

Bienvenid@ {{user.username}} al sistema de monitoreo IOT

="mx-5 px-5 mt-5 pt-5">

En esta plataforma web podrá visualizar los datos que se obtienen de los sensores de lsa estaciones IOT. En específico podrá:

    ="mx-5 px-5">

  • Ver en tiempo real las mediciones de su estación.
  • Observar en el mapa global el promedio de las mediciones de otras estaciones.
  • Descargar los datos medidos en un archivo CSV.

{% endblock %}

En esta sección se despliegan los servidores necesarios en AWS por medio del script de CloudFormation. En el script se especifica la creación de la VPC (Red Privada Virtual), los permisos de seguridad y la creación de las máquinas EC2. Adicionalmente, para las máquinas se especifican los comandos que deben ejecutarse para equiparlas con los ambientes de ejecución que necesitan. Por ejemplo, para la creación de la base de datos se ejecutan comandos que descargan Postgres y la extensión de Timescale y los configuran con valores predeterminados.

En el script de este tutorial se crearán máquinas para la base de datos, el bróker MQTT y los servicios (receptor, visualizador, control de actuadores). Para desplegar la infraestructura siga estos pasos:

  1. Ingrese a AWS Academy y entre al módulo donde encuentra la terminal. Inicie el laboratorio dando clic en el botón "Start lab".

Imagen ilustrativa

Figura 2. Interfaz de inicio del laboratorio

  1. Descargue el script en la terminal con el siguiente comando:
~$ wget -O template https://raw.githubusercontent.com/SELF-Software-Evolution-Lab/Realtime-Monitoring-webApp/main/tutoriales/Capa%20de%20Aplicaci%C3%B3n/IOT-System.template.json
  1. Ejecute el siguiente comando para desplegar la infraestructura:
aws cloudformation create-stack --stack-name iot-system --template-body file://template --capabilities "CAPABILITY_IAM"

La terminal debe mostrar el ID del stack creado.

  1. Ahora espere unos momentos mientras se despliega la infraestructura. Para conocer el estado del despliegue, diríjase a la consola de AWS, pulsando el botón "AWS" en la interfaz de inicio del laboratorio, y acceda a la sección "CloudFormation" (https://us-east-1.console.aws.amazon.com/cloudformation/home) :

Imagen ilustrativa

Figura 3. Inicio de la consola de AWS

En "CloudFormation" podrá ver el estado del stack, a saber: creación en proceso, fallo o creación completada.

Figura 4. Stacks creados en CloudFormation

Espere a que el estado del stack iot-system sea "CREATE_COMPLETE".

  1. Ahora puede revisar las máquinas creadas por el script. Diríjase a "EC2->Instances" (puede usar este enlace: https://us-east-1.console.aws.amazon.com/ec2/v2/home). Allí puede encontrar el listado de las máquinas que se necesitan para el tutorial (cinco máquinas EC2 en total).

Imagen ilustrativa

Figura 4. Listado de máquinas aprovisionadas para el tutorial

Luego de haber desplegado la infraestructura se necesita configurarla. La base de datos no necesita configuraciones adicionales, pero el bróker MQTT sí. Como se trabajó en el tutorial de capa de comunicación ("Instalación y configuración de Mosquitto") la configuración que se realizará será agregar los usuarios que usted permitirá en su sistema. Las demás configuraciones como especificación del puerto, reglas de tópicos permitidos y modificación del archivo mosquitto.conf ya están hechas. Si quiere cambiar los valores configurados puede seguir las instrucciones dadas en el tutorial de capa de sesión. Para agregar los usuarios deseados siga estas instrucciones.

  1. Ingrese a la máquina EC2 que contiene el bróker MQTT siguiendo los pasos a continuación:
  1. Haga clic en el campo "Instance ID" de la máquina "MQTT Broker" listada en AWS.

Imagen ilustrativa

Figura 5. Listado de máquinas aprovisionadas para el tutorial

  1. Haga clic en la pestaña "Connect".

Imagen ilustrativa

Figura 6. Detalle de una máquina EC2

  1. Haga clic en el botón "Connect" al final de la ventana.

Imagen ilustrativa

Figura 9. Ventana de conexión a una máquina EC2

  1. Una vez se logre la conexión a la máquina, edite el archivo que contiene los usuarios de prueba. Este tiene la ruta /etc/mosquitto/users.txt. Puede usar el siguiente comando para abrirlo:
~$ sudo nano /etc/mosquitto/users.txt

En el editor agregue los usuarios con su contraseña correspondiente en líneas separadas. Ejemplo:

admin:admin

admin2:admin2

user1:123456

pe.perez:abc123

ironman:jarvis123

jfkennedy:apolo11

Es importante que tenga dos usuarios administradores, uno que sea capaz de leer y otro de escribir en cualquier tópico del bróker MQTT. Por defecto encuentra admin1 y admin2 en el archivo. Si cambia estos usuarios asegúrese de cambiar las reglas de acceso en el archivo /etc/mosquitto/acl.txt.

  1. Guarde el archivo presionando "Ctrl+X", luego "Y" y por último "Enter".
  2. Ahora debe encriptar las contraseñas usando la herramienta que Mosquitto incluye, utilizando el siguiente comando:
~$ sudo mosquitto_passwd -U /etc/mosquitto/users.txt
  1. Reinicie el servicio de Mosquitto para que carguen los cambios realizados en la configuración a través del siguiente comando:
~$ sudo systemctl restart mosquitto.service
  1. Por último, revise que el servicio esté ejecutando exitosamente con el comando:
~$ sudo systemctl status mosquitto.service

La terminal debe mostrar que el servicio está activo y corriendo (active). Para salir del resumen del servicio oprima "Ctrl+C".

Según la arquitectura establecida, hay tres servicios que se deben ejecutar, uno en cada máquina. Los servicios son el receptor de datos, el visualizador y el control de actuadores. Todos estos servicios se encuentran en el mismo proyecto de Django, pero se inician de maneras diferentes. Para configurar los servicios basta con editar las configuraciones del proyecto una vez y luego descargarlo en cada máquina. A continuación se explica el procedimiento.

  1. Efectúe un fork del repositorio del proyecto con su cuenta de git y clónelo en su computador local. La URL del repositorio es:

https://github.com/SELF-Software-Evolution-Lab/IOTMonitoringServer

  1. Edite el archivo en IOTMonitoringServer/settings.py, con el programa de su elección.
  1. En este archivo de configuración solo debe cambiar algunas variables de las explicadas al inicio de este tutorial. Debe cambiar ALLOWED_HOSTS, DATABASES y MQTT_HOST.
  2. En la variable ALLOWED_HOSTS debe cambiar "ip.maquina.visualizador" por la IP pública de la EC2 del servicio de visualización. Ej.: "ip.maquina.visualizador" pasa a ser "192.168.0.5".
  3. En la variable DATABASES debe cambiar el valor del atributo HOST que es "ip.maquina.db" por la IP pública de la EC2 que contiene la base de datos. Ej.: "ip.maquina.db" pasa a ser "157.253.0.40".
  4. En la variable MQTT_HOST debe cambiar "ip.maquina.mqtt" por la IP pública de la EC2 que ejecuta el bróker MQTT. Ej.: "ip.maquina.mqtt" pasa a ser "30.44.127.23".
  1. Con las configuraciones realizadas, suba los cambios al repositorio, ejecutando los estos comandos:
~$ git add –all
~$ git commit -m "mensaje commit"
~$ git push
  1. Conéctese a cada una de las máquinas EC2 referidas como "IoT Receiver app", "IoT Alert app", "IoT Viewer app" en el listado de instancias de AWS. Por cada máquina debe hacer lo siguiente:
  1. Clone el repositorio al que hizo fork, usando el siguiente comando:
~$ git clone <enlace_repositorio> 

Ingrese sus credenciales de github a continuación: usuario, contraseña (token de acceso).

  1. Acceda al directorio del proyecto:
~$ cd IOTMonitoringServer
  1. Ejecute las migraciones a la base de datos desde cualquiera de las máquinas EC2 ( "IoT Receiver app", "IoT Alert app" o "IoT Viewer app") donde haya previamente descargado el zip. Como todos los servicios le apuntan a una única base de datos, las migraciones deben ejecutarse desde una sola máquina. Utilice el comando:
~$ python3 manage.py migrate
  1. Inicie el servicio de recepción de datos de la siguiente manera:
  1. Entre a la consola de la máquina EC2 "IOT Receiver App".
  2. Ejecute el siguiente comando:
~$ nohup python3 IOTMonitoringServer/manage.py start_mqtt &

Deberá observar en la consola los mensajes de conexión con el servidor MQTT y suscripción. Con esto ya tiene el receptor iniciado.

  1. Inicie el servicio de control de actuadores de la siguiente manera:
  1. Entre a la consola de la máquina EC2 "IOT Alert App".
  2. Ejecute el siguiente comando:
~$ python3 IOTMonitoringServer/manage.py start_control &

Deberá observar en la consola los mensajes de inicio del servicio. Con esto ya tiene el servicio de control de actuadores iniciado.

  1. Inicie el servicio de visualización.
  1. Entre a la consola de la máquina EC2 "IOT Viewer App".
  2. Ejecute el siguiente comando:
~$ nohup sudo python3 IOTMonitoringServer/manage.py runserver 0.0.0.0:80 &

Deberá observar en la consola los mensajes de inicio del servidor Django. Con esto ya tiene el servicio de visualización iniciado.

Los servicios ya están arriba para empezar a tratar peticiones. Sin embargo, para usar el servicio "IoT Viewer app" se deben crear un administrador y usuarios. El registro lo puede hacer siguiendo los pasos a continuación:

  1. Registre el administrador.
  1. En la consola de la máquina IOT Viewer App, detenga el servicio con "Ctrl+C"
  2. Ejecute el comando:

~$ python3 IOTMonitoringServer/manage.py createsuperuser

El programa le pedirá una serie de datos como usuario, correo y contraseña. Ingrese los datos que desee.

  1. Registre el resto de usuarios, quienes únicamente podrán visualizar los datos. Esto lo debe hacer desde el rol administrador siguiendo estos pasos:
  1. Desde el navegador de su preferencia ingrese a http://<ip.máquina.IOT Viewer App>/ donde <ip.maquina.IOT Viewer App> debe ser la IP pública de la máquina EC2 que tiene corriendo el servicio IOT Viewer App. Ej: http://192.168.0.1/
  2. La plataforma le pedirá las credenciales de un usuario, ingrese las del administrador que creó en el punto anterior

Interfaz de inicio de sesión de la aplicación “Viewer”

Figura 10. Interfaz de inicio de sesión de la aplicación "Viewer"

  1. Al entrar puede visualizar varios botones: "Inicio", "Datos en tiempo real", "Mapa histórico", "Descarga de datos", "Usuarios" y "Variables". Haga clic en "Usuarios".
  1. En la página "Usuarios" hay una tabla con todos los usuarios del visualizador. Para crear uno, haga clic en "Crear usuario".
  1. Llene el formulario con la información del usuario que quiere crear. Recuerde que por las reglas de Mosquitto sólo los usuarios registrados en el bróker pueden enviar datos por lo que los usuarios que cree en este servicio deben ser los mismos que están configurados en el bróker MQTT.

Nota: la contraseña debe cumplir con los requisitos que se muestran en el formulario de creación.

 Interfaz de creación de usuario de la aplicación “Viewer”

Figura 11. Interfaz de creación de usuario de la aplicación "Viewer"

  1. Valide que la creación fue exitosa observando si el usuario aparece en la lista de usuarios.

Lista de usuarios creados

Figura 12. Vista de usuarios creados

  1. Cierre la sesión del usuario administrador.
  2. Desde la consola de la máquina "IoT Viewer App" reinicie el servicio, utilizando el comando dado al final de la sección "Instalar y configurar los servicios de aplicación".

Ahora, probaremos el funcionamiento de los servicios desplegados conectando un dispositivo a las capas superiores.

  1. Conecte el dispositivo siguiendo los pasos del tutorial de capa de dispositivos, ya sea a través del NodeMCU o del emulador Raspberry. Recuerde modificar el código en los enlaces para actualizar las variables de conexión al bróker; por ejemplo, IP del Bróker, credenciales de usuario, etc. Para facilitar esta tarea, hemos colocado comentarios que tienen "TODO" para indicar cuáles son las variables cuyos valores deben cambiar. El código para ejecutar en el NodeMCU está en este enlace y el del emulador en este enlace.
  2. Se utilizará una pantalla para mostrar las alertas generadas. La referencia recomendada es OLED 128x64 I2C (SSD1306). Para el emulador se hará uso de la consola para mostrar las alertas. Para mayor información sobre cómo conectar la pantalla al nodo MCU puede consultar el anexo que se encuentra al final de este tutorial.

Nota: el código para el NodeMCU está programado para utilizar una pantalla OLED 128×64. Si usted cuenta con otro tipo de pantalla, debe asegurarse de:

  1. Ingrese a la plataforma de visualización desde su navegador preferido. Recuerde que la dirección es http://<ip.maquina.IOT Viewer App>/. Use las credenciales de usuario que usó en el paso anterior.
  2. Observe el comportamiento de los servicios "IoT Receiver App" e "IoT Viewer App" accediendo a las diferentes funcionalidades de la interfaz: "Datos en tiempo real", "Mapa histórico", etc.
  3. Por otro lado, para probar el servicio "IoT Alert App" siga los pasos a continuación.
  1. Ingrese a la plataforma con el usuario administrador.
  2. Entre a la sección "Variables". En esta sección podrá observar las variables que se han creado en el sistema. Debe ver que están las variables de temperatura y humedad.
  3. Escoja una variable y haga clic en "Editar".
  4. En el formulario de edición de la variable temperatura cambie el "min_value" y "max_value" con el propósito de probar que la regla del procesamiento de eventos se está ejecutando correctamente. Para esto tenga en cuenta los datos que está colectando el dispositivo y coloque un rango muy cercano al valor actual. Por ejemplo, si el dispositivo está midiendo 21°C de temperatura, puede usar el rango [19-23].
  5. Una vez asignados los rangos para temperatura, guarde los datos oprimiendo "Actualizar".
  6. Realice los pasos c, d y e para el resto de variables.
  7. Ahora que los rangos están configurados, usted debe intentar que los datos comunicados por la capa física los sobrepasen.
  1. Si está usando el Node MCU puede variar las mediciones haciendo lo siguiente:
  1. Sople con su aliento (aire caliente) los sensores para subir la temperatura y humedad.
  2. O, ubique el sensor debajo de su axila, como si fuera un termómetro, para subir la temperatura y humedad.
  3. También puede usar su creatividad para cambiar los valores sin dañar los dispositivos.
  1. Si está usando el emulador edite el programa y envíe datos por fuera de los rangos establecidos
  1. Tenga en cuenta que el cambio en la medición debe durar alrededor de 10 minutos para que se envíe la alerta al NodeMCU o al emulador.
  2. En el caso de usar el Node MCU, aparecerá una alerta en la pantalla OLED. Por otro lado, en el emulador se imprimirá un mensaje con la misma alerta en la consola donde se esté ejecutando la emulación.

Para el tutorial de capa de aplicación Interfaz se requiere del despliegue que realizó durante esta práctica. No elimine la infraestructura que desplegó, de lo contrario deberá realizar todo el proceso de nuevo.

Las siguientes preguntas lo invitan a reflexionar sobre lo que observó en el desarrollo de los pasos inmediatamente anteriores:

  1. En la arquitectura del tutorial se propone una descomposición de las funcionalidades IoT por microservicios ¿Qué ventajas y desventajas tiene esto?
  2. ¿Qué patrones de comunicación de microservicios se usaron entre los elementos de la arquitectura? ¿Qué ventajas y desventajas ofrecen los patrones observados?
  3. ¿Qué patrones de manejo de datos de microservicios se usaron en la arquitectura? ¿Qué ventajas y desventajas ofrecen los patrones observados?
  4. Para el procesamiento de eventos con el Bróker MQTT se diseñaron los URIs para que cada usuario tuviera dos tópicos: uno para los mensajes que van desde el dispositivo hacia la nube y otro para los mensajes que van en el sentido opuesto (configurando así un lazo cerrado). Suponga ahora que cambiamos por un único URI como este "+/+/+/+/" (sin el in/out), ¿qué impacto tendría este cambio sobre la arquitectura?

Para responder las preguntas considere lo visto a lo largo del tutorial y los videos conceptuales de la semana. ¡Traiga sus reflexiones a las sesiones sincrónicas!

Este tutorial le permitió experimentar la comunicación en lazo cerrado entre dispositivos de la capa física y servicios genéricos de la capa de aplicación de un sistema IoT.

Versión 1.0

Juan Avelino, Kelly Garcés

Autores

Rocío Héndez, Andrés Bayona

Revisores

En este tutorial se le guiará en la configuración de la tarjeta NodeMCU con la pantalla OLED 128x64 I2C. Se muestran las conexiones necesarias entre los dos módulos y se aprovechan las librerías de ejemplo para probar el correcto funcionamiento del NodeMCU con la pantalla.

  1. Inicie conectando el nodo a la pantalla de acuerdo a la imagen 1 y a la tabla 1.

Se muestra las conexiones entre los módulos. Las conexiones corresponden a las mencionadas en la tabla1.

Imagen 1. Diagrama de conexión entre NodeMCU y pantalla OLED.

Pantalla OLED

NodeMCU

Color cable

GND

GND

Negro

VCC

3V3

Rojo

SCL

D1

Verde

SDA

D2

Azul

Tabla 1. Tabla de conexiones NodeMCU - OLED.

Nota: Algunos modelos de la pantalla OLED tienen los pines GND y VCC intercambiados en comparación al diagrama. Es importante que revise en el módulo físico el nombre de los pines que está conectando.

  1. En su computador abra Arduino para incluir la librería de la pantalla. En la barra superior bajo "Sketch" ("Programa" en español) y luego "Include Library" ("Incluir Librería" en español), haga clic en "Manage Libraries" ("Administrar Bibliotecas") como se indica en la imagen 2.

En la barra superior seleccionar Sketch, luego Include Libraries y en el último menú desplegable seleccionar Manage Libraries.

Imagen 2. Ruta de opciones para "Manage Libraries" en Arduino.

  1. En el gestor de librerías que se acaba de abrir, debe buscar "SSD1306" e instalar la última versión librería "Adafruit SSD1306". En la siguiente imagen puede observar cuál es la librería.

En la ventana de gestor de librerías, primero buscar el término "SSD1306" en la barra de búsqueda que está en la esquina superior derecha. Esa búsqueda arrojará varios resultados. En el resultado con título "Adafruit SSD1306" oprimir el botón "Install" y esperar a que se complete la instalación.

Imagen 3. Librería "Adafruit SSD1306" en el gestor de librerías Arduino.

  1. Cierre el gestor de librerías y cargue el ejemplo para la pantalla 128x64 I2C. Para esto diríjase a "File" en la barra superior, luego "Examples" y en la sección de ejemplos de librerías externas está la opción de "Adafruit SSD1306". Seleccione el archivo "ssd1306_128x64_i2c". La ruta se muestra en la imagen 4.

En la barra superior, abrir el menú "File" y seleccionar "Examples". En el submenú de ejemplos aparecerán algunas secciones y se debe ubicar en la sección de "Examples from Custom Libraries". Dentro de esta sección seleccione Adafruit SSD1306 y por último dé clic en la opción "ssd1306_128x64_i2c". Eso cargará el ejemplo.

Imagen 4. Ruta de opciones para ejemplo de programa de pantalla OLED.

  1. Con el programa de ejemplo abierto, cambie la dirección de la pantalla dentro del script. En la línea que define "SCREEN_ADDRESS" cambie el valor de "0x3D" a "0x3C".

Muestra una parte del código del ejemplo que se cargó con antelación. En especial se señala la línea que contiene "#define SCREEN_ADDRESS 0x3D". Es importante cambiar el "0x3D" por "0x3C".

Imagen 5. Script de ejemplo y ubicación de línea a modificar.

  1. Guarde los cambios. El programa le pedirá que tiene que crear un nuevo proyecto y lo dirigirá a asignarle un nombre para guardar.
  2. Con el NodeMCU conectado como el diagrama y al computador, compile y suba el código al nodo. Tenga en cuenta que ya debe tener listas las configuraciones para ejecutar programas en el NodeMCU (están en el tutorial de capa de dispositivo).

Para compilar y subir el código oprima el botón "Upload" como en la imagen 6.

Se indica dónde está ubicado el botón para cargar el código al nodo. Esto es en la esquina superior izquierda, el segundo ícono en forma de flecha.

Imagen 6. Botón "Upload".

Al darle clic, podrá observar el progreso de carga en la parte inferior de Arduino como se muestra en la imagen 7.

Se muestra la consola con algunos mensajes del progreso. Ej: Writing at 0x12345678 33%.

Imagen 7. Progreso de carga de script en la consola de Arduino.

  1. Con el código cargado podrá observar que la pantalla está mostrando una serie de animaciones. Y así se comprueba que se conectó y ejecutó correctamente el programa.

Se muestra el logo de Adafruit. Foto de la pantalla OLED mostrando líneas diagonales en toda la pantalla.

Foto de la pantalla OLED mostrando todos los caracteres del vocabulario inglés incluyendo números, signos de puntuación y letras mayúsculas y minúsculas. Foto de la pantalla OLED mostrando estrellas pequeñas aleatoriamente ubicadas en la pantalla.

Imagen 8,9,10,11. Ejemplos de animaciones que muestra la pantalla OLED.