TP05 : REST API

Dans ce dernier TP, nous modifions la manière d’envoyer les mesures ambiantes vers Adafruit.io. Nous remplaçons l’interface MQTT par un API REST sur le protocole HTTP+TLS
Vous avez deux options :
- Vous pouvez modifier le TP précédent et utiliser une cible de mesure et une autre cible de passerelle BLE → HTTPS.
- Ou vous pouvez regrouper le tout sur la même cible et simplifier votre code en supprimant le lien BLE.
Objectifs du TP
Ce TP a pour but de mettre en pratique une communication HTTPS/REST par WiFi
À la fin de ce TP, les étudiants :
- auront réutilisé le WiFi du TP précédent;
- auront implémenté un client HTTPS/REST sur Mbed OS;
- auront publié des données sur un serveur Internet;
- auront réalisé des analyses statiques de code et corrigé les erreurs rapportées ;
- auront rédigé un journal de travail et déposé le PDF dans le dépôt git.
Les livrables sont :
- un projet git (tp05) dans votre groupe sur gitlab.forge.hefr.ch avec le code du TP ;
- un journal de travail déposé sur gitlab.
Temps accordé : 4 périodes de 45 minutes en classe + travail personnel à la maison
Délai
Le TP doit être rendu au plus tard 6 jours après la séance en classe à 23:59.
Configuration du projet
Configurez votre projet comme pour le TP précédent (fichiers platformio.ini, mbed_app.json et le dossier mbedignore).
Dans le fichier platformio.ini, ajoutez la librairie pour HTTP:
lib_deps =
...
https://github.com/heia-fr/embsys-http.git#main
...
Sécurité TLS
Comme la plupart des sites actuels, Adafruit.io ne permet pas une simple connexion HTTP, mais exige une couche TLS. Cette couche nécessite des certificats X509 pour authentifier le serveur.
Avec les OS des “grosses machines” (PCs ou serveurs), les certificats racines sont fournis par le vendeur et régulièrement mis à jour. Mais avec {mbedos} ce n’est pas le cas et vous devez donner le certificat SSL “root” dans votre code.
Dans notre cas, l’API se connecte sur le port 443 du host “io.adafruit.com” et pour obtenir la chaîne de certificat, vous pouvez utiliser la commande suivante :
openssl s_client -showcerts -connect io.adafruit.com:443 < /dev/null
Vous verrez alors 3 certificats :
/C=US/ST=New York/L=New York/O=Adafruit Industries LLC/CN=*.adafruit.com/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=GeoTrust RSA CA 2018/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
Celui qui nous intéresse, c’est le deuxième (GeoTrust RSA CA 2018).
-----BEGIN CERTIFICATE-----
MIIEizCCA3OgAwIBAgIQBUb+GCP34ZQdo5/OFMRhczANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
...
nQ//jP8BIwrzBAUH5WcBAbmvgWfrKcuv+PyGPqRcc4T55TlzrBnzAzZ3oClo9fTv
O9PuiHMKrC6V6mgi0s2sa/gbXlPCD9Z24XUMxJElwIVTDuKB0Q4YMMlnpN/QChJ4
B0AFsQ+DU0NCO+f78Xf7
-----END CERTIFICATE-----
Question TP05 : REST API/1
Étudiez les détails du certificat et indiquez la date à laquelle échouera le certificat. Est-ce que votre application fonctionnera toujours après cette date ?
Solution
Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
Validity
Not Before: Nov 6 12:23:45 2017 GMT
Not After : Nov 6 12:23:45 2027 GMT
Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=GeoTrust RSA CA 2018
Question TP05 : REST API/2
Est-ce possible que (à cause d’un problème de certificat) votre application cesse même de fonctionner avant la date que vous venez d’indiquer ?
Quelle solution proposez-vous pour régler ce problème ?
Application de test pour HTTP
Pour tester et vous familiariser avec le module HTTPS, vous pouvez recopier le code suivant qui illustre les opérations suivantes :
- Énumération des APs wifi disponibles
- Connexion au SSID configuré dans
mbed_app.json - Connexion en HTTPS avec un certificat à https://httpbin.org/status/418
- Affichage du code de retour
- Affichage du contenu
// Copyright 2022 Haute école d'ingénierie et d'architecture de Fribourg
// SPDX-License-Identifier: Apache-2.0
#include "ESP32Interface.h"
#include "http_response.h"
#include "https_request.h"
#include "mbed.h"
#include "mbed_trace.h"
#include "nsapi.h"
#include "stdio.h"
#include "string.h"
#define TRACE_GROUP "MAIN"
static constexpr size_t MAX_NUMBER_OF_ACCESS_POINTS = 30;
static constexpr int MAX_CONNECTION_RETRY_COUNT = 3;
static const char* get_security_string(nsapi_security_t sec)
{
switch (sec) {
case NSAPI_SECURITY_NONE:
return "None";
case NSAPI_SECURITY_WEP:
return "WEP";
case NSAPI_SECURITY_WPA:
return "WPA";
case NSAPI_SECURITY_WPA2:
return "WPA2";
case NSAPI_SECURITY_WPA_WPA2:
return "WPA/WPA2";
case NSAPI_SECURITY_UNKNOWN:
default:
return "Unknown";
}
}
void wifiScan(NetworkInterface* networkInterface)
{
MBED_ASSERT(networkInterface != nullptr);
MBED_ASSERT(networkInterface->wifiInterface() != nullptr);
WiFiInterface* wifi = networkInterface->wifiInterface();
WiFiAccessPoint ap[MAX_NUMBER_OF_ACCESS_POINTS];
int result = wifi->scan(ap, MAX_NUMBER_OF_ACCESS_POINTS);
if (result <= 0) {
printf("WiFiInterface::scan() failed with return value: %d\r\n", result);
return;
}
printf("%d networks available:\r\n", result);
for (int i = 0; i < result; i++) {
printf(
"Network: %s secured: %s BSSID: %hhX:%hhX:%hhX:%hhx:%hhx:%hhx RSSI: "
"%hhd Ch: %hhd\r\n",
ap[i].get_ssid(),
get_security_string(ap[i].get_security()),
ap[i].get_bssid()[0],
ap[i].get_bssid()[1],
ap[i].get_bssid()[2],
ap[i].get_bssid()[3],
ap[i].get_bssid()[4],
ap[i].get_bssid()[5],
ap[i].get_rssi(),
ap[i].get_channel());
}
printf("\r\n");
}
void PrintNetworkInfo(NetworkInterface* networkInterface)
{
// print the network info
SocketAddress a;
networkInterface->get_ip_address(&a);
tr_info("IP address: %s", a.get_ip_address() ? a.get_ip_address() : "None");
networkInterface->get_netmask(&a);
tr_info("Netmask: %s", a.get_ip_address() ? a.get_ip_address() : "None");
networkInterface->get_gateway(&a);
tr_info("Gateway: %s", a.get_ip_address() ? a.get_ip_address() : "None");
}
nsapi_error_t ConnectWifi(NetworkInterface* networkInterface)
{
tr_debug("Connecting to the network...");
// check if we're already connected
if (networkInterface->get_connection_status() == NSAPI_STATUS_GLOBAL_UP) {
tr_debug("Already connected");
return NSAPI_ERROR_OK;
}
// set the default parameters (such as wifi pwd or sim pin) before connecting
networkInterface->set_default_parameters();
nsapi_error_t rc = NSAPI_ERROR_OK;
for (int retry = 0; retry < MAX_CONNECTION_RETRY_COUNT; retry++) {
rc = networkInterface->connect();
if (rc == NSAPI_ERROR_OK) {
tr_info("Connection Established.");
PrintNetworkInfo(networkInterface);
break;
} else if (rc == NSAPI_ERROR_AUTH_FAILURE) {
tr_info("Authentication Failure.");
break;
} else if (retry > MAX_CONNECTION_RETRY_COUNT) {
break;
} else {
tr_info("Couldn't connect: %d, will retry (%d/%d)",
rc,
retry,
MAX_CONNECTION_RETRY_COUNT);
}
}
return rc;
}
int main()
{
mbed_trace_init();
mbed_trace_config_set(TRACE_ACTIVE_LEVEL_ALL);
tr_info("Starting program");
ESP32Interface esp32;
wifiScan(&esp32); // just for testing
if (ConnectWifi(&esp32) != NSAPI_ERROR_OK) {
tr_error("Could not connect to the network");
return -1;
}
tr_info("Doing request...");
// pass in the root certificates that you trust,
// there is no central CA registry in Mbed OS.
// Note that the certificate is "static" so it does not use
// stack memory.
static const char SSL_CA_PEM[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIEdTCCA12gAwIBAgIJAKcOSkw0grd/MA0GCSqGSIb3DQEBCwUAMGgxCzAJBgNV\n"
"BAYTAlVTMSUwIwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTIw\n"
"MAYDVQQLEylTdGFyZmllbGQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0\n"
"eTAeFw0wOTA5MDIwMDAwMDBaFw0zNDA2MjgxNzM5MTZaMIGYMQswCQYDVQQGEwJV\n"
"UzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UE\n"
"ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZp\n"
"ZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEi\n"
"MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVDDrEKvlO4vW+GZdfjohTsR8/\n"
"y8+fIBNtKTrID30892t2OGPZNmCom15cAICyL1l/9of5JUOG52kbUpqQ4XHj2C0N\n"
"Tm/2yEnZtvMaVq4rtnQU68/7JuMauh2WLmo7WJSJR1b/JaCTcFOD2oR0FMNnngRo\n"
"Ot+OQFodSk7PQ5E751bWAHDLUu57fa4657wx+UX2wmDPE1kCK4DMNEffud6QZW0C\n"
"zyyRpqbn3oUYSXxmTqM6bam17jQuug0DuDPfR+uxa40l2ZvOgdFFRjKWcIfeAg5J\n"
"Q4W2bHO7ZOphQazJ1FTfhy/HIrImzJ9ZVGif/L4qL8RVHHVAYBeFAlU5i38FAgMB\n"
"AAGjgfAwge0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0O\n"
"BBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtV\n"
"rNzXEMIOqYjnME8GCCsGAQUFBwEBBEMwQTAcBggrBgEFBQcwAYYQaHR0cDovL28u\n"
"c3MyLnVzLzAhBggrBgEFBQcwAoYVaHR0cDovL3guc3MyLnVzL3guY2VyMCYGA1Ud\n"
"HwQfMB0wG6AZoBeGFWh0dHA6Ly9zLnNzMi51cy9yLmNybDARBgNVHSAECjAIMAYG\n"
"BFUdIAAwDQYJKoZIhvcNAQELBQADggEBACMd44pXyn3pF3lM8R5V/cxTbj5HD9/G\n"
"VfKyBDbtgB9TxF00KGu+x1X8Z+rLP3+QsjPNG1gQggL4+C/1E2DUBc7xgQjB3ad1\n"
"l08YuW3e95ORCLp+QCztweq7dp4zBncdDQh/U90bZKuCJ/Fp1U1ervShw3WnWEQt\n"
"8jxwmKy6abaVd38PMV4s/KCHOkdp8Hlf9BRUpJVeEXgSYCfOn8J3/yNTd126/+pZ\n"
"59vPr5KW7ySaNRB6nJHGDn2Z9j8Z3/VyVOEVqQdZe4O/Ui5GjLIAZHYcSNPYeehu\n"
"VsyuLAOQ1xk4meTKCRlb/weWsKh/NEnfVqn3sF/tM+2MR7cwA130A4w=\n"
"-----END CERTIFICATE-----\n";
HttpsRequest* request = new HttpsRequest(
&esp32, SSL_CA_PEM, HTTP_GET, "https://httpbin.org/status/418");
HttpResponse* response = request->send();
// TODO : if response is NULL, check response->get_error()
printf("status is %d - %s\n",
response->get_status_code(),
response->get_status_message().c_str());
printf("body is:\n%s\n", response->get_body_as_string().c_str());
delete request;
return 0;
}
Le résultat devrait ressembler à ça :
[INFO][MAIN]: Starting program
X networks available:
Network: OPEN-HEFR secured: None BSSID: 18:8B:45:6:a8:42 RSSI: -76 Ch: 1
Network: PUBLIC-HEFR secured: None BSSID: 18:8B:45:6:a8:43 RSSI: -76 Ch: 1
Network: INFRA-HEFR secured: WPA2 BSSID: 18:8B:45:6:a8:44 RSSI: -76 Ch: 1
Network: eduroam secured: Unknown BSSID: 18:8B:45:6:a8:40 RSSI: -76 Ch: 1
...
[INFO][MAIN]: Connection Established.
[INFO][MAIN]: IP address: xxx.xxx.xxx.xxx
[INFO][MAIN]: Netmask: xxx.xxx.xxx.xxx
[INFO][MAIN]: Gateway: xxx.xxx.xxx.xxx
[INFO][MAIN]: Doing request...
[INFO][TLSW]: Starting TLS handshake with (null)
[INFO][TLSW]: TLS connection to (null) established
[INFO][TLSW]: Certificate verification passed
[INFO][TLSW]: Closing TLS
status is 418 - I'M A TEAPOT
body is:
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`
L’interface REST de Adafruit.io
L’interface REST de Adafruit.io est documenté ici.
Étudiez comment mettre à jour une donnée, expérimentez avec la commande curl donnée en exemple
et codez une fonction avec Mbed OS sur votre cible.
Réalisation
Implémentez une des deux options proposées plus haut :
- Modifier le TP précédent et utiliser une cible de mesure et une autre cible de passerelle BLE → HTTPS.
- Regrouper le tout sur la même cible et simplifier votre code en supprimant le lien BLE.
L’avantage de la première option est qu’il vous suffit de bien implémenter l’héritage de la classe PublisherBase.
L’avantage de la deuxième option est que le code est simplifié et que vous pouvez réaliser la solution avec une seule cible.
À vous de choisir !
Tests et validations
Comme pour le TP précédent, configurez l’analyse statique de code de manière plus stricte.
À ne pas oublier
Gardez toujours en têtes les bonnes pratiques ainsi que les dix commandements du bon programmeur.
- Choisissez de bons noms pour les classes, les méthodes et les variables.
- Implémentez les bibliothèques avec un haut niveau d’abstraction pour pouvoir réutiliser les méthodes dans d’autres projets.
- Faites des “git commit” régulièrement avec de bons commentaires.
- Configurez le CI/CD de gitlab et testez automatiquement le plus de choses possibles.
- Implémentez des tests unitaires.
- Utilisez des assertions dans votre code pour le documenter et le rendre plus robuste.
Journal de travail
- Rédigez un rapport (journal de travail) avec les indications suivantes :
- Une page de titre avec au minimum :
- le nom et le logo officiel de l’école
- le nom du cours : Informatique Embarquée
- le titre de votre document : Travail Pratique 3 : Station météo connectée
- le numéro de votre groupe
- les noms des auteurs (vous) avec la classe dans laquelle vous êtes
- la date à laquelle vous avez terminé le rapport
- éventuellement la version du rapport
- Une introduction pour poser le contexte
- Un résumé des notions que vous avez apprises pendant ce TP en précisant si c’est
- non acquis
- acquis, mais encore à exercer
- parfaitement acquis
- Un résumé des points qui vous semblent importants et que vous devez retenir
- Les réponses aux questions.
- Le code source bien formaté et avec du “syntax highlighting” de votre code source.
- Une conclusion par laquelle vous donnez vos impressions sur le TP, ce que vous avez aimé, ce que vous avez moins aimé, et éventuellement des suggestions pour des changements. Indiquez également le nombre d’heures que vous avez passées, par personne, en dehors des heures de TP en classe.
Important
Déposez votre rapport dans un dossier /docs de votre dépôt git (tp05) avec le nom report05.pdf (le chemin complet vers votre
rapport est donc /docs/report05.pdf)