Dans cet article, nous allons aborder les Azure Functions avec un projet concret utilisant une des possibilités de ces Functions.

Présentation des Azure Functions

Tout d’abord, faisons une rapide présentation de ce service Azure. Les Functions sont, comme le décrit Microsoft, une plateforme de calcul serverless basée sur les évènements. Pour expliquer un peu plus, il s’agit d’un conteneur virtuel dans lequel vous pouvez déployer des bouts de code, quel que soit le langage. Pour le moment, sont disponibles les langages suivants : C#, JS, F#, Java, PowerShell, Python, TypeScript. La liste est susceptible d’évoluer, comme les versions supportées. La liste actuelle est disponible à cette adresse : https://docs.microsoft.com/fr-fr/azure/azure-functions/supported-languages

Ces fonctions, comme dit plus haut, s’exécutent lors d’évènements, appelés déclencheurs (Triggers) qui peuvent être de différents types :

Il est possible d’associer/combiner (binding) des fonctions entres elles afin de créer une orchestration plus complexe.
Azure Functions binding

Vous pouvez déployer vos Functions de plusieurs manières (comme pour les App Services en fait), ou même les écrire directement dans l’éditeur en ligne sur le portail Azure.

Pour plus de détails sur tout cela (dont les modes de facturation), je vous renvoie vers la présentation officielle sur le site Azure : https://azure.microsoft.com/fr-fr/services/functions

Présentation de l’exemple de réalisation

Notre exemple sera une fonction déclenchée par un appel Http, qui servira à enregistrer les données de températures d’une VMC double flux.

Description d’une VMC Double flux

Le principe d’une VMC double flux est de renouveler l’air intérieur en extrayant l’air vicié (comme une simple VMC) et de faire rentrer de l’air neuf, en le préchauffant avec l’air extrait grâce à un échangeur thermique. Cela évite les déperditions de chaleur d’un système simple. Cela est très utile pour une maison bien isolée.
Description VMC Double flux

Détail du projet

Mon but ici est de récupérer les données fournies par ma VMC afin de les conserver et de pouvoir en faire un suivi dans le temps (ce qui reste encore à faire). La VMC fournie beaucoup de données mais ici je ne vais m’intéresser qu’aux températures et aux taux d’humidité.

La VMC a des capteurs sur toutes ses entrées/sorties, ce qui fait qu’on va se retrouver avec 4 paires de températures/taux d’humidité, celles de l’entrée d’air extérieur et son rejet, l’air de pulsion dans la maison et son air extrait (voir schéma ci-dessous).
Flux air VMC double flux
Le fonctionnement du projet est le suivant :

  • Les données sont récupérées de la VMC par un script Python hébergé sur un NAS dans le même réseau domestique. Le script est en Python, car la librairie d’accès à la VMC est elle-même en Python (https://github.com/michaelarnauts/comfoconnect).
  • Le script poste ensuite les données à la fonction Azure
  • Ce script tourne comme tâche planifiée sur le NAS, exécutée toutes les 5 minutes
  • La fonction Azure traite et enregistre les données reçues dans une Table Azure

Schéma application

Détails du projet

Script Python

L’envoi des données à la fonction Azure se fait par un POST d’un objet JSON contenant toutes les données. Cet objet ressemble à cela :

{
"temperatureSupply": 156,
"temperatureExtract": 159,
"temperatureExhaust": 56,
"temperatureOutdoor": 45,
"humiditySupply": 33,
"humidityExtract": 42,
"humidityExhaust": 89,
"humidityOutdoor": 67
}

Il s’agit des données brutes retournées par la VMC. On voit que les températures n’ont pas de décimales et les taux d’humidité sont exprimés en pourcentage.

Un traitement sera effectué par la fonction C# Azure afin de stocker des valeurs plus exploitables, c’est à dire des températures avec leur décimale.

Voici juste la partie du code qui envoie la mesure :

 body = {
        'temperatureSupply'   :measure.temperatureSupply,
        'temperatureExtract'  :measure.temperatureExtract,
        'temperatureExhaust'  :measure.temperatureExhaust,
        'temperatureOutdoor'  :measure.temperatureOutdoor,
        'humiditySupply'      :measure.humiditySupply,
        'humidityExtract'     :measure.humidityExtract,
        'humidityExhaust'     :measure.humidityExhaust,
        'humidityOutdoor'     :measure.humidityOutdoor      
        }  

    myurl = "https://zehnder-measures.azurewebsites.net/api/RegisterMeasure?code=<APIKey>"
    req = urllib.request.Request(myurl)
    req.add_header('Content-Type', 'application/json; charset=utf-8')
    jsondata = json.dumps(body)
    jsondataasbytes = jsondata.encode('utf-8')   # needs to be bytes
    req.add_header('Content-Length', len(jsondataasbytes))
    logging.info("send measure to Azure:"+str(jsondataasbytes))
    response = urllib.request.urlopen(req, jsondataasbytes)
    print(response.getcode())
    logging.info("Sending returns code :"+ str(response.getcode()))

On notera que l’url utilisée contient le nom de l’App Service zehnder-measures ainsi que le nom de la fonction RegisterMeasure (voir ci-dessous le code C#). Le paramètre ?code permet d’envoyer la clé API propre à mes Functions. Il est possible de sécuriser autrement l’accès à la fonction, par exemple avec une authentification OAuth.

Je ne vous impose pas le reste du script Python, d’une part parce que je ne suis pas forcément très fier de ce code (je découvre Python), d’autre part parce que ce code n’a pas beaucoup d’intérêt pour cet article. Il récupère les valeurs de la VMC à l’aide de l’API et les envoie à Azure.

Code C# de la fonction

Une Azure Function est simplement une méthode statique dans une classe statique, prenant en paramètres essentiellement le trigger dont elle a besoin, ainsi que des paramètres additionnels.
La fonction est ici codée en C#, en .Net Core 3.1, ce qui impose une version v3 des Functions. Mais pas de panique, c’est juste un paramètre à changer en config.

Voici donc le code de cette méthode

 public static class RegisterMeasure
    {
        [FunctionName("RegisterMeasure")]
        public static async Task Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            [Table("VmcMeasures",Connection = "AzureWebJobsStorage")] IAsyncCollector measureTable,
            ILogger log)
        {
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            VmcMeasure receivedMeasure = null;
            try
            {
                receivedMeasure = JsonConvert.DeserializeObject(requestBody);
            }
            catch (Exception ex)
            {
                log.LogError($"Error when DeserializeObject: {ex.Message}");
            }

            if (receivedMeasure != null)
            {
                receivedMeasure.ProcessData();
                await measureTable.AddAsync(receivedMeasure.ToTableEntity());
                log.LogInformation($"Measure ({receivedMeasure.Id}-{receivedMeasure.CreationTime}) registered");
                return new OkResult();
            }
            return new BadRequestObjectResult("Impossible to register value");
        }
    }

Détaillons donc cette méthode.

Signature de la méthode

La méthode est asynchrone. Elle a un attribut [FunctionName], qui comme son nom l’indique définit le nom que doit avoir la méthode dans Azure, et définira en partie l’url pour l’appeler.

Dans ses paramètres, on retrouve le Trigger, ici un HttpRequest qui a un attribut HttpTrigger pour mieux le définir.

On a ensuite une liaison (binding) avec un autre élément Azure, ici la table dans laquelle on va écrire nos données. Ce paramètre est de type IAsyncCollector<TableEntity>. VmcMeasureTableEntity dérive de TableEntity et représente une ligne de notre table, avec les différentes propriétés de notre objet de mesure (c’est-à-dire les températures et taux d’humidité).

  public class VmcMeasureTableEntity : TableEntity
    {
        public DateTime CreationTime { get; set; }
        public double TemperatureSupply { get; set; }
        public double TemperatureExtract { get; set; }
        public double TemperatureExhaust { get; set; }
        public double TemperatureOutdoor { get; set; }
        public double HumiditySupply { get; set; }
        public double HumidityExtract { get; set; }
        public double HumidityExhaust { get; set; }
        public double HumidityOutdoor { get; set; }
    }

Le paramètre est décoré avec un attribut Table qui indique le nom de la table dans laquelle on va écrire et le nom de la connexion à utiliser. Ce nom de connexion se retrouve dans les paramètres settings.json.

Cela ressemble à ça :

{
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=storagefunctiojns;AccountKey=<Key>==",
}

Le dernier paramètre est un ILogger. Oui, ça sert à logger.

Corps de la méthode

La méthode est très simple. Elle reçoit la requête qui contient l’objet JSON avec les données et le désérialise.

string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
VmcMeasure receivedMeasure = JsonConvert.DeserializeObject<VmcMeasure>(requestBody);

Ensuite, cet objet VmcMeasure est légèrement modifié pour avoir les températures réelles, c’est à dire avec leur décimales. On divise juste par 10 ces valeurs.

receivedMeasure.ProcessData();
public void ProcessData()
        {
            TemperatureSupply = TemperatureSupply / 10d;
            TemperatureExtract = TemperatureExtract / 10d;
            TemperatureExhaust = TemperatureExhaust / 10d;
            TemperatureOutdoor = TemperatureOutdoor / 10d;
        }

La dernière étape consiste à envoyer les données dans la table Azure. L’objet VmcMeasure est d’abord converti en VmcMeasureTableEntity, un simple mapping.

await measureTable.AddAsync(receivedMeasure.ToTableEntity());

Voilà tout ce que fait la méthode. Et c’est bien tout l’intérêt des Azure Functions, avoir un bout de code assez simple, qui s’exécute dans le cloud, facile et rapide à déployer sans avoir besoin de créer un site ASP.Net complet.

Dans un prochain article nous verrons comment déployer ce code sur Azure.

En guise de conclusion, j’ajoute que j’ai ici fait le choix d’utiliser le service Azure Functions car il me permet d’effectuer un traitement sur mes données et qu’une deuxième fonction avec un Minuteur sera utlisée pour récupérer les données d’un thermostat connecté NetAtmo. Mais si on ne voulait qu’envoyer des données de télémétrie (températures, taux d’humidité) on pourrait utiliser le service Azure IOT. Cette autre approche doit être étudiée selon le contexte d’utilisation. Voici un lien vers la documentation Azure IOT : https://docs.microsoft.com/fr-fr/azure/iot-hub/quickstart-send-telemetry-python