en Desarrollo, Python

MQTT series II – Aplicación con Python, NodeJS, Arduino y Mosquitto

Introducción

Con los conceptos y características de MQTT claras, su relación con el IoT y la manera en la que funciona el protocolo, se va a desarrollar una aplicación donde se implementen estos conceptos utilizando como dispositivo, un Arduino Uno. Es recomendable leer la primer entrega de esta serie, para evitar confusiones y comprender mejor el caso práctico que vamos a desarrollar.

Requisitos previos

Esta aplicación tiene como requisitos para su entorno de desarrollo:

Es necesario tener estas tecnologías antes de continuar con el proceso. Cabe destacar que este caso se desarrolla en Linux Ubuntu 20.04, sin embargo, también puede ser desarrollado en otros sistemas operativos.

Hay tres bibliotecas necesarias para la implementación de este caso práctico, dos de ellas para Python y una para NodeJS, estas son:

  • Python:
    • PySerial: Biblioteca utilizada para la comunicación serial con el Arduino.
    • Paho-MQTT: Biblioteca utilizada para la ejecución de un cliente MQTT.
  • NodeJS:
    • MQTT.JS: Biblioteca utilizada para la ejecución un cliente MQTT.

Configuración de Arduino

El Arduino va a simular la implementación de un dispositivo de detección de temperatura, este emite el valor que varía entre 27 y 35 grados. La comunicación se realiza por medio del protocolo serial, de esta manera, el siguiente código muestra la configuración inicial del Arduino:

void setup()
{
    Serial.begin(9600); // El baudrate se establece en 9600
}

Seguidamente, para la simulación del dispositivo de temperatura, se establece el código que se se ejecutará en ciclo:

int temp = 27; // Variable temp para simular el sensor

void loop()
{
    Serial.println(temp); // Se envía el dato
    delay(10000);         // Se espera 10 segundos para realizar una actualización
    temp++; // Se aumenta el valor del dato temp
    if (temp > 35) // Para que el dato no exceda 35
        temp = 27; // Se reinicia a 27 y se varía en este rango
}

El Arduino estará enviando la información cada 10 segundos por medio del puerto serial, este puerto varia dependiendo del sistema operativo y otros aspectos como el puerto de conexión. Un ejemplo de este es /dev/ttyACM0 para Linux o COM 4 en el caso de Windows. Esto puede no siempre ser igual, por lo que se debe asegurar el puerto en el que está conectado el Arduino para transferirle la información.

Este código se implementa utilizando la herramienta de desarrollo Arduino IDE, para ejecutar el programa en el Arduino, se debe Verificar y Compilar el código.

Configuración en Mosquitto

Mosquitto es el framework utilizado para la configuración del Broker MQTT una vez instalado, basta con ejecutar en la terminal:

mosquitto -v

Esto ejecuta Mosquitto en modo verboso, así podemos observar paso a paso lo que sucede durante la constante interacción del broker y los clientes. Al iniciar Mosquitto, la terminal muestra similar a lo siguiente:

1628150609: mosquitto version 2.0.11 starting
1628150609: Using default config.
1628150609: Starting in local only mode. Connections will only be possible from clients running on this machine.
1628150609: Create a configuration file which defines a listener to allow remote access.
1628150609: For more details see https://mosquitto.org/documentation/authentication-methods/
1628150609: Opening ipv4 listen socket on port 1883.
1628150609: Opening ipv6 listen socket on port 1883.
1628150609: mosquitto version 2.0.11 running

De esta manera, ya conocemos el puerto 1883 y la dirección IP que por defecto es 127.0.0.1.

Configuración del Publisher

Nuestro cliente publisher, está escrito en Python, en este cliente se realizan dos funciones importantes:

  • Recibir la información proveniente del Arduino.
  • Publicar la información a los topics correspondientes.

Antes de implementar el código, se deben instalar las bibliotecas antes mencionadas, para esto, ejecutar la siguiente línea de en la terminal:

pip install paho-mqtt pyserial

Para hacer que el cliente en Python funcione como publisher, primero hay que establecer las configuraciones iniciales de conexión tanto para el Arduino, como para el broker.

import paho.mqtt.client as mqtt  # Importa la biblioteca MQTT
import serial  # Importa la biblioteca PySerial

# --- Configuraciones de Arduino ----
arduino = serial.Serial() // Creamos la instancia para la conexión
# El baudrate debe ser el mismo que se configuró en el arduino
arduino.baudrate = 9600
# Este es el puerto de conexión serial, puede cambiar dependiendo de entorno en el que se desarrolle. 
arduino.port = '/dev/ttyACM0'

# --- Configuraciones del Broker ---
# La IP del localhost y el puerto en el que está escuchando el broker
broker_address = '127.0.0.1'
broker_port = 1883

Ahora bien, para iniciar cada conexión, se agregan las configuraciones respectivas:

client = mqtt.Client('klooidPub') # Se crea la instancia del cliente MQTT con el ID 'klooidPub'
# Se configura la propiedad de Last will (La última voluntad)
client.will_set('klooid/temp/even', payload=-1, qos=0, retain=False) # Cuando se desconecte el publicador, entonces el mensaje que enviará (payload) es un -1

# --- Inicio de conecciones ---
arduino.open()  # Conexión con el Arduino iniciada
client.connect(broker_address, port=broker_port) # Conexión con el broker iniciada
client.loop_start() # Se ejecuta un ciclo para gestionar en segundo plano, la comunicación y los mensajes correspondientes a la conexión

Cabe destacar que para la configuración del Last will, se debe establecer en un topic específico, para este ejemplo se utiliza ‘klooid/temp/even’, además, se configuran propiedades como qos=0 y retain=False, la última indica que el Last will no se va a actualizar con el último mensaje entrante. Ahora bien, la función que gestiona la obtención del valor de temperatura y el envío hacia el broker, se describe a continuación:

def get_temp():
    """
    Función principal para la obtención de las señales y la publicación de mensajes
    """
    while True:
        temp = arduino.readline()  # Se obtiene la temperatura
        temp = int(temp) # Se hace una conversión a tipo int

        # Cuando la temperatura es par, se envía a al topic klooid/temp/even
        if temp % 2 == 0:
            # Para el envío de este mensaje, se trabaja con retained messages y con QoS = 2
            client.publish('klooid/temp/even', temp, retain=True, qos=2)
        else:
            # En el caso de que sea impar
            # Retained message activado y un QoS = 0
            client.publish('klooid/temp/odd', temp, retain=True)


# Se inicia la función
get_temp()

Como se puede observar, el publisher estará enviando mensajes a los topics klooid/temp/even y klooid/temp/odd, para cuando el valor de la temperatura es par o impar, respectivamente. Para hacer funcionar el programa, ejecutar la siguiente línea en la terminal:

python3 publisher.py

Donde publisher.py es el nombre del archivo que contiene el código. Una vez que se ejecuta este código, el resultado mostrado en la terminal de Mosquitto es:

1628190067: New connection from 127.0.0.1:38677 on port 1883.
1628190067: New client connected from 127.0.0.1:38677 as klooidPub (p2, c1, k60).
1628190067: Will message specified (2 bytes) (r0, q0).
1628190067: 	klooid/temp/even
1628190067: Sending CONNACK to klooidPub (0, 0)

El broker muestra que recibe una nueva conexión de un cliente al que se le denomina klooidPub, se configura el Last will y se le notifica al cliente que su conexión ha sido recibida (CONNACK). Seguidamente, en la terminal de Mosquitto se ejecutan dos solicitudes:

1628190067: Received PUBLISH from klooidPub (d0, q0, r1, m0, 'klooid/temp/odd', ... (2 bytes))

1628190077: Received PUBLISH from klooidPub (d0, q2, r1, m2, 'klooid/temp/even', ... (2 bytes))
1628190077: Sending PUBREC to klooidPub (m2, rc0)
1628190077: Received PUBREL from klooidPub (Mid: 2)
1628190077: Sending PUBCOMP to klooidPub (m2)

Tenemos dos recepciones de mensajes, uno para odd y otro para even, en el caso de odd se recibe el mensaje y no se realiza ninguna otra acción, esto porque su QoS es 0, sin embargo para even se puede observar cómo se realiza el proceso de verificación de cuatro pasos, en el que se valida la entrega del mensaje para «exactamente, un vez», debido a que el QoS es 2. Cabe destacar que al no haber ningún suscriptor conectado, estos mensajes no serán enviados a nadie por el momento.

Configuración de los Subscribers

Para la configuración de los clientes que funcionan como subscribers es necesario iniciar (en una carpeta distinta, recomendable) un nuevo proyecto de NodeJS. Para iniciarlo ejecutar en la terminal:

npm init

Precionar la tecla Enter hasta que se cree el proyecto, esto puede demorarse un poco. Una vez creado el proyecto, ejecutar:

npm install mqtt --save

De esta manera, se agrega la biblioteca MQTT que será utilizada para crear nuestro cliente. En este caso de aplicación se utilizan dos subscribers, para el primero, el código es:

var mqtt = require('mqtt') // Importa la biblioteca MQTT


var options = {
    clientId: 'klooidSub1' // Se configura el ID del cliente
}

// Se inicia la conexión con el broker
var client = mqtt.connect('mqtt://localhost:1883', options)

// Cuando el evento 'connect' se detecta, quiere decir que se recibe en CONNACK
client.on('connect', function () {
    console.log(client.options) // Se imprimen las propiedades de la conexión del cliente
    client.subscribe('klooid/temp/even') // Se suscribe al topic 'klooid/temp/even'
})

// Cuando el evento'message' es percibido, indica que se publicó un mensaje en el topic
client.on('message', function (topic, message) {
    if (topic === 'klooid/temp/even')
        console.log('Temp even is: %s', message) // Se imprime el mensaje publicado
})

El segundo cliente funciona igual, pero este tiene una diferencia en el ID y en la cantidad de topics a los que se suscribe.

var mqtt = require('mqtt') // Importa la biblioteca MQTT


var options = {
    clientId : 'klooidSub2' // Se configura el ID
}

// Se inicia la conexión con el broker
var client = mqtt.connect('mqtt://localhost:1883', options)

// Cuando el evento 'connect' se detecta, quiere decir que se recibe en CONNACK
client.on('connect', function () {
    console.log(client.options) // Se imprimen las propiedades de la conexión
    // Se suscribe a todos los topics de este nivel en adelante: 
    // 'klooid/temp/even' y 'klooid/temp/odd'
    client.subscribe('klooid/temp/#') 
})

// Cuando el evento 'message' es percibido, indica que se publicó un mensaje en el topic
client.on('message', function (topic, message) {
    if (topic === 'klooid/temp/even')
        console.log('Temp even is: %s', message) // Imprime el mensaje para even
    else if (topic === 'klooid/temp/odd')
        console.log('Temp odd is: %s', message) // Imprime el mensaje para odd
})

Muy bien, teniendo los clientes configurados, para hacerlos funcionar se deben ejecutar los comandos:

node client1.js
node client2.js

Ambos en terminales distintas, donde client1.js es el archivo que contiene el código para el subscriber 1 y client2.js es el archivo que contiene el código para el subscriber 2.

Funcionamiento

Con todo en ejecución, al conectar los subscribers se debe ver lo siguiente en la terminal de Mosquitto:

  • Subscriber 1:
1628194404: New client connected from 127.0.0.1:46294 as klooidSub1 (p2, c1, k60).
1628194404: No will message specified.
1628194404: Sending CONNACK to klooidSub1 (0, 0)
1628194404: Received SUBSCRIBE from klooidSub1
1628194404: 	klooid/temp/even (QoS 0)
1628194404: klooidSub1 0 klooid/temp/even
1628194404: Sending SUBACK to klooidSub1
1628194404: Sending PUBLISH to klooidSub1 (d0, q0, r1, m0, 'klooid/temp/even', ... (2 bytes))

El broker recibe la conexión, le envía la confirmación de conexión (CONNACK), después, recibe la solicitud de suscripción, suscribe el cliente al topic solicitado y después, le envía el mensaje publicado en el topic. Ahora bien, ¿por qué le envía el mensaje publicado, si apenas se está conectando?

Esto se debe a que el publisher tiene la propiedad de Retained Messages activada, por lo que el último mensaje será publicado a cada nueva conexión entrante, sin necesidad de que espere los 10 segundos de publicación.

  • Subscriber 2:
1628194411: New connection from 127.0.0.1:46296 on port 1883.
1628194411: New client connected from 127.0.0.1:46296 as klooidSub2 (p2, c1, k60).
1628194411: No will message specified.
1628194411: Sending CONNACK to klooidSub2 (0, 0)
1628194411: Received SUBSCRIBE from klooidSub2
1628194411: 	klooid/temp/# (QoS 0)
1628194411: klooidSub2 0 klooid/temp/#
1628194411: Sending SUBACK to klooidSub2
1628194411: Sending PUBLISH to klooidSub2 (d0, q0, r1, m0, 'klooid/temp/odd', ... (2 bytes))
1628194411: Sending PUBLISH to klooidSub2 (d0, q0, r1, m0, 'klooid/temp/even', ... (2 bytes))

El proceso es el mismo, sin embargo, al final se publican dos mensajes. Esto se debe a que el cliente está suscrito a dos tópicos distintos y en ellos, la propiedad de Retained Messages está activada.

Fig 1. Resultado de conexión de los subscribers.

En la figura 1 se aprecia cómo las conexiones se han realizado con éxito para ambos subscribers, en la izquierda se muestra la conexión del subscriber 1 que este recibe únicamente publicaciones en el topic even mientras que en la derecha se observa cómo las actualizaciones para ambos topics son procesadas en el subscriber 2.

Finalmente, ¿qué pasa si el publisher se desconecta?

Fig 2. Desconexión del publisher.

En la figura 2, se muestra en la última línea, cómo el mensaje recibido para ambos es de -1, este es el mensaje que se configuró en el publisher para que se enviara al presentar una desconexión, básicamente, es el resultado de implementar el Last will and Testament.

Conclusiones

En este caso de aplicación, se aprecia cómo MQTT es un protocolo simple pero robusto, de fácil implementación y que aporta grandes beneficios para la comunicación en proyectos IoT. Esto se debe a la capacidad de transferencia de información, la seguridad que brinda para el transporte de mensajes y las características como Last will and Testament que son un factor diferenciador. Además, MQTT brinda aislamiento temporal y espacial, donde cada dispositivo es agnóstico del otro y no necesita saber la infraestructura general de la red ni estar comunicada a ella, únicamente se preocupa por la conexión broker-cliente.

Escribe un comentario

Comentario