TP04 : Station météo sur Internet

Dans le TP précédent, nous avons réalisé une station météo connectée qui transmet des mesures à un écran par 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ù.
Vous utiliserez encore une fois deux cibles :
- La première cible fait exactement la même chose que pour le TP précédent. Elle se charge de prendre des mesures environnementales et de les diffuser (broadcast) avec le BLE;
- La deuxième cible reçoit les mesures diffusées par BLE, les affiche sur l’écran, et les envoie 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 dans le dépôt git.
Les livrables sont :
- un projet git (tp03) dans votre groupe sur gitlab.forge.hefr.ch avec le code du TP ;
- un journal de travail déposé sur gitlab.
Temps accordé : 4 périodes de 45 minutes en classe + travail personnel à la maison
Délai
Le TP doit être rendu au plus tard 6 jours après la séance en classe à 23:59.
Matériel
Pour ce TP, vous avez besoin du WiFi BLE click :
Ce click-board utilise l’interface UART et vous pouvez le mettre sur le slot 1 ou sur le slot 2.
Configuration de Mbed OS et de PlatformIO
Pour utiliser le module BLE, 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 6 7 | |
Dossier mbedignore
En plus de BLE, nous devons ré-activer la stack IP et le WiFi de Mbed OS pour ce TP :
Éditez le fichier mbedignore/connectivity/.mbedignore et supprimez les lignes suivantes :
drivers/ble/*
drivers/wifi/*
FEATURE_BLE/*
libraries/*
lwipstack/*
mbedtls/*
netsocket/*
Il doit vous rester les lignes suivantes :
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 :
Modifiez le fichier mbed_app.json pour l’utilisation du Wifi et
configurez les “secrets” de votre connexion :
"DISCO_HEIAFR": {
...
"target.network-default-interface-type" : "WIFI",
"nsapi.default-wifi-ssid" : "\"MySSID\"",
"nsapi.default-wifi-password" : "\"MyPWD\"",
"nsapi.default-wifi-security": "WPA_WPA2"
}
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 pourrez utiliser le broker Adafruit.io ou 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 Adafruit
Afin de simplifier la réalisation, nous utilisons le dashboard offert par Adafruit. Rendez-vous sur io.adafruit.com et cliquez sur “Get Started for Free”1 en haut à droite.
Créez un nouveau dashboard en cliquant sur “+ New Dashboard”
Dans votre nouveau dashboard, ajoutez un bloc, par exemple une gauge, pour représenter l’humidité :
Configurez le bloc pour l’humidité :
Créez ensuite un nouveau feed pour l’humidité :
Et connectez ce feed à votre bloc
Vous pouvez obtenir les détails d’un feed en cliquant sur “feed info”
Le feed peut recevoir des données par une connextion Web ou MQTT. Cette page vous indique le topic MQTT correspondant à votre feed.
Obtenez encode une clé en cliquant sur “My Key”
Testez maintenant l’envoi d’une valeur sur le feed que vous avez créé à l’aide de MQTT Explorer ou en ligne de commande :
mosquitto_pub \
-h io.adafruit.com \
-p 1883 \
-u <VOTRE USERNAME (par ex. supcik)> \
-P <VOTRE CLE> \
-t <VOTRE FEED (par ex. supcik/feeds/humidity)> \
-m 42
La gauge de votre dashboard devrait maintenant indiquer 42 :
Complétez le dashboard avec un bloc pour la température et un bloc pour la pression atmosphérique.
Notez que vous pouvez très bien configurer deux blocs avec le même feed. Expérimentez avec une mesure donnée (par exemple la température sur une gauge et sur un graph).
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, nous utiliserons 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. Les données publiées ainsi doivent être visibles dans votre dashboard. Il est important que la publication vers votre dashboard soit validée avant de commencer la réalisation du client MQTT pour votre cible.
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
IPublisherdans un fichieripublisher.hpp. En C++, le mot cléinterfacen’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’interfaceIPublishercontient 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éthodepublishMeasurementsreçoit les mesures des capteurs. - La classe
PublisherBaseest la réalisation de base de toutes les classes Publisher. La classe hérite deIPublisheret 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.cppest 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(); } delete networkInterface_; networkInterface_ = nullptr; } 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
MQTTPublisherrepré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 classeMQTTPublisherest 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; // options.MQTTVersion = 3; // 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 Adafruit ou ThingsBoard relatives au topic et au payload du message et selon les tests effectués lors de la mise en œuvre du dashboard. - 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 minute. Votre application doit se
comporter correctement lorsque la publication au broker échoue : elle doit
afficher un message et continue à fonctionner. Vous pouvez limiter la
réalisation à une seule tentative de connexion.
Note
Pour prendre en main la partie WiFi, vous pouvez commencer par afficher la liste des points d’accès visibles par votre cible. 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"
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()
{
NetworkInterface* pNetworkInterface =
NetworkInterface::get_default_instance();
MBED_ASSERT(pNetworkInterface != nullptr);
MBED_ASSERT(pNetworkInterface->wifiInterface() != nullptr);
WiFiInterface* wifi = pNetworkInterface->wifiInterface();
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 classeMQTTPublisheren 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. -
Publiez des données environnementales vers le broker pendant au moins 6 heures et documentez l’accès à votre dashboard dans le rapport. Le lecteur du rapport doit pouvoir accéder au dashboard et vérifier les données publiées.
Tests et validations
Comme pour le TP précédent, configurez l’analyse statique de code de manière plus stricte.
À ne pas oublier
Gardez toujours en têtes les bonnes pratiques ainsi que les dix commandements du bon programmeur.
- 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.
- Configurez le CI/CD de gitlab et testez automatiquement le plus de choses possibles.
- Implémentez des tests unitaires.
- Utilisez des assertions dans votre code pour le documenter et le rendre plus robuste.
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 : Informatique Embarquée
- le titre de votre document : Travail Pratique 3 : Station météo connectée
- 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 un dossier /docs de votre dépôt git (tp04) avec le nom report04.pdf (le chemin complet vers votre
rapport est donc /docs/report04.pdf)
-
Cliquez sur “Sign In” si vous avez déjà un compte. ↩