Arduino ESP – Azure IoT – FOTA

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 sizevoid *userContextCallback){
  (void)update_state;
  (void)size;

  DynamicJsonDocument deviceTwin(1024);

  switch (update_state)
  {
  case DEVICE_TWIN_UPDATE_COMPLETE:
     DeserializationError e = deserializeJson(deviceTwin, payLoadsize);

     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:

Exporter le binaire du 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.

kbeaugrand/ESP32_AzureIoT_OTA: This project aims to demonstrate how we can use ESP32 with Azure IoT Hub and implement OTA features through the platform. (github.com)

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s