TP04 : Station météo MQTT
Dans le TP précédent, nous avons réalisé une station météo connectée qui transmet des mesures en effectuant du broadcasting BLE. Le but de ce TP est de publier ces mêmes mesures à un site Internet et de permettre ainsi un accès aux données depuis n’importe où.
Pour ce TP, nous utiliserons une seule cible qui devra directement transmettre les données environnementales sur un serveur par MQTT.
Objectifs du TP
Ce TP a pour but de mettre en pratique une communication MQTT par WiFi
À la fin de ce TP, les étudiants :
- auront activé la stack IP de Mbed OS et configuré le WiFi;
- auront implémenté un client MQTT sur Mbed OS;
- auront publié des données sur un serveur Internet;
- auront réalisé des analyses statiques de code et corrigé les erreurs rapportées ;
- auront rédigé un journal de travail et déposé le PDF sur Teams.
Les livrables sont :
- un release avec le tag “tp04” doit être créé sur votre dépôt.
- un journal de travail déposé sur Teams.
Temps accordé : 4 périodes de 45 minutes en classe + travail personnel à la maison
Délai
Les dates de rendu des TPs sont indiquées sur le plan de cours.
Matériel
Pour ce TP, vous avez besoin du WiFi BLE click :
Ce click-board utilise l’interface UART et vous pouvez le placer sur le slot 1 ou sur le slot 2.
Configuration de Mbed OS et de PlatformIO
Pour utiliser le module WiFi et la librairie MQTT, vous devez configurer votre projet de la manière suivante :
Fichier platformio.ini
Dans le fichier platformio.ini
, ajoutez la librairie pour le module EPS32 et
pour le MQTT aux dépendances de votre projet (ligne 5 et 6) :
1 2 3 4 5 |
|
Dossier mbedignore
Pour ce TP, nous devons également activer la stack IP et le WiFi de Mbed OS:
Éditez le fichier mbedignore/connectivity/.mbedignore
et supprimez les lignes suivantes :
drivers/wifi/*
libraries/*
lwipstack/*
mbedtls/*
netsocket/*
Les lignes suivantes devraient toujours être présentes :
cellular/*
drivers/802.15.4_RF/*
drivers/cellular/*
drivers/emac/*
drivers/lora/*
drivers/mbedtls/*
drivers/nfc/*
lorawan/*
nanostack/*
nfc/*
Effacez aussi la ligne du fichier mbedignore/platform/.mbedignore
(et
conservez le fichier vide)
Fichier mbed_app.json
Pour modifier les valeurs par défaut, éditez le fichier mbed_app.json
de votre
projet et ajoutez les lignes suivantes :
1 2 3 4 5 6 7 8 |
|
Publication vers un broker MQTT public
Afin de faciliter l’intégration des données dans une infrastructure existante, nous utiliserons un broker public pour publier les données de votre station météo. Selon les instructions que vous recevrez en classe, vous utiliserez le broker ThingsBoard.io. Votre code client sera identique, au-delà des informations permettant de vous authentifier. Cela démontre l’avantage d’utiliser un standard de communication bien établi.
Le Dashboard ThingsBoard
Afin de mettre en place le dashboard ThingsBoard, vous devez lire attentivement les instructions données sur Getting Started with ThingsBoard. Ce guide décrit avec détails toutes les étapes vous permettant de mettre en place un dashboard, permettant d’afficher les données reçues de votre station météo en utilisant un des nombreux widgets mis à disposition. Vous devez préparer un dashboard permettant de visualiser (sous une forme de votre choix) les données de température, d’humidité et de pression atmosphérique provenant de votre station.
Pour l’authentification, vous devez utiliser l’authentification basée sur les Username et Password, comme expliqué sur Authentication based on Username and Password. Afin de créer un nom d’utilisateur et un mot de passe pour un device, vous devez créer les credentials pour le device configuré, comme illustré dans l’image ci-dessous (cliquez sur le bouton Manage Credentials, choisissez MQTT Basic comme type de credentials et choisissez un Username et Password).
A la fin de cette étape, vous devez être à même de publier des données vers le broker MQTT à l’aide d’un client comme mosquitto_pub et comme décrit sous MQTT Linux ou MacOS , MQTT Windows ou une autre méthode de votre choix. La ligne de commande à utiliser devrait être similaire à :
mosquitto_pub -d -q 1 -h "demo.thingsboard.io" -p "1883" -t "v1/devices/me/telemetry" -u "YOUR_CLIENT_USERNAME" -P "YOUR_CLIENT_PASSWORD" -m {"temperature":25}
Réalisation
La réalisation du client MQTT doit se faire en respectant l’architecture des classes affichées dans le diagramme de classe ci-dessous :
Dans le diagramme ci-dessus, les classes CoAPPublisher
et HTTPPublisher
ne
sont pas détaillées car elles ne sont pas réalisées dans ce travail. Comme vous
pouvez le voir dans le diagramme, la réalisation se base sur une interface
IPublisher
et une classe de base PublisherBase
. Afin de débuter la
réalisation, vous devez tout d’abord créer le squelette des classes de la
manière suivante :
- Vous devez définir l’interface
IPublisher
dans un fichier “ipublisher.hpp”. En C++, le mot cléinterface
n’existe pas et une interface est en fait une abstract class dont toutes les méthodes sont abstraites (pure virtual) et qui ne contient en principe pas d’attribut. L’interfaceIPublisher
contient les trois méthodes présentées dans le diagramme de classe. La méthodeconnect()
reçoit les paramètres nécessaires à la connexion et la méthodepublishMeasurement()
reçoit les mesures des capteurs. - La classe
PublisherBase
est la réalisation de base de toutes les classes Publisher. La classe hérite deIPublisher
et est également une classe abstraite. Cette classe fournit la fonctionnalité de connexion au réseau pour les classes Publisher, à l’aide de la méthodeconnectNetworkInterface()
. La réalisation de la classePublisherBase
à insérer dans votre fichierpublisher_base.cpp
est donnée ci-dessous :publisher_base.cpp// Copyright 2022 Haute école d'ingénierie et d'architecture de Fribourg // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /**************************************************************************** * @file publisher_base.cpp * @author Serge Ayer <serge.ayer@hefr.ch> * * @brief Base class for MQTT and CoAP publishers * * @date 2022-01-01 * @version 0.1.0 ***************************************************************************/ #include "publisher_base.hpp" #include "ESP32Interface.h" #include "mbed_trace.h" #if defined(MBED_CONF_MBED_TRACE_ENABLE) #define TRACE_GROUP "PublisherBase" #endif // MBED_CONF_MBED_TRACE_ENABLE PublisherBase::PublisherBase() { static ESP32Interface esp32; networkInterface_ = &esp32; MBED_ASSERT(networkInterface_ != nullptr); } PublisherBase::~PublisherBase() { if (networkInterface_ != nullptr && networkInterface_->get_connection_status() != NSAPI_STATUS_DISCONNECTED) { networkInterface_->disconnect(); } } nsapi_error_t PublisherBase::connectNetworkInterface() { tr_debug("Connecting to the network..."); // check if we're already connected if (networkInterface_->get_connection_status() == NSAPI_STATUS_GLOBAL_UP) { tr_debug("Already connected"); return NSAPI_ERROR_OK; } // set the default parameters (such as wifi pwd or sim pin) before connecting networkInterface_->set_default_parameters(); nsapi_error_t rc = NSAPI_ERROR_OK; for (uint8_t retry = 0; retry <= MAX_CONNECTION_RETRY_COUNT; retry++) { rc = networkInterface_->connect(); if (rc == NSAPI_ERROR_OK) { tr_info("Connection Established."); break; } else if (rc == NSAPI_ERROR_AUTH_FAILURE) { tr_info("Authentication Failure."); return rc; } else { tr_info("Couldn't connect: %d, will retry.", rc); } } printNetworkInfo(); return rc; } void PublisherBase::printNetworkInfo() const { // print the network info SocketAddress a; networkInterface_->get_ip_address(&a); tr_info("IP address: %s", a.get_ip_address() ? a.get_ip_address() : "None"); networkInterface_->get_netmask(&a); tr_info("Netmask: %s", a.get_ip_address() ? a.get_ip_address() : "None"); networkInterface_->get_gateway(&a); tr_info("Gateway: %s", a.get_ip_address() ? a.get_ip_address() : "None"); }
-
La classe
MQTTPublisher
représente la classe concrète que l’application pourra utiliser afin de publier des données vers le broker. La classe réalise toutes les méthodes abstraites de l’interfaceIPublisher
. Après avoir créé une instance de cette classe, le client pourra ainsi se connecter (une seule fois) au broker MQTT puis publier les mesures à intervalle régulier. La classe réalise également une méthode permettant de vérifier que le client MQTT est bien connecté. -
La méthode
connect()
de la classeMQTTPublisher
est donnée ci-dessous :mqtt_publisher.cppnsapi_error_t MQTTPublisher::connect(const char* serverHostname, const char* userName, const char* keyOrPwd) { // call the base class for connecting to the network nsapi_error_t retCode = PublisherBase::connectNetworkInterface(); if (retCode != NSAPI_ERROR_OK) { tr_error("Could not connect to network: %d", retCode); return retCode; } static const char* SOCKET_STRING = "TCPSocket"; tr_info("Opening %s", SOCKET_STRING); retCode = tcpSocket_.open(networkInterface_); if (retCode != NSAPI_ERROR_OK) { tr_error("%s::open() failed, code: %d", SOCKET_STRING, retCode); return retCode; } static constexpr int SOCKET_TIMEOUT = 15000; tcpSocket_.set_timeout(SOCKET_TIMEOUT); SocketAddress socketAddress; tr_debug("Resolving hostname %s", serverHostname); retCode = networkInterface_->gethostbyname(serverHostname, &socketAddress); if (retCode != NSAPI_ERROR_OK) { tr_error("gethostbyname couldn't resolve remote host: %s, code: %d", serverHostname, retCode); return retCode; } tr_debug("Hostname %s resolved to %s", serverHostname, socketAddress.get_ip_address()); static constexpr int serverPort = 1883; socketAddress.set_port(serverPort); tr_debug("%s: trying to connect socket with address: %s on port %d", SOCKET_STRING, socketAddress.get_ip_address(), socketAddress.get_port()); retCode = tcpSocket_.connect(socketAddress); if (retCode < 0) { tr_error("%s: connect() failed with code: %d", SOCKET_STRING, retCode); return retCode; } else { tr_debug("%s: connected with %s server", SOCKET_STRING, serverHostname); } // options for MQTT MQTTPacket_connectData options = MQTTPacket_connectData_initializer; // do not initialize the clientID options.username.cstring = (char*)userName; options.password.cstring = (char*)keyOrPwd; // store user name and key userName_ = userName; key_ = keyOrPwd; // connect to the broker tr_debug("Trying to connect to MQTT broker %s with user name %s", serverHostname, userName); retCode = mqttClient_.connect(options); if (retCode != NSAPI_ERROR_OK) { tr_error("connect : Failed to connect MQTT client, code : %x", retCode); return retCode; } return retCode; }
-
Vous devez réaliser la méthode
publishMeasurements()
en suivant les spécifications du broker ThingsBoard relatives au topic et au payload du message et selon les tests effectués lors de la mise en œuvre du dashboard. Pour la réalisation, vous devez utiliser l’instance deMQTTClient
définie dans la classeMQTTPublisher
et la méthodepublish()
de la classeMQTTClient
. -
Vous devez également réaliser la méthode
isConnected()
qui valide que le client MQTT est bien connecté au broker.
La dernière étape de la réalisation consiste à modifier le programme principal
pour créer une instance de MQTTPublisher
, connecter cette instance au broker
et publier les mesures à intervalle régulier. Vous devez limiter la publication
des mesures à une fréquence maximale de 1 publication par minute. Votre application doit se
comporter correctement lorsque la publication au broker échoue : elle doit
afficher un message et continuer à fonctionner. Vous pouvez limiter la
réalisation à une seule tentative de connexion.
Points d’accès dans la salle de classe
Afin d’accéder au point d’accès mis à disposition dans la salle de classe, vous pouvez utiliser les informations suivantes dans votre fichier “mbed_app.json”:
- “nsapi.default-wifi-ssid” : “"Notyour2.4G"“,
- “nsapi.default-wifi-password” : “"Fribourg"“,
Note
Pour prendre en main la partie WiFi, vous pouvez commencer par afficher la liste des points d’accès visibles par votre cible. Pour ce faire, vous pouvez vous inspirer du code suivant :
// Copyright 2022 Haute école d'ingénierie et d'architecture de Fribourg
// SPDX-License-Identifier: Apache-2.0
/****************************************************************************
* @author Serge Ayer <serge.ayer@hefr.ch>
*
* @brief Network related utility functions
*
* @date 2022-01-01
* @version 0.1.0
***************************************************************************/
#include "nsapi.h"
#include "ESP32Interface.h"
static const char* get_security_string(nsapi_security_t sec)
{
switch (sec) {
case NSAPI_SECURITY_NONE:
return "None";
case NSAPI_SECURITY_WEP:
return "WEP";
case NSAPI_SECURITY_WPA:
return "WPA";
case NSAPI_SECURITY_WPA2:
return "WPA2";
case NSAPI_SECURITY_WPA_WPA2:
return "WPA/WPA2";
case NSAPI_SECURITY_UNKNOWN:
default:
return "Unknown";
}
}
void wifiScan()
{
printf("Starting wifi scan\n");
static ESP32Interface esp32;
WiFiInterface* wifi = &esp32;
MBED_ASSERT(wifi != nullptr);
static constexpr size_t MAX_NUMBER_OF_ACCESS_POINTS = 10;
WiFiAccessPoint ap[MAX_NUMBER_OF_ACCESS_POINTS];
// scan call returns number of access points found
int result = wifi->scan(ap, MAX_NUMBER_OF_ACCESS_POINTS);
if (result <= 0) {
printf("WiFiInterface::scan() failed with return value: %d\r\n", result);
return;
}
printf("%d networks available:\r\n", result);
for (int i = 0; i < result; i++) {
printf(
"Network: %s secured: %s BSSID: %hhX:%hhX:%hhX:%hhx:%hhx:%hhx RSSI: "
"%hhd Ch: %hhd\r\n",
ap[i].get_ssid(),
get_security_string(ap[i].get_security()),
ap[i].get_bssid()[0],
ap[i].get_bssid()[1],
ap[i].get_bssid()[2],
ap[i].get_bssid()[3],
ap[i].get_bssid()[4],
ap[i].get_bssid()[5],
ap[i].get_rssi(),
ap[i].get_channel());
}
printf("\r\n");
}
Questions / Requis
-
Expliquez le fonctionnement de la méthode
connect()
de la classeMQTTPublisher
en détail. En particulier, détaillez les erreurs qui peuvent survenir lors de la connexion et les composants logiciels utilisés afin de réaliser une connexion vers un broker MQTT. -
Vous devez publier des données environnementales vers le broker Thingsboard pendant au moins 6 heures et ajoutez le lien public à votre dashboard dans le rapport. Le lien vers le dashboard public doit fonctionner sans s’identifier sur la plateforme Thingsboard.
Sécurité (Bonus)
Votre projet a besoin de plusieurs données sensibles telles que le mot de passe du WiFi ou le mot de passe pour le broker MQTT. Ce n’est pas une bonne idée de mettre ces données sensibles dans le code ni dans tout autre fichier géré par git.
Décrivez et implémentez une technique qui permet de compiler le code sans divulguer d’information sensible dans git.
Voici quelques liens utiles:
Tests et validations
Comme pour le TP précédent, configurez l’analyse statique de code de manière plus stricte.
À ne pas oublier
- Choisissez de bons noms pour les classes, les méthodes et les variables.
- Implémentez les bibliothèques avec un haut niveau d’abstraction pour pouvoir réutiliser les méthodes dans d’autres projets.
- Faites des “git commit” régulièrement avec de bons commentaires.
- Utilisez des assertions dans votre code pour le documenter et le rendre plus robuste.
- Validez tous les codes de retour des méthodes/fonctions utilisées.
Journal de travail
- Rédigez un rapport (journal de travail) avec les indications suivantes :
- Une page de titre avec au minimum :
- le nom et le logo officiel de l’école
- le nom du cours : Module Acquisition et traitement de données : Internet des Objets
- le titre de votre document : Travail Pratique 4 : Station météo MQTT
- le numéro de votre groupe
- les noms des auteurs (vous) avec la classe dans laquelle vous êtes
- la date à laquelle vous avez terminé le rapport
- éventuellement la version du rapport
- Une introduction pour poser le contexte
- Un résumé des notions que vous avez apprises pendant ce TP en précisant si c’est
- non acquis
- acquis, mais encore à exercer
- parfaitement acquis
- Un résumé des points qui vous semblent importants et que vous devez retenir
- Les réponses aux questions.
- Le code source bien formaté et avec du “syntax highlighting” de votre code source.
- Une conclusion par laquelle vous donnez vos impressions sur le TP, ce que vous avez aimé, ce que vous avez moins aimé, et éventuellement des suggestions pour des changements. Indiquez également le nombre d’heures que vous avez passées, par personne, en dehors des heures de TP en classe.
Important
Déposez votre rapport dans le devoir Teams correspondant avec le nom report04.pdf
.