L’objectif est d’écrire une application Android qui accède au serveur MQTT de The Things Network.
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 :
Lire la suite du document sur MQTT.
Il faut ajouter les dépendances vers les bibliothèques nécessaires à l’utilisation du protocole MQTT : le client Paho MQTT et le service Android fournis par Eclipse.
Liens : Eclipse Paho et github
Il faut ajouter l’url d’Eclipse Paho dans le ficher build.gradle
du projet :
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven {
url "https://repo.eclipse.org/content/repositories/paho-snapshots/"
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Puis il faut ajouter les dépendances vers le client Paho MQTT et le service Android dans le fichier build.gradle
du dossier app
:
...
dependencies {
...
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
}
Remarque : en cas d’utilisation d’androidx
, il faudra ajouter :
...
dependencies {
...
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
}
On obtient :
L’utilisation du protocole MQTT suppose que l’on ait un accès réseau. Pour cela, on va attribuer les permissions INTERNET
, ACCESS_NETWORK_STATE
, ACCESS_WIFI_STATE
et WAKE_LOCK
à l’application Android dans son fichier AndroidManifest.xml
. On ajoutera aussi le service MqttService
.
Exemple :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tv.monapplicationmqtt">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ParametresConnexionActivity"></activity>
<service android:name="org.eclipse.paho.android.service.MqttService" />
</application>
</manifest>
On va réaliser une classe ClientMQTT
qui encapsule une instance de MqttAndroidClient
.
Lien : la classe MqttAndroidClient
Le constructeur est le suivant :
On lui passe en paramètre le serverURI
qui spécifie le protocole, le nom d’hôte et le port à utiliser pour se connecter à un serveur MQTT. Dans notre cas, on va utiliser le serveur The Things Network.
MqttAndroidClient mqttAndroidClient;
String serverUri = "tcp://eu.thethings.network:1883";
String clientId = "mes_ruches";
mqttAndroidClient = new MqttAndroidClient(context, serverUri, clientId);
Ensuite, on paramètre le login et le mot de passe de connexion dans une instance MqttConnectOptions
. La connexion est réalisée par un appel à connect()
. Si l’opération réussie, on souscrit au topic de notre Device ID :
String username = "mes_ruches"; // <ApplicationID>
String password = "ttn-account-v2.vC-aqMRnLLzGkNjODWgy81kLqzxBPAT8_mE-L7U2C_w"; // Access Keys
String subscriptionTopic = "mes_ruches/devices/capteur_temperature/up"; // <ApplicationID>/devices/<DeviceID>/up
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
mqttConnectOptions.setAutomaticReconnect(true);
mqttConnectOptions.setCleanSession(false);
mqttConnectOptions.setUserName(username);
mqttConnectOptions.setPassword(password.toCharArray());
mqttAndroidClient.connect(mqttConnectOptions, null, new IMqttActionListener()
{
@Override
public void onSuccess(IMqttToken asyncActionToken)
{
DisconnectedBufferOptions disconnectedBufferOptions = new DisconnectedBufferOptions();
disconnectedBufferOptions.setBufferEnabled(true);
disconnectedBufferOptions.setBufferSize(100);
disconnectedBufferOptions.setPersistBuffer(false);
disconnectedBufferOptions.setDeleteOldestMessages(false);
mqttAndroidClient.setBufferOpts(disconnectedBufferOptions);
mqttAndroidClient.subscribe(subscriptionTopic, 0, null, new IMqttActionListener(){});
}
});
Pour récupérer les messages du topic, on utilisera une instance de la classe MqttCallbackExtended
qui fournit une interface de callback. La méthode a surchargé de cette classe est messageArrived(String topic, MqttMessage mqttMessage)
pour récupérer un MqttMessage
.
Il faut enregistrer l’instance de rappel MqttCallbackExtended
en appelant setCallback()
de la classe MqttAndroidClient
.
Lien : la classe MqttCallbackExtended
On est prêt à utiliser une instance de notre classe ClientMQTT
:
ClientMQTT clientMQTT;
clientMQTT = new ClientMQTT(getApplicationContext());
clientMQTT.mqttAndroidClient.setCallback(new MqttCallbackExtended()
{
@Override
public void connectComplete(boolean b, String s)
{
Log.w(TAG,"connectComplete");
}
@Override
public void connectionLost(Throwable throwable)
{
Log.w(TAG,"connectionLost");
}
@Override
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception
{
Log.w(TAG, "messageArrived : " + mqttMessage.toString());
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken)
{
Log.w(TAG, "deliveryComplete");
}
});
Remarques :
payload_raw
: le décodage base64 sera réalisable par la méthode decode()
de la classe Base64
.payload_fields
: pour la manipulation des données JSON, on utilisera la classe JSONObject
.Codes sources :
base64 est un codage de données utilisant 64 caractères (en fait 65 avec le caractère de complément). On forme des groupes de 24 bits successifs de données qui sont codés par une chaîne de 4 caractères. Un caractère représente donc 6 bits. Si on a moins de 24 bits, on ajoute des « = » complémentaires pour former quand même 4 caractères. Chaque groupe de 6 bits est utilisée comme index dans la table de codage.
Table de décodage :
Exemple de décodage : réception de données d’un capteur DHT22 (Température et Humidité)
“payload_raw”:“CLYO2A==” → données brutes encodées en base64 des 2 x 2 octets de données en provenance du capteur DHT22 (Température et Humidité)
“payload_fields”:{“humidite”:38,“temperature”:22.3} → données décodées et formatées en JSON par la fonction Decoder() du serveur TTN
Donc décodons les données base64 CLYO2A==
:
On reforme les 4 octets :
On obtient les valeurs :