Site : tvaira.free.fr
MQTT (Message Queuing Telemetry Transport) est un protocole de messagerie publish-subscribe basé sur le protocole TCP/IP. [cf. fr.wikipedia.org/wiki/MQTT]
C’est maintenent un protocole utilisé dans le cadre de l’Internet des objets (IoT) et peut être considéré comme un protocole M2M (Machine-to-Machine).
Liens :
MQTT offre une solution adaptée à une transmission de messages (proche d’un système de messagerie ou d’un bus logiciel comme D-BUS). Tous les acteurs se connectent à un élément central (le broker) et communiquent entre eux en envoyant (publish) des messages libellés avec un sujet et en souscrivant (subscribe) aux sujets qui les intéressent.
Les messages sont envoyés par des « publishers » sur un « topic » (canal de communication) à un « broker ». Ces messages peuvent être lus par des « subscribers » (abonnés). Les « topics » peuvent avoir une hiérarchie qui permettra de sélectionner les informations. Les « publishers » et « subscribers » sont considérés comme des « clients » MQTT : Liste des clients et bibliothèques.
Chaque message doit donc avoir un sujet associé (« topic »). Les sujets (« topic ») sont divisés en niveaux. Il y a des « wild-cards » (jokers) +
et #
:
capteurs/temperature/salon et capteurs/temperature/chambre capteurs/humidite/salon et capteurs/humidite/chambre
+
: sujet unique. Par exemple : a/+/c sera abonné à a/b/c et a/x/c.
#
: multi-sujets. Par exemple : a/# sera abonné à a/‹tout›
Le publisher définira l’option QoS dans les messages qui transitent via MQTT :
Le protocole MQTT fonctionne sur TCP en utilisant le port 1883.
On capture un échange publish entre un client (localhost) et un broker (localhost) avec wireshark :
Les trames 1, 2 et 3 correspondent à l’établissement classique (SYN-SYN/ACK-ACK) du mode connecté de TCP ici du client publisher vers le port 1883 du broker et les trames 13 et 14 correspondent à sa libération.
Les trames 4, 6, 8, 9 et 10 transportent le protocle MQTT. On filtre maintenant seulement l’échange MQTT :
L’échange peut se représenter de la manière suivante :
[trame 4] Client1 → Connect Command → Broker
[trame 6] Client1 ← Connect Ack ← Broker
[trame 8] Client1 → Publish Message → Broker
[trame 9] Client1 → Disconnect Req → Broker
[trame 10] Client2 ← Publish Message ← Broker
Capture wireshark : mqtt-publish.pcap
Remarque : le Client2 est ici un « subscriber » déjà connecté au broker.
Capture wireshark : mqtt-subscribe.pcap
Pour réaliser quelques essais en MQTT, il faut tout d’abord un broker. On utilisera Mosquitto.
Installation du broker MQTT Mosquitto :
$ sudo apt-get install mosquitto
$ mosquitto -v
mosquitto version 1.4.15 (build date Sat, 07 Apr 2018 11:16:43 +0100) starting
Using default config.
Opening ipv4 listen socket on port 1883.
...
Les ports d’écoute du broker Moquito sont :
Le service mosquitto
est contrôlé par systemd
si celui-ci est installé :
$ systemctl status mosquitto
mosquitto.service - LSB: mosquitto MQTT v3.1 message broker
Loaded: loaded (/etc/init.d/mosquitto; generated)
Active: active (running) since Sun 2018-11-18 09:28:52 CET; 12s ago
Docs: man:systemd-sysv-generator(8)
Tasks: 1 (limit: 4915)
CGroup: /system.slice/mosquitto.service
└─25349 /usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
nov. 18 09:28:52 sedatech systemd[1]: Starting LSB: mosquitto MQTT v3.1 message broker...
nov. 18 09:28:52 sedatech mosquitto[25308]: * Starting network daemon: mosquitto
nov. 18 09:28:52 sedatech mosquitto[25308]: ...done.
nov. 18 09:28:52 sedatech systemd[1]: Started LSB: mosquitto MQTT v3.1 message broker.
Le fichier de configuration par défaut est /etc/mosquitto/mosquitto.conf
et les messages de journalisation (log) seront stockés dans /var/log/mosquitto/mosquitto.log
.
Installation des clients pour publier et souscrire à des sujets :
$ sudo apt-get install mosquitto-clients
On dispose maintenant de deux « clients » en mode CLI :
Les options de bases sont :
-h
: hostname (par défaut localhost)-t
: le « topic »-m
: le messagePour créer un échange de messages MQTT, on va ouvrir (au moins) deux terminaux, un pour le publisher et l’autre pour le subscriber.
$ mosquitto_sub -t greeting
$ mosquitto_pub -t greeting -m "Hello World !"
Exemple simulant des capteurs de température :
$ mosquitto_sub -h 192.168.52.12 -t "capteurs/#" -v
On simule l’envoi de températures :
$ mosquitto_pub -h 192.168.52.12 -t capteurs/temperature/salon -m 23.0
$ mosquitto_pub -h 192.168.52.12 -t capteurs/temperature/chambre -m 21.0
Les valeurs peuvent être de type numérique ou du texte (au format JSON par exemple).
Il existe une bibliothèque C libmosquitto
pour écrire un client MQTT :
$ sudo apt-get install libmosquitto-dev
Quelques fonctions de la bibliothèque libmosquitto
:
mosquitto_lib_version
: la version de la bibliothèquemosquitto_lib_init
: initialise la bibliothèquemosquitto_lib_cleanup
: libère l’utilisation de la bibliothèquemosquitto_new
: créer un clientmosquitto_destroy
: détruire un clientmosquitto_reinitialize
: détruit un client et en crée un nouveaumosquitto_username_pw_set
: le userid et le password pour l’authentificationmosquitto_connect
: connecter un client au brokermosquitto_connect_bind
: idem mais contraint l’interface à faire un bindmosquitto_connect_async
: connexion asynchronemosquitto_reconnect
: reconnexion après une connexion perduemosquitto_reconnect_async
: idem en asynchronemosquitto_disconnect
: déconnexion d’un clientmosquitto_publish
: publish un message avec un sujetmosquitto_subscribe
: subscribe aux messages d’un sujetmosquitto_unsubscribe
: unsubscribe aux messages d’un sujetmosquitto_loop
: boucle de traitement des messages entrants en provenance du brokermosquitto_loop_forever
: idem mais continue le traitement jusqu’à ce que le client soit déconnectémosquitto_loop_read
mosquitto_loop_write
mosquitto_socket
: retourne la socket TCP/IP bas niveaumosquitto_want_write
: retourne vrai si on est en attente d’envoi de données au brokermosquitto_loop_start
: démarre un thread pour triater la boucle mosquitto_loop
en arrière-planmosquitto_loop_end
: arrête un threadExemple :
#include <stdio.h>
#include <string.h>
#include <mosquitto.h>
/*
* gcc publisher.c -o publisher -lmosquitto
*/
int main(int argc, char *argv[])
{
char *broker = "192.168.52.12";
int port = 1883;
char *message = "hello world !";
char *sujet = "greeting";
struct mosquitto *client;
int ret = 0;
// initialise la bibliothèque
mosquitto_lib_init();
// crée un client
// parametres : generate an id, create a clean session, no callback param
client = mosquitto_new(NULL, true, NULL);
ret = mosquitto_connect(client, broker, port, false); // pas de connexion persistante
if (ret != MOSQ_ERR_SUCCESS)
{
perror("mosquitto_connect");
return(ret);
}
// publie un message dans le sujet
// parametres : client handle, message id, topic, length of message, message to be sent, QoS, retained
mosquitto_publish(client, NULL, sujet, strlen(message)+1, (const void *)message, 0, false);
// détruit le client
mosquitto_destroy(client);
// libère les ressources MQTT
mosquitto_lib_cleanup();
return 0;
}
Code source : publisher.c
Qt For Automation fournit des modules supplémentaires pour la communication M2M (machine à machine) dont Qt MQTT.
Qt MQTT fournit une implémentation conforme à la norme MQTT. Il permet aux applications d’agir en tant qu’IHM de télémétrie et aux appareils de publier des données de télémétrie.
Remarque : Qt MQTT fait parti de Qt For Automation et pas directement de Qt.
$ sudo git clone git://code.qt.io/qt/qtmqtt.git
$ cd qtmqtt/
$ sudo qmake
$ sudo make
$ sudo make install
En cas d’erreurs, il est possible que cela soit du à la version de Qt. Dans ce cas, il faut sélectionner la branche qui correspond à la version de Qt utilisée.
Par exemple :
$ qmake -v
QMake version 3.1
Using Qt version 5.11.3 in /usr/lib/arm-linux-gnueabihf
$ sudo git clone git://code.qt.io/qt/qtmqtt.git
$ cd qtmqtt/
$ sudo git checkout 5.11.2
$ sudo qmake
$ sudo make
$ sudo make install
Liens :
Pour accèder aux classes du module Qt MQTT, il faut ajouter dans son fichier de projet .pro
:
QT += mqtt
Les classes du module Qt MQTT sont :
Créer un client MQTT :
#include <QtMqtt/QtMqtt>
#include <QtMqtt/QMqttClient>
...
QMqttClient *client;
...
...
client = new QMqttClient(this);
client->setHostname("192.168.52.12");
client->setPort(1883);
...
Ensuite, il est possible de se connecter :
client->connectToHost();
Et au final de se déconnecter :
client->disconnectFromHost();
Une fois connecté, on s’abonnera à un sujet :
QMqttSubscription *subscription;
...
subscription = client->subscribe(QLatin1String("greeting"));
if (!subscription)
{
QMessageBox::critical(this, "Erreur", "Impossible de s'abonner !");
return;
}
On peut publier un message :
QByteArray message = "hello world Qt !";
quint8 qos = 1;
client->publish(QLatin1String("greeting"), message, qos);
Et on pourra recevoir les messages du broker : il faut connecter le signal messageReceived()
à un slot qui recevra en paramètres le message (un QByteArray
) et le topic (un QMqttTopicName
)
// connexion signal/slot
connect(client, SIGNAL(messageReceived(const QByteArray &, const QMqttTopicName &)), this, SLOT(messageRecu(const QByteArray &, const QMqttTopicName &)));
// le slot
void XXX::messageRecu(const QByteArray &message, const QMqttTopicName &topic)
{
qDebug() << QDateTime::currentDateTime().toString() << topic.name() << message;
}
Pour gérer la connexion au niveau de l’application, les signaux stateChanged()
, connected()
et disconnected()
peuvent être utilisés. On a vu dans la capture wireshark ci-dessus, un échange de Ping Request et Ping Response : pour faire une requête, on appellera requestPing()
et la réponse sera signalée par pingResponseReceived()
.
Exemple (sans IHM) :
// Fichier .h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QtMqtt/QtMqtt>
class Dialog : public QDialog
{
Q_OBJECT
private:
QMqttClient *client;
QMqttSubscription *subscription;
public:
Dialog(QWidget *parent = 0);
~Dialog();
public slots:
void connecte();
void deconnecte();
void messageRecu(const QByteArray &message, const QMqttTopicName &topic);
};
#endif // DIALOG_H
// Fichier .cpp
#include "dialog.h"
#include <QDebug>
#include <QMessageBox>
Dialog::Dialog(QWidget *parent) : QDialog(parent)
{
client = new QMqttClient(this);
client->setHostname("192.168.52.12");
client->setPort(1883);
connect(client, SIGNAL(connected()), this, SLOT(connecte()));
connect(client, SIGNAL(disconnected()), this, SLOT(deconnecte()));
connect(client, SIGNAL(messageReceived(const QByteArray &, const QMqttTopicName &)), this, SLOT(messageRecu(const QByteArray &, const QMqttTopicName &)));
client->connectToHost();
}
Dialog::~Dialog()
{
client->disconnectFromHost();
}
void Dialog::connecte()
{
subscription = client->subscribe(QLatin1String("greeting"));
if (!subscription)
{
QMessageBox::critical(this, "Erreur", "Impossible de s'abonner au broker!");
return;
}
}
void Dialog::deconnecte()
{
QMessageBox::critical(this, "Information", "Déconnexion du broker");
}
void Dialog::messageRecu(const QByteArray &message, const QMqttTopicName &topic)
{
qDebug() << QDateTime::currentDateTime().toString() << topic.name() << message;
}
Code source : qt-mqtt-client.zip
The Things Network est un réseau LoRaWAN open source qui est disponible dans plusieurs pays et basé sur une communauté de plus de 17000 membres. Il peut être utilisé sans contrainte commerciale ou privée. Things Network utilise MQTT pour publier les activations et les messages des appareils référencés, mais il permet également de publier un message pour un appareil spécifique en réponse.
Lire : TTN MQTT API
Cela change la connexion :
Soit :
client = new QMqttClient(this);
client->setHostname("eu.thethings.network");
client->setPort(1883);
client->setUsername(Application_ID);
client->setPassword(Application_Access_Key);
On distinguera les deux types messages :
→ Topic : <AppID>/devices/<DevID>/up
$ mosquitto_sub -h eu.thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/up'
→ Uplink Fields : my-app-id/devices/my-dev-id/up/<field>
. Les données seront une chaîne avec un encodage de style JSON.
→ Topic : <AppID>/devices/<DevID>/down
$ mosquitto_pub -h <Region>.thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/down' -m '{"port":1,"payload_raw":"Base64 message"}'
La classe QByteArray
possède une méthode toBase64()
pour encoder la partie “Base64 message”.
→ Downlink Fields : Au lieu de payload_raw, on peut également utiliser payload_fields.
$ mosquitto_pub -h <Region>.thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/down' -m '{"port":1,"payload_fields":{"led":true}}'
Qt MQTT fait parti de Qt For Automation et pas directement de Qt. Qt For Automation fournit des modules supplémentaires pour la communication M2M (machine à machine) dont Qt MQTT.
La version de Qt utilisée ici est la 5.10.1. La fabrication de QtMqtt nécessite une installation de Qt5 pour Android fonctionnelle.
On utilisera le script build_qt_mqtt.sh qu’il faudra paramétrer en fonction de son installation :
#!/bin/bash
export NDK_ROOT="/home/tv/Android/Sdk/android-ndk-r10e"
export ANDROID_NDK_ROOT=$NDK_ROOT
export QT_ROOT="/opt/Qt5.10.1"
export SR="$NDK_ROOT/platforms/android-19/arch-arm"
export BR="$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86/bin/arm-linux-androideabi-"
platform=android-armv7
platform_sort=arm
qmake="$QT_ROOT/5.10.1/android_armv7/bin/qmake"
dir=qtmqtt
rm -rf $dir
git clone git://code.qt.io/qt/qtmqtt.git
[ ! -f "$qmake" ] && { echo "qmake introuvable"; exit 1; }
[ ! -x "$qmake" ] && { echo "qmake permission execution manquante"; exit 1; }
pushd $dir
make clean
$qmake "QMAKE_CXX=$BR"g++ "QMAKE_LINK=$BR"g++ -o Makefile qtmqtt.pro
make || exit 1
make install || exit 1
popd
echo
echo "fini"
echo
rm -rf $dir
Le site de Qt fournit l’exemple Quick MQTT Example pour tester.
Code source : qt-mqtt-client.zip
En Java sous Android, il faudra utiliser le client Paho MQTT et le service Android fournis par Eclipse.
Lien : MQTT