Yocto/OE et Qt MQTT : le duo gagnant

Magazine
Marque
GNU/Linux Magazine
Numéro
223
Mois de parution
février 2019
Spécialité(s)


Résumé

Déjà évoqué dans diverses publications [1], le protocole Machine-2-Machine MQTT, se veut être le standard de communication pour les objets connectés (comprendre IoT ici !). En effet, de par sa légèreté et son efficacité, il en fait un protocole très prisé pour la gestion de la télémétrie en environnement embarqué.


Body

 

L’article qui suit sera, pour les auteurs, l’occasion non pas de présenter MQTT dans les moindres détails, mais plutôt d’introduire et utiliser le nouveau module Qt MQTT sur notre plateforme i.MX7. Nous réaliserons ainsi un petit démonstrateur s’articulant autour de ce module en intégrant une partie graphique pour la visualisation des données avec le module Qt Charts. Let ‘s go !

1. MQTT, bref rappel

Afin de mieux comprendre le contexte mis en place dans notre étude, les auteurs se permettront tout de même de faire une rapide présentation du protocole MQTT et de ses spécificités.

1.1 Il était une fois …

Contrairement à ce que l’on pourrait penser, MQTT (pour MQ Telemetry Transport) ne doit pas sa popularité au monde de l’IoT et des objets connectés. En effet, créé en 1999 par Andy Stanford-Clark d’IBM et Arien Nipper d’Arcom (Eurotech depuis), ce protocole dédié au monde du M2M (Machine-to-Machine), avait pour but d’être un moyen de communication simple, avec une faible consommation en énergie et léger en termes de bande passante (il faut se remettre dans le contexte de l’époque où les équipements devaient être connectés au satellite par exemple).

Basé sur un mécanisme « publication/abonnement » ou « publisher/subscriber » pour les anglophones, celui-ci est situé au niveau 5 – 7 du modèle OSI, le protocole MQTT repose ainsi sur la couche de transport TCP/IP et il est utilisé sur le port 1883 ou 8883 pour les communications chiffrées SSL/TLS lors des différents échanges de messages.

1.2 Le Principe

MQTT permet aux appareils connectés de partager des informations (données physiques en provenance des périphériques) sur un sujet donné (appelé Topic) à un serveur de messages (appelé Broker). Le Broker renvoie ensuite les différentes informations vers les clients qui se sont préalablement abonnés aux différents Topics.

Pour l’utilisateur, les Topics sont traités en hiérarchie en utilisant le « / » comme séparateur. Ceci a pour effet d’offrir la possibilité d’une gestion très fine des différents thèmes (différentes pièces d’une maison par exemple). L’agencement rappelle un peu la gestion d’un système de fichiers. Ainsi, les clients peuvent s’abonner à un niveau spécifique de la hiérarchie d’un sujet ou à plusieurs niveaux en utilisant des « wildcards » spécifiques que nous allons découvrir par la suite. L’image suivante (figure 1) permet de schématiser le concept du protocole MQTT :

principe

Fig. 1 : Principe de fonctionnement.

1.3 Messages

Un client MQTT peut publier des messages dès qu’il se connecte à un Broker. Chaque message doit contenir un Topic que le Broker peut utiliser pour transmettre le message aux clients abonnés. Typiquement, chaque message a une charge utile (payload) qui contient les données à transmettre sous forme d’octet. C’est donc au client expéditeur de déterminer la structure de la charge utile ; on peut retrouver :

  • des données binaires,
  • des données texte,
  • des données au format XML,
  • des données au format JSON.

Comme brièvement abordé en début de section, un message PUBLISH comporte plusieurs attributs (voir figure 2) :

  • Topic : nous avons, dans cet exemple, une hiérarchie de plusieurs Topics accessibles chacun à leur niveau par d’éventuels clients destinataires (maison/chambre/temperature). Par exemple, afin de récupérer la donnée du capteur chambre de la maison, il faudra ainsi s’inscrire sur le Topic temperature.

    Une notion vraiment intéressante dans le protocole MQTT, concerne l’utilisation des wildcards. En effet, il est aussi possible de ne pas spécifier de Topic de façon explicite lors d’une souscription, mais plutôt d’utiliser ce qu’on appellera des caractères génériques. MQTT en possède 2 (+ et #) :

  • le + pour la gestion à 1 niveau de hiérarchie. Par exemple, il est possible de récupérer l’ensemble des températures de la maison de la manière suivante : maison/+/temperature
  • le #, quant à lui, permet de gérer plusieurs niveaux de hiérarchie. Imaginons ici, vouloir récupérer l’ensemble des données de la maison (température, humidité…), il nous faudrait par exemple souscrire de la manière suivante : maison/#
  • Charge utile : le contenu du message, ici une simple donnée de température.
  • QoS : la qualité de service, que nous expliquerons juste après.
  • Retain : permet de définir si le message doit être enregistré sur le Broker comme étant la dernière valeur valide. Celle-ci sera ainsi envoyée de façon automatique aux clients lors d’une connexion au Broker.
mqtt

Fig. 2 : Message MQTT « PUBLISH ».

1.4 Sécurité

On ne parle pas protocole sans aborder la question de la sécurité, MQTT permet donc d’intégrer quelques notions à ce sujet, on retrouvera par exemple les différentes possibilités suivantes :

  • le transport de données sécurisé en SSL/TLS ;
  • l’authentification des clients par le biais de certificats SSL/TLS ;
  • une gestion par mot de passe et login.

1.5 QoS ?

La Qualité de Service (Quality of Service) est un accord entre l’expéditeur d’un message et le destinataire d’un message qui définit la garantie de livraison pour un message spécifique. On retrouvera ainsi 3 niveaux :

  • QoS 0 : Au plus une fois (At most once), c’est la méthode la plus rapide et elle ne nécessite qu’un seul message. C’est aussi le mode de transfert le moins fiable. Le message n’est pas stocké du côté de l’expéditeur et n’est pas acquitté.
  • QoS 1 : Au moins une fois (At least once), ce niveau garantit que le message sera livré au moins une fois, mais peut être livré plus d'une fois.
  • QoS 2 : Exactement une fois (Exactly once), le message est toujours délivré exactement une fois. C’est la méthode la plus lente, car elle nécessite 4 messages (PUBLISH, PUBREC, PUBREL, PUBCOMP).

On notera que la qualité de service doit dépendre entièrement de la donnée, de sa sensibilité et de sa criticité.

1.6 Le module Qt MQTT

Présent depuis la version 5.10 de Qt, le module qtmqtt (https://github.com/qt/qtmqtt) propose une implémentation du protocole MQTT (dans les versions 3.1, 3.1.1 et 5 pour Qt 5.12), permettant ainsi aux habitués et utilisateurs du framework déjà riche en fonctionnalités, de pouvoir implémenter un nouveau protocole, et qui plus est, un standard dans le monde de l’(I)IoT.

Qt MQTT est disponible sous licence GPLv3 et/ou sous licence commerciale. C’est aussi un module à part entière de Qt for Automation (incluant aussi Qt OPCUA, Qt KNX, etc.).

C’est donc ce module que nous découvrirons dans la deuxième partie de l’article. Soyez patient !

2. Préparation de l’environnement

Afin d’intégrer au mieux les différentes parties relatives à l’utilisation de Qt sur notre plateforme à disposition, la fameuse WaRP7 (encore elle !), nous allons commencer notre étude par une introduction consacrée à la configuration de notre cible, c’est-à-dire :

  • configuration de l’environnement Yocto/OpenEmbedded ;
  • configuration de l’image ;
  • génération de l’image avec les composants Qt5 nécessaire ;
  • génération du SDK compatible Qt5.

2.1 Configuration de l’environnement

Il nous faudra en premier lieu rapatrier les sources du BSP (Board Support Package) de la communauté Freescale/NXP. Pour ce faire, rien de plus simple, l’utilitaire repo nous permet en quelques commandes de récupérer l’ensemble des sources nécessaires à la bonne construction d’une image pour notre cible visée :

$ mkdir ~/bin

$ curl http://commondatastorage.googleapis.com/git-repo-downloads/repo > ~/bin/repo

$ chmod a+x ~/bin/repo

$ PATH=${PATH}:~/bin

$ mkdir warp7_bsp

$ cd warp7_bsp

$ repo init -u https://github.com/Freescale/fsl-community-bsp-platform -b sumo

$ repo sync

Pour ce qui est de la version du BSP utilisée dans cet article, les auteurs auront choisi la dernière version stable, à savoir sumo, thud n’étant pas encore disponible sur le référentiel Freescale (le mainteneur préférant attendre la consolidation du support de l’i.MX8).

Ensuite, une fois les sources à disposition, il nous faudra aussi intégrer la couche de métadonnées pour une utilisation de Qt5 :

$ cd sources

$ git clone -b master https://github.com/meta-qt5/meta-qt5.git

$ cd ..

L’étape qui suit nous permettra la création de notre environnement. Afin d’avoir un ensemble cohérent, il conviendra de placer plusieurs variables lors de l’invocation de cette commande :

  • MACHINE : imx7s-warp, qui représente notre plateforme cible ;
  • DISTRO : poky, permettant de choisir la distribution de référence du projet Yocto (configuration disponible sous poky/meta-poky/conf/distro/poky.conf pour les curieux) ;
  • setup-environment : c’est le script qui nous permettra de configurer notre environnement de base ;
  • warp7-build : qui représente notre fichier de construction.

$ DISTRO=poky MACHINE=imx7s-warp source setup-environment warp7-build

Une fois cette commande lancée, nous nous retrouvons dans notre répertoire de travail où il faudra par exemple, rendre disponible la couche meta-qt5 au grand guru bitbake. Pour ce faire, il est recommandé d’utiliser la commande bitbake-layers. Celle-ci permet la gestion des couches au sein de notre environnement. Nous passerons en paramètre de cette commande, l'option add-layer qui permettra de mettre à jour le fichier conf/bblayers.conf, vous-êtes prêts ?

$ bitbake-layers add-layer ../sources/meta-qt5/

Voilà, nous disposons maintenant d’un environnement de construction prêt à être configuré pour nos besoins. Passons à la configuration de notre image.

2.2 Configuration de notre image

Dans cette sous-partie, nous allons préparer notre image système afin que celle-ci soit la plus complète et exhaustive possible pour notre étude. Pour ce faire, il conviendra dans un premier temps de créer une couche (« layer » au sens Yocto/OpenEmbedded) spécifique à notre intégration, ce qui nous permettra ainsi de stocker les différentes recettes et recettes dérivées utilisées lors de la personnalisation des différentes applications (systemd par exemple).

Rien de plus simple, il suffit d’utiliser la commande bitbake-layers avec comme argument create-layer et en paramètre, le nom de la couche à créer, qui dans notre cas aura le nom de meta-glmf :

$ bitbake-layers create-layer ../sources/meta-glmf

NOTE : Starting bitbake server...

L’exécution de la commande aura pour effet de créer notre « layer » dédiée à l’endroit spécifié. On notera la disparition de la commande yocto-layer depuis la version 2.5 (sumo) [2]. En effet, celle-ci faisait doublon avec la nouvelle implémentation de la sous-commande create-layer comme vu précédemment.

Enfin, il en sera de même que pour la meta-qt5, il nous faudra rendre celle-ci accessible dans notre configuration :

$ bitbake-layers add-layer ../sources/meta-glmf/

Maintenant fin prêts pour accueillir les différentes recettes, il nous est possible d’attaquer la phase de configuration. Commençons par remplacer SysVinit, l’init manager utilisé par défaut au sein du projet Yocto, par son « remplaçant » systemd. Il faudra rajouter quelques lignes dans le fichier de configuration principal (conf/local.conf) :

# Use systemd as init system

VIRTUAL-RUNTIME_init_manager = "systemd"

DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"

VIRTUAL-RUNTIME_initscripts = ""

DISTRO_FEATURES_append = " systemd"

Puis, on ajoutera le support du WiFi au sein de la variable DISTRO_FEATURES. Dans notre cas, cette variable aura pour effet de rajouter les composants logiciels relatifs à l’utilisation du Wi-Fi en espace utilisateur, nous retrouverons ainsi les paquets iw et wpa-supplicant (packagegroup-base-wifi) :

DISTRO_FEATURES_append = " wifi"

Bien entendu, il est possible ici aussi de se servir de la variable IMAGE_INSTALL pour y insérer les 2 paquets précédents.

Bref, restons dans le périmètre du sans-fil (sans jeu de mots) avec la configuration de notre interface réseau disponible sur notre plateforme. Pour ce faire, les auteurs ont choisi d’utiliser systemd-networkd pour gérer cet aspect. Ceci permet entre autres de ne pas rajouter de gestionnaire supplémentaire à notre image finale (NetworkManager ou connman par exemple). De plus, dans la configuration actuelle de systemd, networkd y est activé par défaut (depuis la version 2.4 de poky -> rocko). Pourquoi donc s’en priver ?

Un des atouts principal du projet Yocto réside dans le fait de pouvoir « surcharger » une recette existante sans en modifier les sources originales. Dans notre cas de figure, il nous faudra par exemple inclure le fichier de configuration pour notre interface wlan0, fichier au format .network pour la gestion avec systemd. Il nous est ainsi fortement conseillé d’utiliser le mécanisme de dérivation propre à bitbake.

Afin de réaliser cette opération, il va nous falloir respecter avant toutes choses quelques règles :

  • Garder la même arborescence que la recette originale (pas obligatoire, mais c’est une bonne pratique) : l’originale étant définie comme suit : poky/meta/recipes-core/systemd/. Dans notre environnement, ceci deviendra meta-glmf/recipes-core/systemd/ ;
  • Créer un fichier permettant d’utiliser la même recette, mais avec nos différents ajouts. Celui-ci devra porter l’extension .bbappend pour être interprété par bitbake comme étant une recette dérivée de l’originale. Dans notre cas, la recette principale est définie sous le nom systemd_237.bb, dans notre « layer » elle devra donc être implémentée sous la forme systemd_%.bbappend. Le « % » permet de ne pas spécifier de version particulière ;
  • La recette dérivée devra comporter la tâche do_install_append() afin de réaliser l’opération d’installation du fichier .network après l’invocation du do_install() propre à la recette originale.

Ainsi nous aurons dans notre couche meta-glmf, la structure suivante :

$ tree recipes-core/systemd

recipes-core/systemd

├── systemd

│   ├── warp7-wifi.network

└── systemd_%.bbappend

Avec le fichier .bbappend qui contiendra les directives suivantes :

FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"

SRC_URI_append = " \

 file://warp7-wifi.network \

"

do_install_append() {

 install -m 0644 ${WORKDIR}/warp7-wifi.network ${D}${sysconfdir}/systemd/network/

}

Quelques explications :

  • SRC_URI, permet de spécifier les fichiers contenus dans la recette, ici nous faisons référence à la configuration réseau de notre interface sans-fil ;
  • FILESEXTRAPATHS_prepend, qui permet d’inclure notre nouveau répertoire dans l’algorithme de recherche, rendant ainsi notre fichier visible par bitbake.

Pour ce qui est de la gestion de wlan0, nous faisons le choix d’utiliser le mode DHCP pour une attribution automatique de l’adresse IP :

[Match]

Name=wlan0

[Network]

DHCP=yes

Et voilà, notre interface Wi-Fi est maintenant configurée... enfin presque : il faudra lors du démarrage de la carte, renseigner les informations relatives à l’accès du sans-fil dans le fichier wpa_supplicant.conf (mais vous savez sans aucun doute réaliser cette étape !).

Autre point à implémenter au sein de notre configuration, la gestion de notre driver Wi-Fi. Par défaut, celui-ci se trouve être lié de façon non statique à notre image noyau, donc sous forme de module. Implémentation qui nous impose l’étape manuelle du modprobe pour l’insertion.

Par chance, Yocto/OE permet d’éviter de retoucher à l’interface ncurses pour la reconfiguration de l’image noyau. En effet, il est possible via la variable KERNEL_MODULE_AUTOLOAD, de gérer comme son nom l’indique, le chargement automatique du module lors du démarrage de la carte. En fait, cette commande a pour effet de créer un fichier <nom_module>.conf dans /etc/modules-load.d/. Tout comme systemd, il nous faudra ici aussi, dériver la recette principale de notre noyau Linux pour insérer les lignes suivantes :

KERNEL_MODULE_AUTOLOAD_append = " brcmfmac"

Nous retrouverons ainsi l’arborescence suivante :

$ tree recipes-kernel

recipes-kernel

└── linux

    └── linux-fslc_%.bbappend

Enfin, avant de pouvoir « cuisiner » notre image, il nous faut au préalable prévoir l’intégration de quelques applications, on retrouvera par exemple le module qtmqtt utile pour notre applicatif qui va suivre, ainsi que l’implémentation d’un serveur sftp (openssh-sftp-server) pour déployer notre application par le biais de Qt Creator. On retrouvera aussi le paquet mosquitto, Broker open source implémentant le protocole MQTT, celui-ci nous sera utile dans notre étude dans la mesure ou au moins une des cibles aura aussi le rôle de Broker. Il faudra pour ce faire, intégrer dans le fichier conf/local.conf, la ligne suivante :

CORE_IMAGE_EXTRA_INSTALL_append = " qtmqtt openssh-sftp-server mosquitto"

2.3 Génération de l’image

Vous l’avez sans doute compris, nous pouvons maintenant lancer la construction de notre image :

$ bitbake core-image-base

Loading cache: 100% |########################################################################################################################################################################| Time: 0:00:00

Loaded 2451 entries from dependency cache.

NOTE: Resolving any missing task queue dependencies

Build Configuration:

BB_VERSION           = "1.38.0"

BUILD_SYS            = "x86_64-linux"

NATIVELSBSTRING      = "universal"

TARGET_SYS           = "arm-poky-linux-gnueabi"

MACHINE              = "imx7s-warp"

DISTRO               = "poky"

DISTRO_VERSION       = "2.5.1"

TUNE_FEATURES        = "arm armv7ve vfp thumb neon callconvention-hard"

TARGET_FPU           = "hard"

meta                 

meta-poky            = "HEAD:45ef387cc54a0584807e05a952e1e4681ec4c664"

….

Quelques cafés plus tard, notre image résultante se trouvera dans tmp/deploy/images/imx7s-warp/core-image-base-imx7s-warp.wic.gz, fichier qu’il faudra décompresser et copier sur la cible (en utilisant la commande dd). Pour de plus amples informations quant à l’opération de flashage, le lecteur pourra se référer à la documentation en [3].

2.4 Génération du SDK

La génération du SDK (Software Development Kit) est en soi une tâche peu compliquée, car bien évidemment facilitée par l’utilisation du projet Yocto ! C’est donc pour cette raison que nous ferons ici un bref rappel sur cette génération et nous ne pourrons que conseiller le lecteur à se référer aux diverses publications parues dans GLMF [4].

2.4.1 Génération

Par habitude des auteurs, l’ensemble de l’applicatif en espace utilisateur se fera au travers du framework Qt, il nous faudra donc générer une chaîne de compilation croisée compatible. Pour ce faire :

$ bitbake meta-toolchain-qt5

Une fois la génération terminée et comme d’habitude, le résultat de la compilation se trouvera dans tmp/deploy/sdk.

2.4.2 Installation

Elle se fera tout simplement en exécutant le script suivant où il conviendra de spécifier le chemin d'installation (/opt/poky/2.5.1 dans cet exemple) :

$ cd tmp/deploy/sdk

$ ./poky-glibc-x86_64-meta-toolchain-qt5-armv7vehf-neon-toolchain-2.5.1.sh

Poky (Yocto Project Reference Distro) SDK installer version 2.5.1

=================================================================

Enter target directory for SDK (default: /opt/poky/2.5.1):

2.4.3 Vérification

Afin de vérifier notre environnement, il nous faudra réaliser quelques commandes.

« Sourcer » notre environnement, plaçons-nous dans un Shell, et utilisons la commande ci-après :

$ . /opt/poky/2.5.1/environment-setup-armv7vehf-neon-poky-linux-gnueabi

Ou :

$ source /opt/poky/2.5.1/environment-setup-armv7vehf-neon-poky-linux-gnueabi

Il est ensuite possible de vérifier la version de Qt utilisée :

$ qmake -v

QMake version 3.1

Using Qt version 5.11.2 in /opt/poky/2.5.1/sysroots/armv7vehf-neon-poky-linux-gnueabi/usr/lib

Pour la création du Kit avec Qt Creator, les auteurs laisseront au lecteur le soin de le mettre en place en s’aidant des diverses publications de GNU/Linux Magazine.

2.5 Boot to Qt ?

Jamais évoqué dans les colonnes de GNU/Linux Magazine, Boot2Qt est une pile logicielle légère, optimisée et complète pour les systèmes Linux embarqués développée par The Qt Company. Celle-ci repose sur l’utilisation du projet Yocto et de ses sous-ensembles (bitbake, poky…) afin d’être la plus optimisée possible pour une utilisation avec Qt. Un des autres avantages de cette solution est la mise à disposition de plateformes de référence, par exemple :

Mais en plus de cela, chaque plateforme dispose d’un ensemble pré-généré (image + SDK)... intéressant, non ?

3. Mise en situation : démonstrateur MQTT

Comme annoncé en début d’article, l’idée ici est de réaliser une petite étude permettant de montrer le potentiel du module Qt MQTT en environnement embarqué, avec comme pour habitude des auteurs, une utilisation du projet Yocto pour l’intégration logicielle (promis le prochain parlera aussi de Buildroot). L’application MQTT reposera sur l’utilisation de 2 WaRP7 (1 pour chaque auteur), chacune étant distante et sur des réseaux séparés. Enfin, une des 2 plateformes s’occupera de la partie Broker (mosquitto) en plus de la partie Publish. Le tout sera agrémenté d’une partie IHM (subscriber) pour l’affichage des différentes données en provenance des WaRP7. Ci-après (figure 3), le schéma de principe de l’étude proposée :

setup

Fig. 3 : Environnement de test.

3.1 Gestion « publisher »

Le publisher sera sous la forme d’un service embarqué sur la cible (WaRP7 dans notre exemple). Pour la lecture des capteurs, nous reprendrons ce qui a été fait dans un article précédent [5]. Nous axerons ainsi l’article sur l’échange de données et de leur visualisation. Les auteurs ont décidé ici de récupérer les données de l’accéléromètre ainsi que celles du capteur de température intégré à la WaRP7.

À la création du projet, il faudra bien entendu penser à ajouter le module MQTT dans le fichier de projet QMake (le fameux .pro), pour ce faire, rien de bien compliqué :

QT += mqtt

Comme défini, nous retrouverons les Topics suivants :

  • Warp7MQTT/nom/accelerometer/{x,y,z}
  • Warp7MQTT/nom/temperature

nom est fonction des auteurs (Jean et Pierre-Jean).

Pour Pierre-Jean (par exemple), la définition s’effectuera de la manière suivante :

#define ROOT_TOPIC QString("Warp7MQTT/")

#define HOME_NAME QString("Pierre-Jean/")

const QString TOPIC_PATH_ACC_X = QString(ROOT_TOPIC + HOME_NAME + "accelerometer/X");

const QString TOPIC_PATH_ACC_Y = QString(ROOT_TOPIC + HOME_NAME + "accelerometer/Y");

const QString TOPIC_PATH_ACC_Z = QString(ROOT_TOPIC + HOME_NAME + "accelerometer/Z");

const QString TOPIC_PATH_TEMP = QString(ROOT_TOPIC + HOME_NAME + "temperature");

Le reste de l’initialisation s’effectuera de manière très simple :

  • Utilisation de la classe QMqttClient pour la création du client MQTT ;
  • Définition des différents paramètres de connexion au Broker (hostname et numéro de port) ;
  • Création d’une connexion avec une fonction de type SLOT pour la gestion des notifications lors d’une connexion :

    //initialisation du client mqtt

    m_mqttClient = new QMqttClient(this); // Création du client

    m_mqttClient->setHostname("imx7s.ddns.net"); //désignation du broker

    m_mqttClient->setPort(1883);    //numéro de port

    connect(m_mqttClient, &QMqttClient::stateChanged, this, &CMqttClient::clientStateChanged); //connexion d'un signal à une fonction

    m_mqttClient->connectToHost(); //connexion

À noter que dans notre exemple, la connexion au Broker s’effectue par le biais de la fonction connectToHost(). Pour les lecteurs voulant intégrer des notions de sécurité, il est possible et préférable de passer par la fonction connectToHostEncrypted() afin de mettre en place une connexion chiffrée de type SSL/TLS.

On crée ensuite le timer m_updateEventLoopTimer (par le biais de la classe QTimer) qui permettra de publier les mises à jour de façon régulière vers le Broker :

//initialisation de la boucle de mise à jour

    m_updateEventLoopTimer = new QTimer();

    connect(m_updateEventLoopTimer, &QTimer::timeout, this, &CMqttClient::updateBroker);

Celui-ci sera déclenché uniquement lors d’une connexion effective au Broker (état Connected), état géré dans la fonction SLOT clientStateChanged lors d’un signal émis par la fonction stateChanged :

void CMqttClient::clientStateChanged(QMqttClient::ClientState state)

{

    switch (state) {

    case QMqttClient::Disconnected:

        qDebug() << "Client Disconnected";

        break;

    case QMqttClient::Connecting:

        qDebug() << "Client Connecting";

        break;

    case QMqttClient::Connected:

        qDebug() << "Client Connected";

        m_updateEventLoopTimer->start(100);

        break;

    }

}

Sur le signal timeout du timer défini précédemment, la fonction updateBroker() sera appelée (temps défini par le paramètre passé en argument de la fonction start()). Cette fonction aura pour rôle de récupérer les données physiques des capteurs (fonction readData()), afin de les publier par la suite sur les différents Topics :

void CMqttClient::updateBroker()

{

    //on met à jour les données lues sur les capteurs

    m_sensors->readData();

    //on envoie au broker

    //qos = 0

    //retain msg = true

    m_mqttClient->publish(TOPIC_PATH_ACC_X, QByteArray::number(m_sensors->accelerometerX()), 0, true);

    m_mqttClient->publish(TOPIC_PATH_ACC_Y, QByteArray::number(m_sensors->accelerometerY()), 0, true);

    m_mqttClient->publish(TOPIC_PATH_ACC_Z, QByteArray::number(m_sensors->accelerometerZ()), 0, true);

    m_mqttClient->publish(TOPIC_PATH_TEMP, QByteArray::number(m_sensors->temperature()), 0, true);

}

Pour nos besoins, on se contente d’une qualité de service faible et on demande au broker d’enregistrer la valeur du dernier message grâce au booléen « Retain Message ».

À partir de là, le client est prêt. Il ne reste plus qu’à le déployer sur les WaRP7 avec un HOME_NAME différent pour chacune (il aurait été certainement plus judicieux de passer une gestion automatique de cette variable, une gestion avec adresse MAC ou au travers un fichier de configuration. Mais nous ne sommes bien entendu pas ici sur une phase d’industrialisation du produit).

Le lecteur souhaitant tester son implémentation peut d’ores et déjà vérifier son bon fonctionnement avec les clients gratuits disponibles sur smartphones/tablettes.

3.2 Dessine-moi une interface

Nous allons ici présenter plusieurs modules du framework Qt permettant de visualiser et représenter des données. Nous aborderons notamment les modules QtChart et QtDataVisualization.

Pour commencer, on crée un projet Qt Quick Application > Empty et on ajoute directement les lignes ci-après afin de créer un lien vers les différents modules essentiels à notre application :

QT += quick charts mqtt

Définissons ensuite une classe d’interface (CSensorsInterface) pour le QML afin de lui permettre d’accéder aux données :

class CSensorsInterface : public QObject

{

    Q_OBJECT

public:

    CSensorsInterface();

    ~CSensorsInterface();

Cette classe se verra ensuite être implémentée dans le fichier source principal (main.cpp), pour ensuite l’exposer au QML afin que ce dernier puisse accéder aux propriétés et/ou fonctions exposées :

    CSensorsInterface *sensorsData = new CSensorsInterface();

    QQmlApplicationEngine engine;

    //export des classes vers QML

    engine.rootContext()->setContextProperty("Data", sensorsData);

Conformément à la documentation Qt relative au module Qt Chart :

« Note: Since Qt Creator 3.0 the project created with Qt Quick Application wizard based on Qt Quick 2 template uses QGuiApplication by default. As Qt Charts utilizes Qt Graphics View Framework for drawing, QApplication must be used. The project created with the wizard is usable with Qt Charts after the QGuiApplication is replaced with QApplication. »

Il nous est nécessaire de déclarer une QApplication pour le bon fonctionnement avec le module nous donnant accès aux divers éléments graphiques :

    //QApplication nécessaire pour l'utilisation des QtCharts

    QApplication app(argc, argv);

3.3 Give me your data

Il existe de multiples façons d’exposer une donnée au QML. Dans notre cas, on va chercher à exposer une liste de valeurs (accéléromètres et températures), chacune à associer à une clé (le sujet ou « Topic »). De cette manière, nous éviterons de créer une multitude de fonctions. Rendant ainsi le code source bien plus lisible.

L’instanciation du client MQTT se fait de la même manière. Une fois connecté, il suffit de souscrire aux différents Topics définis :

void CSensorsInterface::clientStateChanged(QMqttClient::ClientState state)

{

    switch (state) {

    case QMqttClient::Disconnected:

        qDebug() << "Client Disconnected";

        break;

    case QMqttClient::Connecting:

        qDebug() << "Client Connecting";

        break;

    case QMqttClient::Connected:

        qDebug() << "Client Connected";

        //souscription à la maison de Jean

        subscribe("Warp7MQTT/Jean/#");

        //souscription à la maison de Pierre-Jean

        subscribe("Warp7MQTT/Pierre-Jean/#");

        break;

    }

}

On remarquera ici, l’utilisation du caractère # pour permettre l’accès à un certain niveau de hiérarchie. Ici nous souhaitons récupérer l’ensemble des données physiques en provenance des capteurs.

Quant à la fonction subscribe() permettant la gestion des différents abonnements, elle s’implémentera de la manière suivante :

void CSensorsInterface::subscribe(QString topic)

{

    //création d'un topic

    QMqttTopicFilter topicFilter;

    topicFilter.setFilter(topic);

    //souscription au topic

    QMqttSubscription *sub = m_mqttClient->subscribe(topicFilter, 0);

    if (sub)

    {

        //création de la connexion à une fonction pour les mises à jour

        connect(sub, &QMqttSubscription::messageReceived, this, &CSensorsInterface::messageReceived);

    }

    else

        qDebug() << "Impossible de souscrire au Topic "<< topic;

}

À partir de là, une fois que le client sera connecté et que l’on aura souscrit aux bons sujets, les données seront récupérées de façon régulière par le biais de l’appel à la fonction messageReceived(). L’idée est, comme précisé plus haut, de stocker les données dans une table de hachage (clé/valeur). La table se rempliera et/ou remplacera les valeurs pour une clé déjà existante.

Un seul souci dans cette réflexion : les types supportés par le QML sont limités et ne permettent notamment pas d’exposer une variable de type QHash par exemple.

Plusieurs possibilités pour contourner le problème. Nous allons en proposer une générique. En effet, le moteur QML a la capacité d’introduire des instances QObject à travers le système de méta-objets. Cela signifie que n’importe quel code QML peut accéder aux membres d’une instance d’une classe dérivée de QObject. Il nous faudra donc dériver notre classe CMqttHashDaata de QObject. De plus, afin d’exposer la fonction de récupération des données au sein de notre de table de hachage au QML, celle-ci devra être marquée de la macro Q_INVOKABLE lors de la déclaration.

Regardons ainsi de plus près la définition de notre fameuse classe pour la gestion de la table de hachage où on retrouvera les 2 fonctions pour l’insertion (couple clé/valeur) et récupération :

class CMqttHashData : public QObject

{

    Q_OBJECT

public:

    Q_INVOKABLE double readValue(QString key);

    void insertValue(QString key, double value);

Pour la déclaration de notre table de hachage :

private:

    QHash<QString, double> m_hashValues;

Et les fonctions seront aussi simples que leurs déclarations :

double CMqttHashData::readValue(QString key)

{

    return m_hashValues.value(key);

}

void CMqttHashData::insertValue(QString key, double value)

{

    m_hashValues.insert(key, value);

}

On spécifie ensuite l’objet dans notre classe CSensorsInterface typée en QObject* et on y expose notre propriété, membre de donnée de notre classe via la macro Q_PROPERTY :

    //exposition au QML de la classe m_values

    Q_PROPERTY(QObject* values MEMBER m_values NOTIFY dataChanged())

private:

    QObject                 *m_values;

À savoir, pour une interopérabilité maximale avec le QML, toute propriété inscriptible doit être associée à un signal NOTIFY qui est émis chaque fois que la valeur de la propriété change. Dans notre exemple, le signal dataChanged() sera émis lors d’une modification de donnée en provenance du client MQTT, permettant ainsi une mise à jour automatique côté interface. Ce qui donne côté implémentation :

void CSensorsInterface::messageReceived(QMqttMessage msg)

{

    (static_cast<CMqttHashData*>(m_values))->insertValue(msg.topic().name(), msg.payload().toDouble());

    Q_EMIT dataChanged();

}

Tout ça pour pouvoir accéder aux différentes valeurs de notre table depuis le QML !

Côté interface, voici comment s’effectue la lecture des données :

Data.values.readValue("Warp7MQTT/Jean/temperature")

Explications :

  • Data est le nom d’exposition de la classe CSensorsInterface dans le fichier main.cpp,
  • values représente la propriété exposée de la variable membre m_values,
  • et on passe en paramètre de la fonction readValue(), la clé qui représente tout simplement le Topic désiré.

3.4 Du QML à portée de tous

Afin de représenter ces données brutes, les auteurs ont décidé de mettre en avant des modules QML permettant des affichages de graphes en 2D ou 3D : QtChart et QtDataVisualization.

Il nous faut, dans un premier temps, ajouter les deux modules au projet :

QT += charts

QT += datavisualization

On commence par le fichier main.qml. Il sera défini par trois composants : un fichier pour l’affichage des températures (TemperaturesChart), un fichier pour l’accéléromètre en représentation 2D (AccelerometersChart) et un dernier fichier pour l’accéléromètre en représentation 3D (Accelerometer3D). Tout ceci géré à l’aide d’une SwipeView permettant de glisser d’un composant à l’autre (pages) :

//permet de "swiper" entre les menus

    SwipeView {

        id: view

        anchors.fill: parent

        currentIndex: 0 //index par défaut

        TemperaturesChart {

        }

        AccelerometersChart {

        }

        Accelerometer3D {

        }

    }

    //indicateur permettant d'identifier l'index en cours

    PageIndicator {

        id: indicator

        count: view.count

        currentIndex: view.currentIndex

        anchors.bottom: view.bottom

        anchors.horizontalCenter: parent.horizontalCenter

    }

3.4.1 La température monte

On commence par le plus simple : l’affichage de la température sous forme d’histogramme. Il faut donc dans un premier temps importer le module QML QtCharts :

import QtCharts 2.0

On créé ensuite l’élément graphique de type ChartView qui servira de base pour les graphes que nous afficherons :

ChartView {

    id: chart

    title: "Les températures !"

    antialiasing: true

Afin de réaliser un histogramme, il conviendra de créer un composant BarSeries, composant auquel on associera des données de type BarSet :

//définition de l'axe des ordonnées

        //valeurs entre 0 et 55

        //avec un affichage sous forme de légende de 10 valeurs

        axisY : ValueAxis {

            min: 0

            max: 55

            tickCount: 10

        }

        //définition de l'axe des abscisses

        //On crée ici une catégorie de valeurs, il peut y en avoir plusieurs !

        axisX: BarCategoryAxis {

            categories: ["Température"]

        }

        //on crée les "sets" de données provenant de Jean et Pierre-Jean

        BarSet {

            label: "Jean";

            values: [Data.values.readValue("Warp7MQTT/Jean/temperature")]

        }

        BarSet {

            label: "Pierre-Jean";

            values: [Data.values.readValue("Warp7MQTT/Pierre-Jean/temperature")]

        }

Lorsqu’on crée un BarSet, les valeurs se présentent sous la forme [données Cat 1, données Cat 2, données Cat N] qui correspondent aux catégories créées dans le CatergoryAxis (légende). Le résultat est visible sur la figure 4.

BarSeries

Fig. 4 : Histogramme représentant les températures.

3.4.2 On accélère le rythme

On va essayer de créer un historique défilant sur un nombre maximum de données définies. Et ceci, avec les deux fois 3 axes provenant des accéléromètres des WaRP7 respectives des auteurs. Le concept est tout à fait similaire à l’exemple ci-dessus, il suffit de créer un ChartView et d’y intégrer des types de graphes : ici des SplineSeries, ce sont des courbes qui prennent en paramètres des points en faisant des interpolations non linéaires pour rendre les transitions plus fluides, allons-y !

On crée les objets SplineSeries :

    SplineSeries {

        id: lineSeriesXJean

        name: "Jean X"

        axisY: dataAxisY

        axisX: dataAxisX

    }

    SplineSeries {

        id: lineSeriesPierreXJean

        name: "Pierre-Jean X"

        axisY: dataAxisY

        axisX: dataAxisX

    }

Pour ensuite définir les axes :

    ValueAxis {

        id: dataAxisY

        min: -30

        max: 30

        tickCount: 15

    }

    ValueAxis {

        id: dataAxisX

        titleText: "Toutes les 100 ms"

        min: 0

        max: maxValues

        tickCount: 5

    }

La mise à jour de l’historique est cependant un peu plus complexe puisqu’il va falloir décaler d’un vers la gauche toutes les données du graphe lorsqu’on atteint la valeur maximale de données définie. Pour éviter de faire toute forme de traitement dans le QML, on va créer une fonction générique qui gère ça côté C++ :

void CSensorsInterface::updateHistoric(QLineSeries *series, qint32 maxLength, double value)

{

    //si longueur maximale de l'historique atteinte

    if (series->count() == maxLength)

    {

        //on retire l'élément le plus ancien

        series->remove(0);

        //dans ce cas on décale

        for (qint32 i = 0 ; i < maxLength - 1 ; ++i)

        {

            series->replace(i, series->at(i).x() - 1, series->at(i).y());

        }

    }

    //ajoute la dernière valeur

    series->append(series->count(), value);

}

Et on l’expose au QML en utilisant la macro Q_INVOKABLE :

    /**

* @brief updateHistoric

* @param series

* @param maxLength

* @param value

*/

    Q_INVOKABLE void updateHistoric(QLineSeries *series, qint32 maxLength, double value);

On termine enfin sur la mise à jour des données du côté QML :

    Connections {

        target: Data

        onDataChanged : {

            //z

            Data.updateHistoric(lineSeriesZJean, maxValues, Data.values.readValue("Warp7MQTT/Jean/accelerometer/Z"))

            Data.updateHistoric(lineSeriesPierreZJean, maxValues, Data.values.readValue("Warp7MQTT/Pierre-Jean/accelerometer/Z"))

            //y

            Data.updateHistoric(lineSeriesYJean, maxValues, Data.values.readValue("Warp7MQTT/Jean/accelerometer/Y"))

            Data.updateHistoric(lineSeriesPierreYJean, maxValues, Data.values.readValue("Warp7MQTT/Pierre-Jean/accelerometer/Y"))

            //x

            Data.updateHistoric(lineSeriesXJean, maxValues, Data.values.readValue("Warp7MQTT/Jean/accelerometer/X"))

            Data.updateHistoric(lineSeriesPierreXJean, maxValues, Data.values.readValue("Warp7MQTT/Pierre-Jean/accelerometer/X"))

        }

    }

Lors d’une connexion avec les signaux/slots, Qt ajoute systématiquement le préfixe on suivi du nom du signal (avec la première lettre en majuscule). Donc le signal dataChanged() deviendra onDataChanged() afin de définir la fonction slot.

La figure 5 présente le résultat de notre précédente implémentation.

2D

Fig. 5 : De jolies courbes !

3.4.3 Le meilleur pour la fin

Un petit exemple en 3D pour sublimer l’article, voilà ce qu’il nous faut. On va s’intéresser au module QtDataVisualization. On importe très logiquement le module dans notre fichier QML :

import QtDataVisualization 1.2

Pour l’exemple, on va chercher à visualiser l’accélération des deux WaRP7. Pour cela, on va juste afficher deux formes circulaires et les faire bouger dans l’espace tridimensionnel ! L’objet Scatter3D est parfaitement adapté pour cette utilisation :

    Scatter3D {

        id: scatterGraph

        width: dataView.width

        height: dataView.height

        shadowQuality: AbstractGraph3D.ShadowQualitySoftLow

        scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricRight

       }

On crée les 3 axes avec les caractéristiques suivantes :

ValueAxis3D {

        id: valueAxisX

        min: -20

        max: 20

    }

Et on les associe au scatterGraph précédemment défini (champ id de Scatter3D) :

        axisZ: valueAxisZ

        axisY: valueAxisY

        axisX: valueAxisX

Et le modèle de données du graphe (Scatter3DSeries) se présente sous cette forme :

        //création de la source de donnée

        Scatter3DSeries {

            id: scatterSeries

            //mode d'affichage du texte lors d'un clic sur un élément

            itemLabelFormat: "(@xLabel, @yLabel, @zLabel)"

            meshSmooth: true

            //lien vers la donnée réelle

            ItemModelScatterDataProxy {

                itemModel: dataModel

                xPosRole: "xPos"

                yPosRole: "yPos"

                zPosRole: "zPos"

            }

        }

Le dataModel peut être défini de nombreuses manières, ici nous avons décidé d’utiliser le composant ListModelde QML (un conteneur de ListElement) :

ListModel {

        id: dataModel

        //list pour Jean

        ListElement{

            xPos: 0;

            yPos: 0;

            zPos: 0;

        }

        //list pour Pierre-Jean

        ListElement{

            xPos: 0;

            yPos: 0;

            zPos: 0;

        }

    }

On remarque que chaque ListElement fait partie d’une liste de données. À l’index  , on retrouve la donnée de Jean et à l’index 1 la donnée de Pierre-Jean. Mais du coup, comment remplir ces données ? En fait, il convient tout simplement de reprendre le modèle de mise à jour de l’exemple en 2D :

Connections {

        target: Data

        onDataChanged : {

            //Jean

            dataModel.set(0,

                          {

                              "xPos":Data.values.readValue("Warp7MQTT/Jean/accelerometer/X"),

                              "yPos":Data.values.readValue("Warp7MQTT/Jean/accelerometer/Y"),

                              "zPos":Data.values.readValue("Warp7MQTT/Jean/accelerometer/Z")

                          })

            //Pierre-Jean

            dataModel.set(1,

                          {

                              "xPos":Data.values.readValue("Warp7MQTT/Pierre-Jean/accelerometer/X"),

                              "yPos":Data.values.readValue("Warp7MQTT/Pierre-Jean/accelerometer/Y"),

                              "zPos":Data.values.readValue("Warp7MQTT/Pierre-Jean/accelerometer/Z")

                          })

        }

    }

Tout ceci en mettant à jour le dataModel et en accédant à ces éléments via la fonction set qui prendra en paramètre, l’index de l’élément.

Et pour le plaisir des yeux, voyez la figure 6...

3D

Fig. 6 : Affichage 3D des données.

Il est possible de sélectionner une forme circulaire de l’espace 3D et voir le texte des coordonnées sous le format défini dans le Scatter3D. Vous pouvez aussi faire un clic droit sur la souris pour vous déplacer dans le graphe.

3.5 Intégration de l’application à notre image

Maintenant que notre application est fonctionnelle, il est intéressant de l’intégrer à l’image de façon à permettre un déploiement plus aisé (reproductibilité en environnement industriel par exemple). Ni une ni deux, éditons une recette permettant de réaliser cette opération (warpmqtt_git.bb) :

SUMMARY = "Qt App (MQTT demo for GLMF)"

...

SRC_URI = " \

 git://github.com/Jeanrenet/WarpMQTTSensor1.git \

 file://warpMQTT.service \

"

SRCREV = "${AUTOREV}"

S = "${WORKDIR}/git"

DEPENDS = " qtbase qtmqtt"

inherit qmake5 systemd

do_install_append() {

 install -Dm 644 ${WORKDIR}/warpMQTT.service ${D}${systemd_unitdir}/system/warpMQTT.service

}

SYSTEMD_SERVICE_${PN} = "warpMQTT.service"

Explications :

  • SRC_URI, spécifie les fichiers contenus dans la recette (référentiel git contenant les sources + le fichier unité afin de gérer notre application au démarrage) ;
  • SRCREV, pour la révision (AUTOREV afin de spécifier la dernière version disponible sur le référentiel) ;
  • DEPENDS = "qtbase qtmqtt", pour spécifier les dépendances du paquet, dans notre qtbase pour la partie core et qtmqtt pour l’accès au module mqtt ;
  • inherit qmake5, permet de spécifier la version spécifique de notre qmake ;
  • inherit systemd, afin de pouvoir installer notre fichier unité ;
  • do_install_append(), aura pour effet de surcharger l’étape d’installation (défini par la variable INSTALLS dans le fichier .pro du projet Qt), ceci afin d’installer notre fichier unité ;
  • SYSTEMD_SERVICE_{PN}, permet de renseigner le nom de notre fichier unité à intégrer. Celui-ci sera lancé par défaut au démarrage.

Le fichier unité qui permettra de configurer le comportement de notre application au démarrage, comprendra les différentes options suivantes :

[Unit]

Description=WarpMqttSensor1 daemon service

After=network-online.target

Wants=network-online.target

[Service]

ExecStart=/usr/bin/WarpMqttSensor1

[Install]

WantedBy=multi-user.target

Explications :

  • La section [unit], contient les différentes options génériques de notre unité :
    • Description : texte qui sera affiché lors de l’exécution de la commande systemctl status warpMQTT ;
    • After et Wants forment un couple afin de s’assurer du démarrage de l’application une fois la mise en service du réseau. Cela permet de garantir qu’une adresse IP a été attribuée à notre interface wlan0. Pour de plus amples informations sur cette fonctionnalité, l’explication dans la documentation officielle en [6].
  • La section [service], permet de spécifier des directives spécifiques (Type, ExecStart, …), dans notre cas, seule la variable ExecStart sera renseignée avant de spécifier le type de commande à lancer lors de l’exécution de l’unité (notre application WarpMqttSensor1) ;
  • La section [install], contient diverses informations concernant l’installation de l’unité via les commandes systemctl enable et systemctl disable. WantedBy permettra de spécifier les différents niveaux d’activations (Target (runlevels)).

Et voilà notre système est complet, il nous suffit maintenant de générer une nouvelle fois notre image (en incluant notre package warpmqtt) et de déployer celle-ci sur notre cible (avec Mender pourquoi pas).

Conclusion

À travers cet article, nous avons pu découvrir la mise en application du protocole MQTT en environnement Linux embarqué, jusqu’à la gestion de l’interface en QML via le module Qt Chart pour la représentation des données, le tout en passant par la génération de notre système embarqué (merci encore au projet Yocto !).

Tout ceci est encore une fois une belle démonstration de force du framework Qt5. En effet, celui-ci offre de plus en plus de possibilités pour l’utilisateur final, qui en fait incontestablement le numéro 1 dans le monde de l’embarqué (gestion du port série (ou même d’une Interface Bus CAN), gestion des protocoles industriels, gestion des IPC, et pleins d’autres encore).

Nous avons aussi vu que MQTT est un protocole pratique et efficace avec une gestion intéressante du QoS (très utile pour des données critiques), mais plus qu’un protocole, il est adopté par de nombreux développeurs, inclus dans de nombreuses API (comme AWS, Google Cloud Platform et Mender, comme le montre la figure 7). C’est tout ceci qui en fait un protocole différenciateur dans le monde de l’IoT.

mender-mqtt

Fig. 7 : Google Cloud Platform, Mender & MQTT.

Pour aller plus loin, il serait intéressant de découvrir et mettre en pratique la gestion de Remote UI avec Qt WebGL, ceci permettrait par exemple de rendre la partie client beaucoup plus légère et plus portable (gestion depuis le navigateur), car l’ensemble du contrôle serait implémenté sur la partie embarquée. Pour les curieux, une petite vidéo sur i.MX7 : https://www.youtube.com/watch?v=YY9rvos_I5w.

Références

[1] D. BODOR, « Faites communiquer vos projets simplement avec MQTT », Hackable Magazine n°26, septembre 2018 : https://connect.ed-diamond.com/Hackable/HK-026/Faites-communiquer-vos-projets-simplement-avec-MQTT

[2] Commit de la suppression de yocto-layer : http://git.yoctoproject.org/cgit.cgi/poky/commit/scripts?h=sumo&id=31684e868588121a4fcc6a966a509e8281ec9f9d

[3] Petit guide pour la gestion du flashage : https://github.com/WaRP7/WaRP7-User-Guide/blob/master/06-Chapter/Yocto_Project.adoc#steps-to-update-the-image

[4] P.-J. TEXIER et J. CHABRERIE, « À la découverte de la WaRP7 », Open Silicium n°20, octobre 2016 : https://connect.ed-diamond.com/Open-Silicium/OS-020/A-la-decouverte-de-la-WaRP7

[5] P.-J. TEXIER et J. CHABRERIE, « À l'assaut du sous-système noyau « Industrial I/O » ! (et du QML …!)  », GNU/Linux Magazine n°215, mai 2018 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-215/A-l-assaut-du-sous-systeme-noyau-Industrial-I-O-!-et-du-QML-!

[6] « Running Services after network is up » : https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/

 



Article rédigé par

Par le(s) même(s) auteur(s)

Mise à jour d’un système Linux embarqué « Over The Air » : comment intégrer et utiliser « Mender » pour vos déploiements

Magazine
Marque
GNU/Linux Magazine
Numéro
219
Mois de parution
octobre 2018
Spécialité(s)
Résumé

Afin de mieux comprendre les enjeux liés à la mise à jour d’un système embarqué connecté (nous parlons bien d’(I)IoT !), nous mettrons en œuvre dans cet article, Mender, une solution OTA permettant la gestion des déploiements sur des systèmes Linux embarqués.

À l’assaut du sous-système noyau « Industrial I/O » ! (et du QML … !)

Magazine
Marque
GNU/Linux Magazine
Numéro
215
Mois de parution
mai 2018
Spécialité(s)
Résumé
Dans cet article, nous allons développer un driver de périphérique en utilisant à la fois le bus i2c et le sous-système Industrial I/O. Le but final sera la mise en place d'une petite application Qt/QML permettant d'afficher les informations d'un capteur sur écran LCD.

i.MX7 : « Communication interprocesseur, donnons vie au Cortex M4 »

Magazine
Marque
GNU/Linux Magazine
Numéro
211
Mois de parution
janvier 2018
Spécialité(s)
Résumé
Nous allons découvrir dans cet article comment appréhender le développement sur plateforme i.MX7. Nous développerons un démonstrateur IoT en associant acquisition des données via Cortex M4, communication interprocesseur et consommation des données côté Cortex A7.

Les derniers articles Premiums

Les derniers articles Premium

PostgreSQL au centre de votre SI avec PostgREST

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Dans un système d’information, il devient de plus en plus important d’avoir la possibilité d’échanger des données entre applications. Ce passage au stade de l’interopérabilité est généralement confié à des services web autorisant la mise en œuvre d’un couplage faible entre composants. C’est justement ce que permet de faire PostgREST pour les bases de données PostgreSQL.

La place de l’Intelligence Artificielle dans les entreprises

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

L’intelligence artificielle est en train de redéfinir le paysage professionnel. De l’automatisation des tâches répétitives à la cybersécurité, en passant par l’analyse des données, l’IA s’immisce dans tous les aspects de l’entreprise moderne. Toutefois, cette révolution technologique soulève des questions éthiques et sociétales, notamment sur l’avenir des emplois. Cet article se penche sur l’évolution de l’IA, ses applications variées, et les enjeux qu’elle engendre dans le monde du travail.

Petit guide d’outils open source pour le télétravail

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Ah le Covid ! Si en cette période de nombreux cas resurgissent, ce n’est rien comparé aux vagues que nous avons connues en 2020 et 2021. Ce fléau a contraint une large partie de la population à faire ce que tout le monde connaît sous le nom de télétravail. Nous avons dû changer nos habitudes et avons dû apprendre à utiliser de nombreux outils collaboratifs, de visioconférence, etc., dont tout le monde n’était pas habitué. Dans cet article, nous passons en revue quelques outils open source utiles pour le travail à la maison. En effet, pour les adeptes du costume en haut et du pyjama en bas, la communauté open source s’est démenée pour proposer des alternatives aux outils propriétaires et payants.

Sécurisez vos applications web : comment Symfony vous protège des menaces courantes

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Les frameworks tels que Symfony ont bouleversé le développement web en apportant une structure solide et des outils performants. Malgré ces qualités, nous pouvons découvrir d’innombrables vulnérabilités. Cet article met le doigt sur les failles de sécurité les plus fréquentes qui affectent même les environnements les plus robustes. De l’injection de requêtes à distance à l’exécution de scripts malveillants, découvrez comment ces failles peuvent mettre en péril vos applications et, surtout, comment vous en prémunir.

Les listes de lecture

9 article(s) - ajoutée le 01/07/2020
Vous désirez apprendre le langage Python, mais ne savez pas trop par où commencer ? Cette liste de lecture vous permettra de faire vos premiers pas en découvrant l'écosystème de Python et en écrivant de petits scripts.
11 article(s) - ajoutée le 01/07/2020
La base de tout programme effectuant une tâche un tant soit peu complexe est un algorithme, une méthode permettant de manipuler des données pour obtenir un résultat attendu. Dans cette liste, vous pourrez découvrir quelques spécimens d'algorithmes.
10 article(s) - ajoutée le 01/07/2020
À quoi bon se targuer de posséder des pétaoctets de données si l'on est incapable d'analyser ces dernières ? Cette liste vous aidera à "faire parler" vos données.
Voir les 98 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous