Application graphique

On vous demande de créer une application Qt qui permettra d’afficher de manière graphique l’ensemble des relevés de températures disponibles sur la Raspberry Pi.

Les objectifs de cette activité sont de montrer :

  1. que le codage réalisé jusqu’à présent peut très bien être utilisé dans une application graphique et ce, sans avoir à fournir de gros efforts.

    Pour cela, il faut veiller lors de la conception à ce que les classes C++ ne dépendent pas du type d’interface homme machine utilisée (Cli ou Gui).

  2. qu’il est possible de spécialiser le comportement de la classe de base (→ TempSensor) en intégrant des fonctionnalités propres à Qt. À cette fin, on vous propose de récupérer la température courante relevée à l’Isle sur la Sorgue via une API Rest proposée par le site OpenWeather link.

    Ceci sera ainsi l’occasion de voir comment on peut décoder un document au format JSON avec Qt et mettre en place du multithreading pour les opérations coûteuses en temps (→ accès au réseau).

L’IHM proposée pour l’application finale figure ci-dessous :

gui temp monitor

🖮 Travail n° 1 IHM v1 : Capteurs de la Raspberry Pi

On vous demande dans une 1er temps de créer l’IHM permettant de visualiser les relevés de température depuis les 3 capteurs de la Raspberry Pi :

  • Sht20 → classe Sht20

  • Tc72 → classe Tc72

  • Coeur → classe CoreTemp

    1. Créer un projet Qt sur la Raspberry Pi

      Pour développer avec Qt Creator sur la Raspberry Pi on vous suggère d’utiliser VNC viewer link ou XMing link sur votre PC pour accéder à l’interface graphique plutôt que de connecter écran, clavier et souris à la Raspberry Pi.

    2. Concevoir l’IHM

    3. Ajouter au projet les fichiers de toutes les classes C++ codée dans les activités précédentes :

      • Bcm2835Wrapperbcm2835wrapper.cpp, bcm2835wrapper.h

      • TempSensortempsensor.cpp, tempsensor.h

      • Sht20sht20.cpp, sht20.h

      • Tc72tc72.cpp, tc72.h

      • CoreTempcoretemp.cpp, coretemp.h

    4. Configurer le projet pour utiliser la librairie /usr/local/libbcm2835.a lors de l’édition de lien.

      → bouton droit sur le nom du projet puis Add Library…​  System Library  Linux Platform & /usr/lib/local/libbcm2835.a

    5. Mettre en œuvre un QTimer pour lire la température sur l’ensemble des capteurs de température de la Raspberry Pi et l’afficher sur l’IHM toutes les 2.5s.

    6. Vérifier le bon fonctionnement de l’application

      Comme l’application utilise la librairie bcm2835, elle doit être exécutée avec sudo.

      Ceci peut être obtenu dans Qtcreator en suivant la procédure suivante :

      1. Aller dans le menu Tools  Options  Environment

      2. Dans l’onglet “System”, il y a un champ “Terminal Option”.

        La valeur par défaut est /usr/bin/xterm -e. La remplacer par /usr/bin/xterm -e sudo

      3. Appliquer la modification en enfonçant le bouton “Apply” et/ou “OK”

      4. Sélectionner l’option “Run” dans la configuration du projet accessible depuis le bandeau latéral.

        Dans la boîte composite nommée “Run”, cocher la case “Run in terminal”

🖮 Travail n° 2 Ihvm v2 : Température extérieure

On vous demande maintenant de compléter l’IHM du travail précédent avec une zone qui affiche la température extérieure courante relevée à l’Isle sur la Sorgue.

Vous vous appuierez sur l’API Rest OpenWeather link pour récupérer cette température.

Pour pouvoir utiliser, cette API il vous faut au préalable créer un compte afin d’obtenir une clé d’API qu’il faudra fournir lors de chaque requête.

  1. Créer dans le projet une nouvelle classe C++ OutdoorTemp qui dérive de TempSensor

  2. Coder la classe OutdoorTemp pour que la méthode readTemperature() de cette classe renvoie, via l’API OpenWeather, la température relevée à l’Isle sur la Sorgue.

    Se baser sur l’exemple ci-dessous pour savoir comment faire une requête sur une API Web depuis Qt et comment décoder du JSON.

    Code source d’exemple

    Le code suivant permet, via l’API “Adresse” link, d’afficher tous les lieu-dits dont le nom contient “bidon”.

    main.pro
    QT -= gui
    QT += network (1)
    
    CONFIG += c++11 console
    CONFIG -= app_bundle
    
    # You can make your code fail to compile if it uses deprecated APIs.
    # In order to do so, uncomment the following line.
    #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
    
    SOURCES += \
            bidonlocator.cpp \
            main.cpp
    
    # Default rules for deployment.
    qnx: target.path = /tmp/$${TARGET}/bin
    else: unix:!android: target.path = /opt/$${TARGET}/bin
    !isEmpty(target.path): INSTALLS += target
    
    HEADERS += \
        bidonlocator.h
    1 Ajouter manuellement cette ligne dans le fichier .pro de façon à pouvoir utiliser la librairie Qt qui permet l’accès au réseau
    main.cpp
    #include <QObject>
    #include <QCoreApplication>
    
    #include "bidonlocator.h"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        BidonLocator bl;
        QObject::connect(&bl, &BidonLocator::finished, &a, &QCoreApplication::quit);
    
        bl.search();
    
        return a.exec();
    }
    bidonlocator.h
    /**
     * Classe de démonstration pour accéder à une API Rest en https et décoder la réponse au format JSON.
     *
     * L'API utilisée est : https://adresse.data.gouv.fr/api-doc/adresse
     * Elle permet de rechercher des adresses en France.
     * Ici, on l'utilise pour récupérer tous les lieu-dit dont le nom contient "bidon".
     *
     * Voir https://stackoverflow.com/questions/53805704/tls-initialization-failed-on-get-request pour
     * savoir comment installer les librairies supplémentaires requises pour initialiser une connexion sécurisée
     * en https.
     */
    #ifndef BIDONLOCATOR_H
    #define BIDONLOCATOR_H
    
    #include <QObject>
    #include <QtNetwork/QNetworkAccessManager>
    #include <QtNetwork/QNetworkRequest>
    #include <QtNetwork/QNetworkReply>
    
    class BidonLocator : public QObject
    {
        Q_OBJECT
    public:
        explicit BidonLocator(QObject *parent = nullptr);
        void search();
    
    private:
        QNetworkAccessManager _nam;
        QNetworkRequest _nr;
        QNetworkReply * _nrep;
    
    private slots:
        void onReplyFinished(QNetworkReply *reply);
    
    signals:
        void finished();
    };
    
    #endif // BIDONLOCATOR_H
    bidonlocator.cpp
    #include <QJsonDocument>
    #include <QJsonArray>
    #include <QJsonObject>
    
    #include <QDebug>
    
    #include "bidonlocator.h"
    
    BidonLocator::BidonLocator(QObject *parent) : QObject(parent)
    {
        connect(&_nam, &QNetworkAccessManager::finished, this, &BidonLocator::onReplyFinished);
        //    connect(&_nam, &QNetworkAccessManager::encrypted, [](QNetworkReply *reply){
        //        qDebug() << reply->sslConfiguration().peerCertificateChain();
        //    });
    }
    
    void BidonLocator::search()
    {
        // Connexion à l'hôte
        // Utiliser méthode connectToHost() si site http plutôt que https
        _nam.connectToHostEncrypted("https://api-adresse.data.gouv.fr");
    
        // Elaboration de la requête HTTP pour rechercher les
        // lieu-dits(-> type=locality) contenant le mot "bidon" (-> q=bidon)
        _nr.setUrl(QUrl("https://api-adresse.data.gouv.fr/search/?q=bidon&type=locality"));
        _nr.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json"));
    
        // Exécution de la requête
        _nrep = _nam.get(_nr);
    }
    
    void BidonLocator::onReplyFinished(QNetworkReply *reply)
    {
        // Lecture de la réponse à la requête.
        QByteArray response = reply->readAll();
    
        // valeur de la réponse :
        /*
    {
        "type": "FeatureCollection"
        , "version": "draft"
        , "features": [
            {"type": "Feature", "geometry": {"type": "Point", "coordinates": [5.479634, 45.30463]}, "properties": {"label": "Plan de Bidon 38210 Tullins", "score": 0.6751872727272726, "type": "locality", "importance": 0.42706, "id": "38517_B145", "name": "Plan de Bidon", "postcode": "38210", "citycode": "38517", "x": 894299.24, "y": 6470302.41, "city": "Tullins", "context": "38, Is\u00e8re, Auvergne-Rh\u00f4ne-Alpes"}}
            , {"type": "Feature", "geometry": {"type": "Point", "coordinates": [4.795415, 45.798143]}, "properties": {"label": "Lieudit le Bidon 69410 Champagne-au-Mont-d'Or", "score": 0.6737554545454545, "type": "locality", "importance": 0.41131, "id": "69040_334umi", "name": "Lieudit le Bidon", "postcode": "69410", "citycode": "69040", "x": 839450.14, "y": 6523643, "city": "Champagne-au-Mont-d'Or", "context": "69, Rh\u00f4ne, Auvergne-Rh\u00f4ne-Alpes"}}
            , {"type": "Feature", "geometry": {"type": "Point", "coordinates": [4.795415, 45.798143]}, "properties": {"label": "Lieudit le Bidon 69410 Champagne-au-Mont-d'Or", "score": 0.6737554545454545, "type": "locality", "importance": 0.41131, "id": "69040_qdol5l", "name": "Lieudit le Bidon", "postcode": "69410", "citycode": "69040", "x": 839450.14, "y": 6523643, "city": "Champagne-au-Mont-d'Or", "context": "69, Rh\u00f4ne, Auvergne-Rh\u00f4ne-Alpes"}}
            , {"type": "Feature", "geometry": {"type": "Point", "coordinates": [2.57978, 46.600328]}, "properties": {"label": "Le Bois Bidon (Meaulne) 03360 Meaulne-Vitray", "score": 0.6653999999999999, "type": "locality", "importance": 0.3194, "id": "03168_B005", "name": "Le Bois Bidon (Meaulne)", "postcode": "03360", "citycode": "03168", "oldcitycode": "03168", "x": 667832.88, "y": 6611227.68, "city": "Meaulne-Vitray", "oldcity": "Meaulne", "context": "03, Allier, Auvergne-Rh\u00f4ne-Alpes"}}
            , {"type": "Feature", "geometry": {"type": "Point", "coordinates": [4.775941, 46.459771]}, "properties": {"label": "Les Crays sur Bidon 71260 Saint-Gengoux-de-Sciss\u00e9", "score": 0.6636799999999998, "type": "locality", "importance": 0.30048, "id": "71416_B067", "name": "Les Crays sur Bidon", "postcode": "71260", "citycode": "71416", "x": 836285.47, "y": 6597065.02, "city": "Saint-Gengoux-de-Sciss\u00e9", "context": "71, Sa\u00f4ne-et-Loire, Bourgogne-Franche-Comt\u00e9"}}
        ]
        , "attribution": "BAN"
        , "licence": "ETALAB-2.0"
        , "query": "bidon"
        , "filters": {"type": "locality"}
        , "limit": 5
    }
    
    => Objet JS dont l'attribut "features" est lui-même un tableau d'objets JS constitués
    d'attributs de type basique (-> "type") et de type objet (-> "geometry") ...
         */
    
        if ( !response.isEmpty() ) {
            // Analyse de la réponse au format JSON
            QJsonDocument jdoc = QJsonDocument::fromJson(response);
    
            // Extraction de l'objet de 1er niveau
            QJsonObject jobj = jdoc.object();
    
            // Extraction de l'objet "filters"
            QJsonObject filters = jobj["filters"].toObject();
    
            // lecture de la clé "type" de l'objet "filters" et conversion en chaîne pour l'affichage
            QString filterType = filters["type"].toString();
            qDebug() << "Filtre de la requête : " << filterType;
    
            QJsonArray jarr = jobj["features"].toArray();
    
            foreach (const QJsonValue & jval, jarr) {
                QJsonObject properties = jval["properties"].toObject();
                QString label = properties["label"].toString();
                qDebug() << "Lieu-dit : " << label;
            }
    
            // Sortie de l'application
            emit finished();
        }
    
        // Libération de la mémoire
        reply->deleteLater();
    }
    Résultat exécution
    Filtre de la requête :  "locality"
    Lieu-dit :  "Plan de Bidon 38210 Tullins"
    Lieu-dit :  "Lieudit le Bidon 69410 Champagne-au-Mont-d'Or"
    Lieu-dit :  "Le Bois Bidon (Meaulne) 03360 Meaulne-Vitray"
    Lieu-dit :  "Les Crays sur Bidon 71260 Saint-Gengoux-de-Scissé"

🞄  🞄  🞄