Aller au contenu

TP03 : Station météo connectée

Dans ce TP, nous introduisons une nouvelle dimension à notre station météo avec un module de communication BLE. Le système est composé de deux appareils (cibles) :

  • une cible se charge de prendre des mesures environnementales et de les diffuser (broadcast) avec le BLE;
  • une deuxième cible reçoit ou observe les mesures diffusées par BLE et les affiche sur l’écran.

Schéma bloc

Objectifs du TP

Ce TP a pour but de mettre en pratique une communication BLE

À la fin de ce TP, les étudiants :

  • auront intégré les composants BLE à leur solution ;
  • auront intégré le broadcasting des données environnementales par BLE;
  • auront intégré le scanning des données environnementales afin d’afficher les données reçues sur un écran LCD ;
  • auront réalisé des analyses statiques de code et corrigé les erreurs rapportées ;
  • auront réalisé des tests de connectivité BLE relatifs à la portée ;
  • 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é : 6 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 deuxième séance en classe à 23:59.

Matériel

Pour ce TP, vous avez besoin du module X-NUCLEO-BNRG2A1. Assurez-vous de configurer la carte correctement en mettant le jumper J14 sur la gauche (position 1) :

X-NUCLEO-BNRG2A1

Le module BLE est placé entre le “STM32F412-DISCO” et la carte “Arduino Click Shield” :

Hardware pour le TP3

Attention : l’utilisation simultanée du module BLE et du module 7-segments n’est pas possible. Vous devez donc retirer le module 7-segments de votre cible pour ce TP.

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 BLE aux dépendances de votre projet (ligne 3) :

1
2
3
4
5
lib_deps =
    https://github.com/heia-fr/embsys-target-disco-heiafr.git#v0.2.0
    https://github.com/ARMmbed/mbed-os-ble-utils.git
    https://github.com/heia-fr/embsys-disco-gfx.git#main
    mbed-star297/BME280@0.0.0+sha.eaf30b268430

Dossier mbedignore

Le dossier mbedignore permet de supprimer des composants de Mbed OS pour accélérer la compilation et l’édition de liens (linker) du programme. Jusqu’ici, nous avions ignoré la partie BLE et nous devons la ré-activer pour ce TP :

Editez le fichier mbedignore/connectivity/.mbedignore et supprimez les lignes suivantes :

drivers/ble/*

et

FEATURE_BLE/*

Fichier mbed_app.json

Les bibliothèques pour Mbed OS telles que la bibliothèque BLE sont souvent accompagnées de paramètres qui influencent le comportement de la bibliothèque et de sa compilation. Pour la bibliothèque BLE que nous utilisons, les paramètres sont décrits dans ce fichier mbed_lib.json

Pour modifier les valeurs par défaut, éditez le fichier mbed_app.json de votre projet et ajoutez les lignes suivantes :

{
    "target_overrides": {
        "*": {
            ...
            "platform.minimal-printf-set-floating-point-max-decimals": 4,
            "ble.ble-role-observer": true,
            "ble.ble-role-broadcaster": true,
            "ble.ble-role-central": false,
            "ble.ble-role-peripheral": false,
            "ble.ble-feature-gatt-client": false,
            "ble.ble-feature-gatt-server": false,
            "ble.ble-feature-security": false,
            "ble.ble-feature-secure-connections": false,
            "ble.ble-feature-signing": false,
            "ble.ble-feature-whitelist": false,
            "ble.ble-feature-privacy": false,
            "ble.ble-feature-phy-management": false,
            "ble.ble-feature-extended-advertising": false,
            "ble.ble-feature-periodic-advertising": false,
            "ble.ble-security-database-filesystem": false,
            "ble.ble-security-database-kvstore": false,
            "ble.ble-security-database-max-entries": 5,
            "ble.ble-gap-max-advertising-sets": 15,
            "ble.ble-gap-host-based-private-address-resolution": false,
            "ble.ble-gap-max-advertising-reports-pending-address-resolution": 16,
            "ble.ble-gap-host-privacy-resolved-cache-size": 16,
            "ble.ble-passkey-display-reversed-digits-deprecation": true
        },
        ...

Vous devez également modifier le fichier mbed_app.json pour l’utilisation de la bibliothèque BLE et pour la configuration des pins du module BLE en ajoutant les définitions suivantes :

    "DISCO_HEIAFR": {
            ...
            "target.components_add": ["BlueNRG_2"],
            "target.features_add": ["BLE"],
            "target.extra_labels_add": ["CORDIO"],
            "bluenrg_2.SPI_MOSI": "D11",
            "bluenrg_2.SPI_MISO":  "D12",
            "bluenrg_2.SPI_nCS":   "A1",
            "bluenrg_2.SPI_RESET": "D7",
            "bluenrg_2.SPI_IRQ":   "A0",
            "bluenrg_2.SPI_SCK":   "D13"
        }

Réalisation

Classe permettant d’encapsuler la production et la consommation de données

Le point de départ pour ce TP est votre réalisation du TP02 et dans ce TP également, des mécanismes de production/consommation devront être mis en œuvre dans une application multi-tâches. Dans le TP03, le mécanisme de production et de consommation doit être réalisé par les composants de deux applications.

La première application fonctionne sur le dispositif muni du capteur et d’un module BLE. Pour cette application, le processus de production et de consommation est réalisé de la manière suivante :

  • Comme dans le TP02, la classe SensorDataServer est un producteur de données. Cette classe a pour mission de lire les données provenant des capteurs et de les mettre à disposition d’un consommateur.
  • Dans le TP02, le consommateur des données produites par l’instance de SensorDataServer est le composant qui affiche les données sur l’écran LCD. Dans ce TP, le consommateur de ces données est le broadcaster BLE, qui diffuse ces données pour qu’un observer BLE puisse les recevoir.

La deuxième application fonctionne sur le dispositif muni d’un module BLE et d’un écran LCD. Pour cette application, le processus de production et de consommation est réalisé de la manière suivante :

  • L’observer BLE est un producteur de données. Les données produites sont celles reçues dans les paquets d’advertising. Les données reçues sont mises à disposition d’un consommateur.
  • Le consommateur des données produites par l’instance d’observer est le composant qui affiche les données sur l’écran LCD.

Afin de réaliser le comportement de production et de consommation de manière uniforme, vous devez encapsuler ces comportements dans une classe DataMailbox. Cette classe définit le type de données représentant une mesure, possède une instance de Mail et fournit deux méthodes :

class DataMailbox 
{
public:
  struct message_t 
  {
    double temperature;
    double pressure;
    double humidity;
    time_t time;
  };
  ...
  bool produceData(const message_t& message);
  bool consumeData(message_t& message);
  ...

Après avoir réalisé cette classe, vous pouvez l’intégrer dans votre réalisation et tester à nouveau le bon comportement de votre application. Le comportement doit être le même que votre application du TP02.

L’API BLE fournie par Mbed OS

Comme pour la plupart des API fournies par Mbed OS, l’API BLE est disponible sous forme de classes C++. Cela permet de programmer une application BLE compatible avec tous les modules BLE compatibles, en réduisant la complexité. Pour notre application, vous devez utiliser les classes suivantes :

  • la classe BLE qui permet d’obtenir un singleton représentant le device et qui permet d’initialiser ce dernier. Le concept de singleton est expliqué par exemple sous singleton pattern.
  • la classe Gap qui permet d’accéder aux fonctionnalités de découverte des dispositifs et de gestion de l’advertising et du scanning.

Avant de détailler l’utilisation de ces classes, il est important de mentionner les points suivants concernant l’API BLE de Mbed OS :

  • De nombreux appels à l’API sont asynchrones et le résultat de ces appels est donc fourni via des fonctions ou méthodes de callback.
  • L’API n’est pas thread safe. Cela signifie que tous les appels à la librairie et que le traitement asynchrone des messages doivent être effectués depuis le même thread.

La classe GAPDevice

Afin de faciliter votre travail d’intégration de la librairie BLE dans votre application, le squelette d’une classe GapDevice est donné ci-dessous :

gap_device.hpp
#ifndef GAP_DEVICE_HPP_
#define GAP_DEVICE_HPP_

// mbed os
#include <mbed.h>
#include "ble/BLE.h"
#include "ble/Gap.h"

class GAPDevice :
  private mbed::NonCopyable<GAPDevice>,
  public ble::Gap::EventHandler
{
public:
  // constructor
  GAPDevice();

  // destructor
  ~GAPDevice();

  void start()
  {
    // errors in this method are considered fatal (stop the system)

    // check whether the BLE instance is already initialized (it should not be)
    if (BLE::Instance().hasInitialized()) {
      MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_APPLICATION, MBED_ERROR_CODE_INITIALIZATION_FAILED),
                 "Ble instance already initialised");
    }

    // this will inform us off all events so we can schedule their handling using our event queue
    BLE::Instance().onEventsToProcess(makeFunctionPointer(this, &GAPDevice::scheduleBleEvents));

    // set this instance as the event handler for all gap events
    BLE::Instance().gap().setEventHandler(this);

    // initialize the ble component
    ble_error_t error = BLE::Instance().init(this, &GAPDevice::onInitComplete);
    if (error != BLE_ERROR_NONE) {
      MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_APPLICATION, error), "Error returned by BLE::init");
    }

    // to show we're advertising we'll blink
    // For blinking at the correct speed, create a separate thread for handling blinks (although
    // this is a bit overkill)
    blinkQueue_.call_every(BLINK_INTERVAL, callback(this, &GAPDevice::toggleLed));
    osStatus status = blinkingThread_.start(callback(&blinkQueue_, &EventQueue::dispatch_forever));
    if (status != osOK) {
      MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_APPLICATION, status),
                 "Error when starting blinking thread");
    }

    status = bleThread_.start(callback(&eventQueue_, &EventQueue::dispatch_forever));
    if (status != osOK) {
      MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_APPLICATION, status),
                 "Error when starting ble thread");
    }
  }

protected:
  // protected methods (may be called by inheriting classes)
  template <typename T, typename R>
  void scheduleCall(T* obj, R (T::*method)())
  {
    eventQueue_.call(obj, method);
  }

  // This is called by inheriting classes when BLE interface is initialised
  virtual void onInitComplete(BLE::InitializationCompleteCallbackContext* event) = 0;

  // helper methods
  static void uint16_encode(uint16_t value, uint8_t* p_encoded_data);
  static void int16_encode(int16_t value, uint8_t* p_encoded_data);
  static void uint32_encode(uint32_t value, uint8_t* p_encoded_data);

  static uint16_t uint16_decode(uint8_t* p_encoded_data);
  static int16_t int16_decode(uint8_t* p_encoded_data);
  static uint32_t uint32_decode(uint8_t* p_encoded_data);

 private:
  // private methods

  // Schedule processing of events from the BLE API in our event queue (served by blethread_)
  void scheduleBleEvents(BLE::OnEventsToProcessCallbackContext* context)
  {
    eventQueue_.call(Callback<void()>(&context->ble, &BLE::processEvents));
  }

 protected:

 private:
  void toggleLed();

  // private data members
  events::EventQueue eventQueue_;
  events::EventQueue blinkQueue_;

  // led used for blinking upon advertising
  DigitalOut led_;

  // threads
  Thread blinkingThread_;
  Thread bleThread_;
};

#endif /* GAP_DEVICE_HPP_ */

Cette classe servira de base aux composants que vous devez développer pour votre application. Vous devrez utiliser cette classe par héritage pour les composants BLEBroadcaster et BLEObserver que vous devrez développer. Les principes mis en œuvre dans cette classe sont les suivants :

  • L’API BLE de Mbed OS requiert que tous les appels aux méthodes de l’API soient exécutés par un seul et même thread. Ce thread doit être créé et démarré par l’application. L’API fournit donc une méthode ble::BLE::onEventsToProcess qui permet de signaler à l’application quand des événements de l’API doivent être traités et l’API fournit également une méthode ble::BLE::processEvents pour traiter ces événements. Ce mécanisme est réalisé pour votre application dans la classe GAPDevice (voir les méthodes GAPDevice::start() et GAPDevice::scheduleBleEvents()).

  • D’autres opérations dans les classes BLE et Gap sont effectuées de manière asynchrone. Afin de recevoir les résultats de ces opérations asynchrones, il est nécessaire d’enregistrer des callbacks pour traiter les résultats. Pour la classe ble::Gap, cet enregistrement se fait avec la méthode ble::Gap::setEventHandler(). La classe GAPDevice hérite de la classe ble::Gap::EventHandler et dans la méthode GAPDevice::start(), nous pouvons enregistrer l’instance de GAPDevice courante comme traitant les événements générés par la classe ble::Gap. Afin de traiter un événement généré par la classe ble::Gap, il suffit de redéfinir une des méthodes de la classe ble::Gap::EventHandler dans la classe GAPDevice - veuillez noter qu’aucune de ces méthodes n’est redéfinie à ce stade.

  • Un mécanisme similaire de callback est utilisé pour l’appel à la méthode ble::BLE::init() pour laquelle le callback est la méthode GAPDevice::onInitComplete(). Cette méthode est une méthode abstraite qui devra être redéfinie par les classes BLEBroadcaster et BLEObserver héritant de GAPDevice. Plus de détails sont donnés ci-dessous concernant les méthodes virtuelles et abstraites.

  • Afin d’obtenir une référence sur l’instance ble::BLE singleton, les méthodes de la classe GAPDevice font appel à la méthode ble::BLE::Instance(). Afin d’obtenir une référence sur l’instance ble::Gap singleton, les méthodes de la classe GAPDevice font appel à la méthode ble::BLE::Instance().Gap()

Pour rappel, les méthodes virtuelles d’une classe sont des méthodes qui peuvent être redéfinies par une classe dérivant de cette classe. Lorsqu’une classe redéfinit une méthode virtuelle, c’est bien la méthode redéfinie dans cette classe qui sera appelée lorsque cette méthode sera invoquée. Lorsqu’une classe redéfinit une méthode virtuelle, le mot clé override devrait être ajouté à la déclaration de la méthode selon la notation void f() override. Une méthode virtuelle peut être définie comme pure virtual dans une classe de base avec la notation virtual ... = 0. Dans ce cas, le type de la classe de base devient abstrait et la méthode doit être définie dans une classe dérivante afin que cette classe soit instanciable.

En rapport à l’utilisation d’un singleton, il est intéressant de mettre en évidence les points suivants dans la classe BLE :

class BLE 
{
public:
    ...
    // Prevent copy construction and copy assignment of BLE.
    BLE(const BLE &) = delete;
    BLE &operator=(const BLE &) = delete;
...
};

Comme commenté dans le code, les notations = delete suivant la déclaration du copy constructor et de l’opérateur d’affectation permettent d’empêcher l’utilisation de ces mécanismes. En d’autres termes, il n’est pas possible pour un programme de copier une instance de la classe BLE, ce qui est nécessaire lorsque l’on utilise des singletons. Ce mécanisme est expliqué de manière détaillée sous deleted functions.

Questions

Expliquez pourquoi le mécanisme de singleton doit déclarer le copy constructor et l’opérateur d’affectation avec un = delete. Démontrez avec un exemple comment il serait possible de dupliquer un singleton.

La classe BLEBroadcaster

La classe BLEBroadcaster est utilisée par l’application afin de diffuser les données de capteurs en mode advertising. Cette classe représente donc un rôle de broadcaster au sens de la terminologie BLE.

Vous devez créer la classe BLEBroadcaster avec les caractéristiques suivantes :

  • La classe hérite de la classe GAPDevice.
  • La classe redéfinit la méthode abstraite privée onInitComplete.
  • La classe définit une méthode privée startAdvertising qui est appelée lorsque l’initialisation du dispositif a été effectuée avec succès.
  • La classe définit une méthode publique setAdvertisementPayload qui sera appelée par l’application pour publier les données environnementales dans les paquets d’advertising.
  • La classe définit une méthode privée setServiceData qui est utilisée pour construire les données d’advertising.
  • La classe définit un attribut de type ble::AdvertisingDataBuilder. Cet attribut sera utilisé dans différentes méthodes afin d’initialiser les données d’advertising et afin de les mettre à jour lorsque de nouvelles données de capteurs sont disponibles. Cet attribut doit être initialisé à l’aide d’un buffer de type uint8_t de la taille maximale des données d’advertising (définie par ble::LEGACY_ADVERTISING_MAX_SIZE).

La méthode BLEBroadcaster::onInitComplete

La méthode BLEBroadcaster::onInitComplete doit être réalisée en tenant compte des points suivants :

  • Si une erreur est survenue lors de l’initialisation, le champ error du paramètre de type ble::BLE::InitializationCompleteCallbackContext passé à l’appel de la méthode contient l’identifiant de l’erreur. Si l’initialisation s’est déroulée sans erreur, alors le champ error contient la valeur BLE_ERROR_NONE. Si une erreur est survenue, il faut considérer cette erreur comme fatale et stopper le système.
  • En utilisant la fonction print_mac_address() de la bibliothèque mbed-os-ble-utils, vous pouvez afficher l’adresse MAC du dispositif sur la console.
  • Finalement, la méthode doit démarrer l’advertising. Comme déjà énoncé, tous les appels à l’API BLE de Mbed OS doivent être effectués par un seul et même thread, dans notre cas le thread nommé bleThread_ défini dans la classe GAPdevice. Afin de démarrer l’advertising, vous devez donc utiliser la méthode GAPDevice::scheduleCall en spécifiant la méthode BLEBroadcaster::startAdvertising en paramètre.

La méthode BLEBroadcaster::startAdvertising

La méthode BLEBroadcaster::startAdvertising doit être réalisée en tenant compte des points suivants :

  • Toute erreur survenant dans cette méthode doit être considérée comme fatale et doit donc stopper le système. Cela signifie bien sûr que le code de retour de toutes les méthodes appelées doit être vérifié.
  • Les paramètres d’advertising doivent être définis en appelant la méthode Gap::setAdvertisingParameters. Pour notre broadcaster, le type d’advertising est advertising_type_t::NON_CONNECTABLE_UNDIRECTED et l’intervalle d’advertising est un paramètre de votre application.
  • Le champ Flags (AD Type 0x01) doit être initialisé en appelant la méthode ble::AdvertisingDataBuilder::setFlags. Les flags à enclencher dans notre application sont ble::adv_data_flags_t::LE_GENERAL_DISCOVERABLE et ble::adv_data_flags_t::BREDR_NOT_SUPPORTED.
  • Le champ Name (AD Type 0x09) doit être initialisé en appelant la méthode ble::AdvertisingDataBuilder::setName. Choisissez un nom spécifique, d’une taille permettant de respecter la limite totale de 31 octets pour les données d’advertising.
  • Les données d’advertising du dispositif GAP - initialisées dans la classe BLEBroadcaster à l’aide de l’attribut ble::AdvertisingDataBuilder - doivent être mises à jour à l’aide de la méthode Gap::setAdvertisingPayload.
  • Finalement, le processus d’advertising doit être démarré à l’aide de la méthode Gap::startAdvertising. Dans le cas de notre application broadcaster, vous pouvez utiliser les paramètres par défaut de la méthode.

Après avoir réalisé la méthode BLEBroadcaster::startAdvertising, votre dispositif devrait envoyer des paquets d’advertising à l’intervalle fixé par l’application. Pour tester ce comportement, vous devez instancier un BLEBroadcaster dans votre application et appeler la méthode BLEBroadcaster::start. Votre dispositif devrait apparaître si vous effectuez un scan à l’aide d’une application comme Nordic Semiconductor nRF Connect for Mobile.

Avant de poursuivre la réalisation, vous devez vous assurer que l’advertising fonctionne correctement. La suite de la réalisation permettra de mettre à jour les données d’advertising afin que celles-ci contiennent les données de capteurs.

La méthode BLEBroadcaster::setServiceData

La méthode BLEBroadcaster::setServiceData est mise à disposition ci-dessous :

BLEBroadcaster::setServiceData
ble_error_t BLEBroadcaster::setServiceData(UUID serviceUUID,
                                           const uint8_t* pServiceData,
                                           uint16_t serviceDataLength)
{
    // set the local service list
    ble_error_t error = advDataBuilder_.setLocalServiceList(
        mbed::make_Span(&serviceUUID, 1), true);
    if (error != BLE_ERROR_NONE) {
        tr_error("Error in setServiceData: %d", error);
        return error;
    }

    // set the service data
    error = advDataBuilder_.setServiceData(
        serviceUUID, mbed::make_Span(pServiceData, serviceDataLength));
    if (error != BLE_ERROR_NONE) {
        tr_error("Error in setServiceData: %d", error);
        return error;
    }

    // set the advertising payload
    error = BLE::Instance().gap().setAdvertisingPayload(
        ble::LEGACY_ADVERTISING_HANDLE, advDataBuilder_.getAdvertisingData());
    if (error != BLE_ERROR_NONE) {
        MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_APPLICATION, error),
                   "Error in gap().setAdvertisingPayload");
    }

    return BLE_ERROR_NONE;
}

Cette méthode peut être utilisée par la méthode BLEBroadcaster::setAdvertisementPayload à développer. Elle prend en paramètre l’UUID du service et un buffer contenant les données de services. Ces données correspondent au champ Service Data du payload d’advertising (AD type 0x16). La méthode utilise l’attribut ble::AdvertisingDataBuilder et doit donc être adaptée au nom de l’attribut que vous aurez choisi pour votre classe BLEBroadcaster.

La méthode BLEBroadcaster::setAdvertisementPayload

La méthode BLEBroadcaster::setAdvertisementPayload doit être réalisée en tenant compte des points suivants :

  • La méthode prend en paramètres les données de capteurs.
  • Un buffer de 8 octets doit être créé sur le stack pour sauvegarder les données de service.
  • Les données de capteurs doivent être encodées dans le buffer à l’aide des méthodes statiques uint16_encode, int16_encode et uint32_encode définies dans la classe GAPDevice. Si vous le préférez, vous pouvez également utiliser les macros définies dans le fichier ‘bstream.h’ qui fait partie de la librairie BLE de Mbed OS.
  • Après avoir encodé les données de capteurs dans le buffer, la méthode setServiceData doit être appelée. L’UUID du service à utiliser est GattService::UUID_ENVIRONMENTAL_SERVICE.

À ce stade, votre BLEBroadcaster devrait être entièrement fonctionnel. Vous devez modifier votre programme principal et appeler la méthode BLEBroadcaster::setAdvertisementPayload lorsque des données de capteurs sont disponibles. Vous devez suivre le même modèle de programmation que celui utilisé pour l’affichage des données sur l’écran LCD. Vous pouvez vérifier le comportement de votre application broadcaster à l’aide de l’application Nordic Semiconductor nRF Connect for Mobile. L’application ne permet pas d’interpréter les données de service, mais vous devriez constater que ces données sont modifiées lorsque les valeurs des mesures de capteurs changent.

La classe BLEObserver

La classe BLEObserver est utilisée par l’application afin de scanner les données de capteurs qui sont émises par un broadcaster. Cette classe représente donc un rôle d’observer au sens de la terminologie BLE.

Vous devez créer la classe BLEObserver avec les caractéristiques suivantes :

  • La classe hérite de la classe GAPDevice.
  • La classe redéfinit la méthode abstraite privée onInitComplete.
  • La classe définit une méthode privée startScanning qui est appelée lorsque l’initialisation du dispositif a été effectuée avec succès.
  • La classe redéfinit la méthode onAdvertisingReport de la classe Gap::EventHandler. Cette méthode est appelée par l’API BLE lorsque le dispositif scanne et reçoit des paquets d’advertising. L’application peut ainsi réaliser un comportement spécifique en fonction des paquets d’advertising reçus.
  • La classe définit un constructeur qui reçoit en paramètre une référence vers une instance de DataMailbox. La classe contient également un attribut pour sauvegarder cette référence et l’utiliser dans différentes méthodes.

La méthode BLEObserver::onInitComplete

La méthode BLEObserver::onInitComplete doit être réalisée en tenant compte des points suivants :

  • Si une erreur est survenue lors de l’initialisation, le champ error du paramètre de type ble::BLE::InitializationCompleteCallbackContext passé à l’appel de la méthode contient l’identifiant de l’erreur. Si l’initialisation s’est déroulée sans erreur, alors le champ error contient la valeur BLE_ERROR_NONE. Si une erreur est survenue, il faut considérer cette erreur comme fatale et stopper le système.
  • Finalement, la méthode doit démarrer le scanning. Tous les appels à l’API BLE de Mbed OS devant être effectués par un seul et même thread, vous devez donc utiliser la méthode GAPDevice::scheduleCall en spécifiant la méthode BLEBroadcaster::startScanning en paramètre.

La méthode BLEObserver::startScanning

La méthode BLEObserver::startScanning doit être réalisée en tenant compte des points suivants :

  • Toute erreur survenant dans cette méthode doit être considérée comme fatale et doit donc stopper le système. Cela signifie bien sûr que le code de retour de toutes les méthodes appelées doit être vérifié.
  • Les paramètres de scanning doivent être définis en appelant la méthode Gap::setScanParameters. Les paramètres de scanning comprennent l’intervalle (scan_interval) ainsi que la fenêtre de scanning (scan_window). Ces paramètres correspondent aux définitions du standard BLE et peuvent être réglés en fonction de l’intervalle d’advertising.
  • Le processus de scanning doit être démarré à l’aide de la méthode Gap::startScan. Dans le cas de notre application broadcaster, vous pouvez utiliser les paramètres par défaut de la méthode.

La méthode BLEObserver::onAdvertisingReport

La méthode BLEObserver::onAdvertisingReport est la méthode qui est appelée à chaque réception d’un paquet d’advertising. Cette méthode est donc la méthode qui permet de décoder les paquets d’advertising reçus du broadcaster. Vous devez réaliser cette méthode en tenant compte des points suivants :

  • La méthode reçoit en paramètre une référence sur une instance de ble::AdvertisingReportEvent.
  • Afin de faciliter le développement et le débogage, vous pouvez filtrer les messages sur la base de l’adresse du broadcaster. Cette adresse peut être obtenue à l’aide de la méthode ble::AdvertisingReportEvent::getPeerAddress. Ce mécanisme de filtrage devrait être désactivé lorsque le développement de la méthode a été validé.
  • Afin de faciliter l’analyse du payload d’advertising, vous pouvez utiliser une instance de ble::AdvertisingDataParser. Vous pouvez construire cette instance sur le stack, en spécifiant le payload à analyser avec la méthode ble::AdvertisingReportEvent::getPayload.
  • À l’aide de cette instance de ble::AdvertisingDataParser, vous pouvez obtenir les AD Structures successives, en interrogeant ble::AdvertisingDataParser::hasNext et en appelant ble::AdvertisingDataParser::next. La méthode next retourne une instance de ble::AdvertisingDataParser::element_t, qui correspond à une AD Structure. Cette structure de donnée contient donc un type dans le champ type et des données dans le champ value. Dans votre code, vous devez donc tester les différents types de AD Structure reçus, qui sont ble::adv_data_type_t::COMPLETE_LOCAL_NAME, ble::adv_data_type_t::COMPLETE_LIST_16BIT_SERVICE_IDS et ble::adv_data_type_t::SERVICE_DATA.
  • Le type ble::adv_data_type_t::COMPLETE_LOCAL_NAME permet de connaître le nom complet du broadcaster.
  • Le type ble::adv_data_type_t::COMPLETE_LIST_16BIT_SERVICE_IDS permet de connaître la liste de services mis à disposition par le broadcaster. Dans notre cas, cette liste contient un seul service qui est GattService::UUID_ENVIRONMENTAL_SERVICE. Vous pouvez utiliser la classe UUID afin de vérifier l’identifiant du service.
  • Le type ble::adv_data_type_t::SERVICE_DATA permet de recevoir les données de service. Dans notre cas, les données de services devraient avoir une taille de 10 octets. Les deux premiers octets contiennet à nouveau l’UUID du service (GattService::UUID_ENVIRONMENTAL_SERVICE), puis la pression atmosphérique, la température et l’humidité. Les valeurs environnementales sont toutes encodées comme des entiers de 16 bits ou 32 bits, comme un nombre à virgule fixe décimal.
  • Dans cette méthode, lorsque que les données environnementales ont été décodées avec succès, ces données doivent être produites dans le DataMailbox afin qu’un consommateur puisse en faire usage.

Le programme principal

Afin de simplifier la réalisation des scénarios de broadcaster et d’observer, vous pouvez réaliser les deux comportements dans une seule application. Dans cette application, vous pouvez tester la présence du capteur Weather click. Si le capteur est présent, l’application réalise le scénario du broadcaster, avec une instance de DataMailbox utilisée pour échanger des données entre le SensorDataServer et le BLEBroadcaster. Si le capteur n’est pas présent, l’application réalise le scénario de l’observer, avec une instance de DataMailbox utilisée pour échanger des données entre le BLEObserver et le programme affichant des données sur l’écran LCD. Vous pouvez ensuite flasher le même programme sur deux dispositifs différents, les deux étant équipés d’un module X-NUCLEO-BNRG2A1 et un seul étant équipé d’un capteur Weather click.

Tests et validations

Vous devez effectuer un test qui démontre que vos dispositifs broadcaster et observer fonctionnent. Pour ce test, vous devez rapporter les distances entre les deux dispositifs que vous avez testées et décrire à partir de quelle distance la transmission de données ne fonctionne plus.

En rapport aux tests automatiques, il faut noter que tester un tel système est difficile avec l’infrastructure de l’école. Pour ce TP, vous ne testerez donc pas de code sur la cible. Par contre, vous allez configurer l’analyse statique de code de manière plus stricte.

Dans le fichier .gitlab-ci.yml, configurez l’analyse statique pour alerter lors de problèmes de niveau low. Ajoutez pour cela l’option --fail-on-defect=low à la commande pio check:

1
2
3
4
5
6
check-job:
  stage: check
  script:
    - pio run -e DISCO_F
    - pio check -e DISCO_F --skip-packages --fail-on-defect=low
    - pio test --without-uploading --without-testing

À 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 (tp03) avec le nom report03.pdf (le chemin complet vers votre rapport est donc /docs/report03.pdf)