Saltar al contenido principal

Módulo MQTT

El módulo MQTT publica y recibe mensajes por MQTT, el protocolo de mensajería de muchos aparatos conectados, a través de un servidor que los reparte (el broker). Un programa publica mensajes en un tema, como casa/salon/temperatura, y recibe los de los temas a los que se suscribe con un callback.

La conexión con el broker no se abre desde el programa: la configuras fuera, en el perfil de la placa o, en Windows, en la configuración del proyecto (ver Plataformas).

use MQTT

Captura del módulo MQTT de JARU para conectar aplicaciones ESP32 a un broker y publicar mensajes IoT

Plataformas​

WindowsESP32 / ESP32-S3ESP32-P4ESP32-S31
La conexión se configura enConfig › Platform › Windows, en el proyecto (Ajustes)El perfil de la placa, en ESP32 Tools (Pantalla, sonido, SD y red)IgualIgual
Cuándo conectaAl arrancar: espera al broker 3 segundos como mucho y, si no conecta, empieza el programa y lo sigue intentandoPor su cuenta, mientras el programa ya correIgualIgual
Si se pierde la conexiónVuelve a conectar solaVuelve a conectar sola, también si se cae la WiFiVuelve a conectar solaVuelve a conectar sola
Usuario y contraseña del brokerLos de la configuraciónIgualIgualIgual
QoS de publish y subscribe00La de la configuraciónLa de la configuración
Retain en publishNoEl de la configuraciónIgualIgual
intStats() y resetIntStats()SíNo existen: dan ERR_ATTRIBUTE_NOT_FOUNDIgualIgual

Funciones​

FunciónQué haceDevuelve
MQTT.publish(tema, mensaje)Publica un mensaje en un tematrue
MQTT.subscribe(tema)Se suscribe a un tematrue
MQTT.unsubscribe(tema)Deja de estar suscrito a un tematrue
MQTT.callback(funcion)Dice qué función recibe los mensajestrue
MQTT.isConnect()Mira si hay conexión con el brokertrue o false
MQTT.pubinfo()Publica la versión de la VMtrue, o false sin conexión
MQTT.intStats()Solo en Windows: cómo va la cola de avisos de la VMUn mapa
MQTT.resetIntStats()Solo en Windows: pone a cero esas cuentastrue

publish, subscribe y unsubscribe no hacen nada si no hay conexión con el broker, y devuelven true igualmente: su true no confirma nada. Para saber si hay conexión, usa isConnect().

Si les pasas un número de argumentos que no es el que piden, devuelven un valor de error, que try no captura (ver Errores y excepciones). La excepción es intStats, que lanza ERR_INVALID_ARG_NUMBER.

Si el tema, o el mensaje de publish, no es una cadena, lanzan ERR_TYPE_MISMATCH, que try sí captura.

publish(tema, mensaje)​

Publica mensaje en el tema tema. Los dos tienen que ser cadenas.

use MQTT

var temperatura = 25.5
MQTT.publish("sensores/temperatura", temperatura.toString())

// Un mensaje en JSON
MQTT.publish("aparato/estado", '{"activo": true, "bateria": 85}')

Si el mensaje es un número, lanza ERR_TYPE_MISMATCH: conviértelo antes en cadena con toString(), como temperatura en el ejemplo.

subscribe(tema)​

Se suscribe al tema tema, una cadena: desde entonces, los mensajes que se publiquen en él llegan al callback. Admite los comodines de MQTT: + vale por un nivel del tema y #, al final, por todos los que siguen.

use MQTT

MQTT.subscribe("casa/salon/luz")
MQTT.subscribe("comandos/#") // todos los temas que empiezan por comandos/

Sin conexión no hace nada, y con la sesión limpia, que es lo de serie, la suscripción se pierde si se pierde la conexión: suscríbete cuando isConnect() dé true, y otra vez después de cada reconexión, como en el ejemplo completo.

unsubscribe(tema)​

Deja de estar suscrito al tema tema. Tiene que ser el mismo texto con el que te suscribiste.

use MQTT

MQTT.subscribe("sensores/temperatura")
// … más tarde
MQTT.unsubscribe("sensores/temperatura")

callback(funcion)​

Dice qué función recibe los mensajes de todos los temas a los que estás suscrito. Hay un solo callback: si lo llamas otra vez, el nuevo sustituye al anterior.

funcion puede ser una función (alRecibir), un método de un objeto (estacion.alRecibir) o el nombre de una función global en una cadena ("alRecibir"). Tiene que recibir exactamente dos parámetros, el tema y el mensaje, los dos cadenas: si tiene otro número, los mensajes se descartan sin avisar. Si funcion no es nada de eso, callback devuelve un valor de error.

use MQTT

func alRecibir(tema, mensaje)
println(tema, ": ", mensaje)
end

MQTT.callback(alRecibir)
MQTT.subscribe("sensores/#")

Con un método, el callback corre con su this, y MQTT mantiene viva la instancia mientras esté registrada:

use MQTT

class Estacion
def init(nombre)
this.nombre = nombre
this.mensajes = 0
end

def alRecibir(tema, mensaje)
this.mensajes = this.mensajes + 1
println(this.nombre, " recibe: ", mensaje)
end
end

var meteo = Estacion("Meteo")
MQTT.callback(meteo.alRecibir)
MQTT.subscribe("sensores/#")
Cuándo corre el callback

Los mensajes llegan por su cuenta y esperan en una cola de la VM. El callback los recibe de uno en uno, dentro de tu programa, como los de Timer: en los puntos seguros de la VM (al dar la vuelta un bucle y al llamar a una función o volver de ella), nunca dentro de pause(). Por eso un programa que espera mensajes da vueltas con pausas cortas, y lo que llega durante un pause(10000) no se atiende hasta que acaba. Al terminar el programa, el callback se suelta.

isConnect()​

Devuelve true si hay conexión con el broker, y false si no.

use MQTT

if (MQTT.isConnect()) then
MQTT.publish("sensores/estado", "activo")
else
println("Sin conexión MQTT")
end

pubinfo()​

Publica la versión de la VM en el tema del aparato, <usuario>/<id>/device, con el usuario y el identificador de la configuración MQTT. Con el usuario vacío, que es lo de serie, el tema empieza por la barra: /<id>/device. El mensaje es como {"HWVersion":"0.9.7.20261104 01"}. Devuelve true si lo ha publicado y false si no hay conexión.

use MQTT

if (MQTT.pubinfo()) then
println("Versión publicada")
end

intStats()​

Solo en Windows. Devuelve un mapa con las cuentas de la cola de avisos de la VM, donde esperan su turno los mensajes de MQTT y también las interrupciones de GPIO, los disparos de los timers y los choques de sprites:

ClaveQué cuenta
enqueuedLos avisos que han entrado en la cola
dequeuedLos que ha sacado la VM para atenderlos
droppedLos que se han perdido porque la cola estaba llena
hasPendingtrue si ahora mismo hay alguno esperando

Las cuentas empiezan con el programa, o en el último resetIntStats(). En la cola caben 127 avisos: si dropped crece, el programa no da abasto, y le llegan más mensajes o interrupciones de los que atiende.

En las placas no existe: llamarla lanza ERR_ATTRIBUTE_NOT_FOUND (Module has no function 'intStats'.). En un programa que también va a la placa, pregunta antes con platform():

use MQTT

if (platform() == HW_WIN32) then
println(MQTT.intStats()) // {hasPending: false, enqueued: 0, dequeued: 0, dropped: 0}
end

resetIntStats()​

Solo en Windows. Pone a cero las cuentas de intStats() y devuelve true. En las placas no existe, como intStats.

Ejemplo completo​

Un aparato que enciende y apaga un LED con los mensajes del tema casa/led, confirma cada cambio y publica un latido cada 10 segundos. Se suscribe al conectar, y otra vez si se pierde la conexión:

use MQTT, GPIO

const PIN_LED = 2
GPIO.pinmode(PIN_LED, GPIO.OUTPUT)

// Un solo callback para todos los temas
func alRecibir(tema, mensaje)
println("[MQTT] ", tema, ": ", mensaje)
if (tema == "casa/led") then
if (mensaje == "on") then
GPIO.write(PIN_LED, HIGH)
elsif (mensaje == "off") then
GPIO.write(PIN_LED, LOW)
end
MQTT.publish("casa/led/estado", mensaje) // confirma el cambio
end
end

MQTT.callback(alRecibir)

var suscrito = false
var ultimoLatido = 0
while (true)
if (MQTT.isConnect()) then
if (!suscrito) then
MQTT.subscribe("casa/led") // al conectar, y otra vez tras perder la conexión
suscrito = true
end
if (clock() - ultimoLatido > 10000) then
MQTT.publish("casa/latido", "vivo")
ultimoLatido = clock()
end
else
suscrito = false
end
pause(50) // pausas cortas: los mensajes se atienden entre una y otra
end

Trampas y problemas conocidos​

  • Un true que no confirma nada. Sin conexión, publish, subscribe y unsubscribe no hacen nada y devuelven true. Mira isConnect().
  • El programa puede empezar antes que la conexión. En la placa, siempre; en Windows, si el broker no contesta en 3 segundos. Un subscribe al principio del programa se pierde si el broker todavía no ha contestado. Suscríbete cuando isConnect() dé true.
  • Las suscripciones no sobreviven a una reconexión. Con la sesión limpia de serie, al volver la conexión hay que suscribirse otra vez.
  • Pausas largas, mensajes que esperan. Mientras el programa está en pause(), los mensajes no se atienden: esperan en la cola, y si se llena, se pierden.
  • El callback, con dos parámetros. Con otro número de parámetros, los mensajes se descartan sin avisar.
  • Un Client ID para cada VM o placa. Dos con el mismo Client ID en el mismo broker se echan la una a la otra. En Windows, todos los proyectos traen de serie JARUWin11_01: si conectas dos a la vez, o usas un broker público como test.mosquitto.org, pon a cada proyecto el suyo en Config › Platform › Windows.

Todos los problemas conocidos de JARU, juntos, están en Problemas conocidos.