Pour mon premier article sur ce blog, je viens vous parler d’un petit sujet qui m’a intéressé il y a quelques semaines. Etant possesseur d’ESP 32 à la maison et prônant l’utilisation des services IoT Hub de Azure dans le monde professionnel, je me suis attaché particulièrement à ce sujet afin de démontrer la possibilité de ce type de fonctionnalité dans un projet IoT.
FOTA
Si vous travaillez déjà sur des projets IoT, je suis certain que vous connaissez ce terme. Il s’agit de l’acronyme de Firmware Over The Air. Autrement dit de la capacité à mettre à jours votre device IoT (le Firmware) à distance et ceux de manière sécurisée.
Azure IoT Hub
Malheureusement, à l’heure où j’écris ce billet, la solution Azure IoT Hub et le Azure IoT Device SDK, ne permettent pas de gérer ce FOTA pour tout type de devices. En effet ceci est couplé au type de device que vous allez utiliser. Néanmoins Microsoft continue de s’avancer sur ces sujets. Il y a d’ailleurs ces possibilités présentes dans plusieurs solutions packagés:
- Azure Sphere OS
- Azure RTOS
Ces deux solutions facilitent l’intégration de ces fonctionnalités, cependant elles nécessitent un matériel spécifique qui supporte ces OS.
Vue globale
L’idée de ce billet est donc de montrer comment l’on peut opérer un FOTA en utilisant la plateforme IoT Hub de Azure pour des appareils ne supportant pas ces OS, mais présentant un SDK permettant d’opérer un Update. Dans notre cas une puce ESP (Expressif).

Dans cette démonstration, nous allons utiliser le device management de Azure IoT Hub pour déployer une configuration d’appareils permettant de changer les propriétés désirées de nos appareils contenant les informations de firmware que nous souhaitons déployer. L’appareil sera alors notifié de ses changements, pourra télécharger depuis l’espace de stockage son firmware et se mettre à jour.
Prérequis
Pour pouvoir réaliser ce type de POC, j’ai utilisé simplement les SDK Arduino pour Azure IoT device disponibles sur GitHub : Azure/azure-iot-arduino: Azure IoT library for the Arduino (github.com)
La page de README de ce repository explique comment préparer son environnement de développement (basé sur Arduino).
Lorsque vous aurez installé la librairie dans votre IDE Arduino, vous pourrez démarrer à partir de l’exemple iothub_ll_telemetry_sample
Une fois votre sample démarré, il vous faudra configurer les différents secrets nécessaires à l’exécution de votre appareil (iot_configs.h) :
- SSID du wifi
- Clef du Wifi
- Chaine de connexion de votre appareil
Pour s’abonner aux changements des propriétés du jumeau numérique de votre appareil, il vous faudra modifier le code de votre Arduino comme ceci:
#include <Arduino.h>
#include <AzureIoTHub.h>
#include <ArduinoJson.h>
#define FIRMWARE_VERSION "0.0.1"
void IoTDevice_DeviceTwinCallback(DEVICE_TWIN_UPDATE_STATE update_state, const unsigned char *payLoad, size_t size, void *userContextCallback){
(void)update_state;
(void)size;
DynamicJsonDocument deviceTwin(1024);
switch (update_state)
{
case DEVICE_TWIN_UPDATE_COMPLETE:
DeserializationError e = deserializeJson(deviceTwin, payLoad, size);
if(e.code() != e.Ok){
Log_Error("Unable to parse twin: %s", e.c_str());
return;
}
break;
case DEVICE_TWIN_UPDATE_PARTIAL:
return;
}
if (deviceTwin["desired"]["firmware"]["version"] != FIRMWARE_VERSION)
{
OTA(deviceTwin["desired"]["firmware"]["source"]);
}
(void)userContextCallback;
}
En démonstration, voici le code que j’utilise pour effectuer le téléchargement du firmware demandé depuis le storage account :
OTA.h
#include <Arduino.h>
#include <HTTPClient.h>
#include <Update.h>
HTTPClient http;
int contentLength;
const char certificates[] =
/* DigiCert Baltimore Root --Used Globally--*/
// This cert should be used when connecting to Azure IoT on the Azure Cloud available globally. When in doubt, use this cert.
"-----BEGIN CERTIFICATE-----\r\n"
"MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ\r\n"
"RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD\r\n"
"VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX\r\n"
"DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y\r\n"
"ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy\r\n"
"VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr\r\n"
"mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr\r\n"
"IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK\r\n"
"mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\r\n"
"XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy\r\n"
"dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye\r\n"
"jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1\r\n"
"BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3\r\n"
"DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92\r\n"
"9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx\r\n"
"jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0\r\n"
"Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz\r\n"
"ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS\r\n"
"R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\r\n"
"-----END CERTIFICATE-----\r\n";
static void OTA(const char *uri)
{
Log_Debug("Begin request for: %s", uri);
http.begin(uri, certificates);
int statusCode = http.GET();
if (statusCode <= 0)
{
Log_Error("status code is :%d. Exiting OTA!", statusCode);
http.end();
return;
}
int contentLength = http.getSize();
Log_Debug("Trying to start OTA with %d bytes", contentLength);
// Check if there is enough to OTA Update
bool canBegin = Update.begin(contentLength);
// If yes, begin
if (canBegin)
{
Log_Info("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
// No activity would appear on the Serial monitor
// So be patient. This may take 2 - 5mins to complete
size_t written = Update.writeStream(http.getStream());
if (written == contentLength)
{
Log_Info("Written: %d successfully", written);
}
else
{
Log_Error("Written only: %d/%d. Retry?", written, contentLength);
}
if (Update.end())
{
Log_Info("OTA done!");
if (Update.isFinished())
{
Log_Info("Update successfully completed. Rebooting.");
ESP.restart();
}
else
{
Log_Info("Update not finished? Something went wrong!");
}
}
else
{
Log_Error("Error Occurred. Error #%d", Update.getError());
http.end();
}
}
else
{
// not enough space to begin OTA
// Understand the partitions and
// space availability
Log_Error("Not enough space to begin OTA");
http.end();
}
}
Préparation du sample
Maintenant que vous avez les briques de votre appareil permettant de réaliser cet OTA, regardons comment enclencher cela dans le code du sample disponible.
Ajout de l’option au client IoT Hub
Dans votre sample, modifiez le code où votre appareil se connecte à l’IoT Hub, ajoutez-y la ligne suivante :
IoTHubDeviceClient_LL_GetTwinAsync(device_ll_handle, IoTDevice_DeviceTwinCallback, NULL);
Cette commande a pour conséquence de récupérer le contenu du jumeau numérique une fois la connexion établie.
Lancement de votre premier OTA
Une fois le code de votre appareil compilé et téléchargé sur votre appareil, effectuez une seconde compilation avec Arduino IDE afin de récupérer le fichier binaire de votre firmware:

Une fois votre binaire exporté, créé un compte de stockage et un conteneur public (Blob) et téléchargez-y votre firmware.
Créez une nouvelle configuration IoT depuis votre IoT hub :

Résultats
> Debug: Begin request for: https://<storage_account_name>.blob.core.windows.net/<container_name>/ESP32_Device_OTA.ino.bin
> Debug: Trying to start OTA with 1040864 bytes
> Info: Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!
> Info: Written: 1040864 successfully
> Info: OTA done!
> Info: Update successfully completed. Rebooting.
Et voilà!!
Pour compléter cette démonstration, il sera important de penser à séparer votre code Arduino de votre configuration (chaînes de connexions).
Pour cela il suffira de créer un système de fichier dans votre chip Arduino et d’y stocker votre fichier de configuration.
Peut-être l’objet d’un prochain billet ?
En attendant, j’ai réalisé un sample un peu plus complet sous forme de librairie Arduino disponible directement sur Github, permettant de développer rapidement ce type de use case avec une sauvegarde (sur le système de fichier de votre Arduino) des configurations à part de votre firmware.