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 | |
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
SensorDataServerest 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
SensorDataServerest 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
BLEqui 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
Gapqui 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 :
#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::onEventsToProcessqui permet de signaler à l’application quand des événements de l’API doivent être traités et l’API fournit également une méthodeble::BLE::processEventspour traiter ces événements. Ce mécanisme est réalisé pour votre application dans la classeGAPDevice(voir les méthodesGAPDevice::start()etGAPDevice::scheduleBleEvents()). -
D’autres opérations dans les classes
BLEetGapsont 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 classeble::Gap, cet enregistrement se fait avec la méthodeble::Gap::setEventHandler(). La classeGAPDevicehérite de la classeble::Gap::EventHandleret dans la méthodeGAPDevice::start(), nous pouvons enregistrer l’instance deGAPDevicecourante comme traitant les événements générés par la classeble::Gap. Afin de traiter un événement généré par la classeble::Gap, il suffit de redéfinir une des méthodes de la classeble::Gap::EventHandlerdans la classeGAPDevice- 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éthodeGAPDevice::onInitComplete(). Cette méthode est une méthode abstraite qui devra être redéfinie par les classesBLEBroadcasteretBLEObserverhéritant deGAPDevice. 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::BLEsingleton, les méthodes de la classeGAPDevicefont appel à la méthodeble::BLE::Instance(). Afin d’obtenir une référence sur l’instanceble::Gapsingleton, les méthodes de la classeGAPDevicefont appel à la méthodeble::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
startAdvertisingqui est appelée lorsque l’initialisation du dispositif a été effectuée avec succès. - La classe définit une méthode publique
setAdvertisementPayloadqui 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
setServiceDataqui 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 typeuint8_tde la taille maximale des données d’advertising (définie parble::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
errordu paramètre de typeble::BLE::InitializationCompleteCallbackContextpassé à l’appel de la méthode contient l’identifiant de l’erreur. Si l’initialisation s’est déroulée sans erreur, alors le champerrorcontient la valeurBLE_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 classeGAPdevice. Afin de démarrer l’advertising, vous devez donc utiliser la méthodeGAPDevice::scheduleCallen spécifiant la méthodeBLEBroadcaster::startAdvertisingen 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 estadvertising_type_t::NON_CONNECTABLE_UNDIRECTEDet l’intervalle d’advertising est un paramètre de votre application. - Le champ Flags (AD Type
0x01) doit être initialisé en appelant la méthodeble::AdvertisingDataBuilder::setFlags. Les flags à enclencher dans notre application sontble::adv_data_flags_t::LE_GENERAL_DISCOVERABLEetble::adv_data_flags_t::BREDR_NOT_SUPPORTED. - Le champ Name (AD Type
0x09) doit être initialisé en appelant la méthodeble::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’attributble::AdvertisingDataBuilder- doivent être mises à jour à l’aide de la méthodeGap::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 :
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_encodeetuint32_encodedéfinies dans la classeGAPDevice. 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
setServiceDatadoit être appelée. L’UUID du service à utiliser estGattService::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
startScanningqui est appelée lorsque l’initialisation du dispositif a été effectuée avec succès. - La classe redéfinit la méthode
onAdvertisingReportde la classeGap::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
errordu paramètre de typeble::BLE::InitializationCompleteCallbackContextpassé à l’appel de la méthode contient l’identifiant de l’erreur. Si l’initialisation s’est déroulée sans erreur, alors le champerrorcontient la valeurBLE_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::scheduleCallen spécifiant la méthodeBLEBroadcaster::startScanningen 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éthodeble::AdvertisingReportEvent::getPayload. - À l’aide de cette instance de
ble::AdvertisingDataParser, vous pouvez obtenir les AD Structures successives, en interrogeantble::AdvertisingDataParser::hasNextet en appelantble::AdvertisingDataParser::next. La méthodenextretourne une instance deble::AdvertisingDataParser::element_t, qui correspond à une AD Structure. Cette structure de donnée contient donc un type dans le champtypeet des données dans le champvalue. Dans votre code, vous devez donc tester les différents types deAD Structurereçus, qui sontble::adv_data_type_t::COMPLETE_LOCAL_NAME,ble::adv_data_type_t::COMPLETE_LIST_16BIT_SERVICE_IDSetble::adv_data_type_t::SERVICE_DATA. - Le type
ble::adv_data_type_t::COMPLETE_LOCAL_NAMEpermet de connaître le nom complet du broadcaster. - Le type
ble::adv_data_type_t::COMPLETE_LIST_16BIT_SERVICE_IDSpermet de connaître la liste de services mis à disposition par le broadcaster. Dans notre cas, cette liste contient un seul service qui estGattService::UUID_ENVIRONMENTAL_SERVICE. Vous pouvez utiliser la classeUUIDafin de vérifier l’identifiant du service. - Le type
ble::adv_data_type_t::SERVICE_DATApermet 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’UUIDdu 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
DataMailboxafin 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 | |
À 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)