Partager via


Tutoriel : Visualiser des données d’appareil IoT depuis IoT Hub à l’aide du service Azure Web PubSub et d’Azure Functions

Dans ce tutoriel, vous apprenez à utiliser le service Azure Web PubSub et Azure Functions pour générer une application serverless avec une visualisation des données en temps réel depuis IoT Hub.

Dans ce tutoriel, vous allez apprendre à :

  • Générer une application de visualisation des données serverless
  • Travailler de concert avec des liaisons d’entrée et de sortie de fonction Web PubSub et un hub Azure IoT
  • Exécuter les exemples de fonctions localement

Prérequis

Si vous n’avez pas d’abonnement Azure, créez un compte gratuit Azure avant de commencer.

Créer un hub IoT

Dans cette section, vous utilisez Azure CLI pour créer un hub IoT et un groupe de ressources. Un groupe de ressources Azure est un conteneur logique dans lequel les ressources Azure sont déployées et gérées. Un hub IoT agit en tant que hub de messages central pour la communication bidirectionnelle entre votre application IoT et les appareils.

Si vous disposez déjà d’un hub IoT dans votre abonnement Azure, vous pouvez ignorer cette section.

Pour créer un hub IoT et un groupe de ressources :

  1. Lancez votre application CLI. Pour utiliser les commandes Common Language Infrastructure (CLI) dans le reste de cet article, copiez la syntaxe de la commande, collez-la dans votre application CLI, modifiez les valeurs des variables et appuyez sur Enter.

    • Si vous utilisez Cloud Shell, sélectionnez le bouton Essayer dans les commandes CLI pour lancer Cloud Shell dans une fenêtre de navigateur partagée. Ou vous pouvez ouvrir Cloud Shell dans un onglet de navigateur distinct.
    • Si vous utilisez Azure CLI localement, démarrez votre application console CLI et connectez-vous à Azure CLI.
  2. Exécutez az extension add pour installer ou mettre à niveau l’extension azure-iot vers la version actuelle.

    az extension add --upgrade --name azure-iot
    
  3. Dans votre application CLI, exécutez la commande az group create pour créer un groupe de ressources. La commande suivante crée un groupe de ressources nommé MyResourceGroup à l’emplacement eastus :

    Notes

    Si vous le souhaitez, vous pouvez définir un autre emplacement. Pour voir les régions disponibles, exécutez az account list-locations. Ce démarrage rapide utilise eastus comme indiqué dans l’exemple de commande.

    az group create --name MyResourceGroup --location eastus
    
  4. Exécutez la commande az iot hub create pour créer un hub IoT. La création de votre hub IoT peut prendre plusieurs minutes.

    YourIotHubName. Remplacez cet espace réservé et les accolades qui l’entourent dans la commande suivante, en utilisant le nom que vous avez choisi pour votre hub IoT. Le nom du hub IoT doit être globalement unique dans Azure. Utilisez le nom de votre hub IoT dans le reste de ce guide de démarrage rapide là où vous voyez l’espace réservé.

    az iot hub create --resource-group MyResourceGroup --name {your_iot_hub_name}
    

Créer une instance Web PubSub

Si vous avez déjà une instance Web PubSub dans votre abonnement Azure, vous pouvez ignorer cette section.

Exécutez la commande az extension add pour installer ou mettre à niveau l’extension webpubsub vers la version actuelle.

az extension add --upgrade --name webpubsub

Utilisez la commande az webpubsub create d’Azure CLI pour créer une instance Web PubSub dans le groupe de ressources que vous avez créé. La commande suivante crée une ressource Web PubSub GRATUITE sous le groupe de ressources myResourceGroup dans la zone EastUS :

Important

Chaque ressource Web PubSub doit avoir un nom unique. Remplacez <your-unique-keyvault-name> par le nom de votre Web PubSub dans les exemples suivants.

az webpubsub create --name "<your-unique-resource-name>" --resource-group "myResourceGroup" --location "EastUS" --sku Free_F1

La sortie de cette commande affiche les propriétés de la ressource que vous venez de créer. Notez les deux propriétés ci-dessous :

  • Nom de la ressource : nom que vous avez fourni au paramètre --name ci-dessus.
  • Nom d’hôte : dans l’exemple, le nom d’hôte est <your-unique-resource-name>.webpubsub.azure.com/.

À ce stade, votre compte Azure est le seul autorisé à effectuer des opérations sur cette nouvelle ressource.

Créer et exécuter les fonctions localement

  1. Créez un dossier vide pour le projet, puis exécutez la commande suivante dans le nouveau dossier.

    func init --worker-runtime javascript --model V4
    
  2. Créez une fonction index pour lire et héberger une page web statique pour les clients.

    func new -n index -t HttpTrigger
    

    Mettez à jour src/functions/index.js avec le code suivant qui sert le contenu HTML en tant que site statique.

    const { app } = require('@azure/functions');
    const { readFile } = require('fs/promises');
    
    app.http('index', {
        methods: ['GET', 'POST'],
        authLevel: 'anonymous',
        handler: async (context) => {
            const content = await readFile('index.html', 'utf8', (err, data) => {
                if (err) {
                    context.err(err)
                    return
                }
            });
    
            return { 
                status: 200,
                headers: { 
                    'Content-Type': 'text/html'
                }, 
                body: content, 
            };
        }
    });
    
  3. Créez un index.html fichier sous le dossier racine.

    <!doctype html>
    
    <html lang="en">
    
    <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0/dist/Chart.min.js" type="text/javascript"
            charset="utf-8"></script>
        <script>
            document.addEventListener("DOMContentLoaded", async function (event) {
                const res = await fetch(`/api/negotiate?id=${1}`);
                const data = await res.json();
                const webSocket = new WebSocket(data.url);
    
                class TrackedDevices {
                    constructor() {
                        // key as the deviceId, value as the temperature array
                        this.devices = new Map();
                        this.maxLen = 50;
                        this.timeData = new Array(this.maxLen);
                    }
    
                    // Find a device temperature based on its Id
                    findDevice(deviceId) {
                        return this.devices.get(deviceId);
                    }
    
                    addData(time, temperature, deviceId, dataSet, options) {
                        let containsDeviceId = false;
                        this.timeData.push(time);
                        for (const [key, value] of this.devices) {
                            if (key === deviceId) {
                                containsDeviceId = true;
                                value.push(temperature);
                            } else {
                                value.push(null);
                            }
                        }
    
                        if (!containsDeviceId) {
                            const data = getRandomDataSet(deviceId, 0);
                            let temperatures = new Array(this.maxLen);
                            temperatures.push(temperature);
                            this.devices.set(deviceId, temperatures);
                            data.data = temperatures;
                            dataSet.push(data);
                        }
    
                        if (this.timeData.length > this.maxLen) {
                            this.timeData.shift();
                            this.devices.forEach((value, key) => {
                                value.shift();
                            })
                        }
                    }
    
                    getDevicesCount() {
                        return this.devices.size;
                    }
                }
    
                const trackedDevices = new TrackedDevices();
                function getRandom(max) {
                    return Math.floor((Math.random() * max) + 1)
                }
                function getRandomDataSet(id, axisId) {
                    return getDataSet(id, axisId, getRandom(255), getRandom(255), getRandom(255));
                }
                function getDataSet(id, axisId, r, g, b) {
                    return {
                        fill: false,
                        label: id,
                        yAxisID: axisId,
                        borderColor: `rgba(${r}, ${g}, ${b}, 1)`,
                        pointBoarderColor: `rgba(${r}, ${g}, ${b}, 1)`,
                        backgroundColor: `rgba(${r}, ${g}, ${b}, 0.4)`,
                        pointHoverBackgroundColor: `rgba(${r}, ${g}, ${b}, 1)`,
                        pointHoverBorderColor: `rgba(${r}, ${g}, ${b}, 1)`,
                        spanGaps: true,
                    };
                }
    
                function getYAxy(id, display) {
                    return {
                        id: id,
                        type: "linear",
                        scaleLabel: {
                            labelString: display || id,
                            display: true,
                        },
                        position: "left",
                    };
                }
    
                // Define the chart axes
                const chartData = { datasets: [], };
    
                // Temperature (ºC), id as 0
                const chartOptions = {
                    responsive: true,
                    animation: {
                        duration: 250 * 1.5,
                        easing: 'linear'
                    },
                    scales: {
                        yAxes: [
                            getYAxy(0, "Temperature (ºC)"),
                        ],
                    },
                };
                // Get the context of the canvas element we want to select
                const ctx = document.getElementById("chart").getContext("2d");
    
                chartData.labels = trackedDevices.timeData;
                const chart = new Chart(ctx, {
                    type: "line",
                    data: chartData,
                    options: chartOptions,
                });
    
                webSocket.onmessage = function onMessage(message) {
                    try {
                        const messageData = JSON.parse(message.data);
                        console.log(messageData);
    
                        // time and either temperature or humidity are required
                        if (!messageData.MessageDate ||
                            !messageData.IotData.temperature) {
                            return;
                        }
                        trackedDevices.addData(messageData.MessageDate, messageData.IotData.temperature, messageData.DeviceId, chartData.datasets, chartOptions.scales);
                        const numDevices = trackedDevices.getDevicesCount();
                        document.getElementById("deviceCount").innerText =
                            numDevices === 1 ? `${numDevices} device` : `${numDevices} devices`;
                        chart.update();
                    } catch (err) {
                        console.error(err);
                    }
                };
            });
        </script>
        <style>
            body {
                font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
                padding: 50px;
                margin: 0;
                text-align: center;
            }
    
            .flexHeader {
                display: flex;
                flex-direction: row;
                flex-wrap: nowrap;
                justify-content: space-between;
            }
    
            #charts {
                display: flex;
                flex-direction: row;
                flex-wrap: wrap;
                justify-content: space-around;
                align-content: stretch;
            }
    
            .chartContainer {
                flex: 1;
                flex-basis: 40%;
                min-width: 30%;
                max-width: 100%;
            }
    
            a {
                color: #00B7FF;
            }
        </style>
    
        <title>Temperature Real-time Data</title>
    </head>
    
    <body>
        <h1 class="flexHeader">
            <span>Temperature Real-time Data</span>
            <span id="deviceCount">0 devices</span>
        </h1>
        <div id="charts">
            <canvas id="chart"></canvas>
        </div>
    </body>
    
    </html>
    
  4. Créez une fonction negotiate que les clients utilisent pour obtenir une URL de connexion de service et un jeton d’accès.

    func new -n negotiate -t HttpTrigger
    

    Mise à jour src/functions/negotiate.js pour l’utiliser WebPubSubConnection qui contient le jeton généré.

    const { app, input } = require('@azure/functions');
    
    const connection = input.generic({
        type: 'webPubSubConnection',
        name: 'connection',
        hub: '%hubName%'
    });
    
    app.http('negotiate', {
        methods: ['GET', 'POST'],
        authLevel: 'anonymous',
        extraInputs: [connection],
        handler: async (request, context) => {
            return { body: JSON.stringify(context.extraInputs.get('connection')) };
        },
    });
    
  5. Créez une fonction messagehandler pour générer des notifications en utilisant le modèle "IoT Hub (Event Hub)".

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Mettez à jour src/functions/messagehandler.js pour ajouter la liaison de sortie Web PubSub avec le code json suivant. Nous utilisons la variable %hubName% comme nom de hub pour l’eventHubName IoT et le hub Web PubSub.

      const { app, output } = require('@azure/functions');
      
      const wpsAction = output.generic({
          type: 'webPubSub',
          name: 'action',
          hub: '%hubName%'
      });
      
      app.eventHub('messagehandler', {
          connection: 'IOTHUBConnectionString',
          eventHubName: '%hubName%',
          cardinality: 'many',
          extraOutputs: [wpsAction],
          handler: (messages, context) => {
              var actions = [];
              if (Array.isArray(messages)) {
                  context.log(`Event hub function processed ${messages.length} messages`);
                  for (const message of messages) {
                      context.log('Event hub message:', message);
                      actions.push({
                          actionName: "sendToAll",
                          data: JSON.stringify({
                              IotData: message,
                              MessageDate: message.date || new Date().toISOString(),
                              DeviceId: message.deviceId,
                          })});
                  }
              } else {
                  context.log('Event hub function processed message:', messages);
                  actions.push({
                      actionName: "sendToAll",
                      data: JSON.stringify({
                          IotData: message,
                          MessageDate: message.date || new Date().toISOString(),
                          DeviceId: message.deviceId,
                      })});
              }
              context.extraOutputs.set(wpsAction, actions);
          }
      });
      
  6. Mettez à jour les paramètres de fonction.

    1. Ajoutez un paramètre hubName et remplacez {YourIoTHubName} par le nom de hub que vous avez utilisé lors de la création de votre IoT Hub.

      func settings add hubName "{YourIoTHubName}"
      
    2. Obtenez la chaîne de connexion de service de IoT Hub.

    az iot hub connection-string show --policy-name service --hub-name {YourIoTHubName} --output table --default-eventhub
    

    Définissez IOTHubConnectionString, en remplaçant <iot-connection-string> par la valeur.

    func settings add IOTHubConnectionString "<iot-connection-string>"
    
    1. Obtenez la chaîne de connexion pour Web PubSub.
    az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
    

    Définissez WebPubSubConnectionString, en remplaçant <webpubsub-connection-string> par la valeur.

    func settings add WebPubSubConnectionString "<webpubsub-connection-string>"
    

    Notes

    Le déclencheur de la fonction Azure Event Hub trigger utilisé dans l’exemple a une dépendance vis-à-vis de Stockage Azure, mais vous pouvez utiliser l’émulateur de stockage local quand la fonction s’exécute localement. Si vous rencontrez une erreur comme There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid., vous devez télécharger et activer l’émulateur de stockage.

  7. Exécutez la fonction localement.

    Vous pouvez maintenant exécuter votre fonction locale par commande ci-dessous.

    func start
    

    Vous pouvez consulter la page statique de votre hôte local en visitant : https://localhost:7071/api/index.

Exécuter l’appareil pour envoyer des données

Inscrire un appareil

Un appareil doit être inscrit dans votre hub IoT pour pouvoir se connecter. Si vous disposez déjà d’un appareil inscrit dans votre hub IoT, vous pouvez ignorer cette section.

  1. Exécutez la commande az iot hub device-identity create dans Azure Cloud Shell pour créer l’identité d’appareil.

    YourIoTHubName : remplacez cet espace réservé par le nom que vous avez choisi pour votre hub IoT.

    az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
    
  2. Exécutez la commande Az PowerShell module iot hub device-identity connection-string show dans Azure Cloud Shell pour obtenir la chaîne de connexion de l’appareil que vous venez d’inscrire :

    YourIoTHubName : Remplacez l’espace réservé ci-dessous par le nom que vous avez choisi pour votre hub IoT.

    az iot hub device-identity connection-string show --hub-name {YourIoTHubName} --device-id simDevice --output table
    

    Notez la chaîne de connexion de l’appareil, qui ressemble à ce qui suit :

    HostName={YourIoTHubName}.azure-devices.net;DeviceId=simDevice;SharedAccessKey={YourSharedAccessKey}

Exécuter le site web de visualisation

Ouvrez la page d’index de l’hôte de fonction : http://localhost:7071/api/index pour afficher le tableau de bord en temps réel. Inscrivez plusieurs appareils et vous verrez le tableau de bord mettre à jour plusieurs appareils en temps réel. Ouvrez plusieurs navigateurs et vous verrez que chaque page est mise à jour en temps réel.

Screenshot of multiple devices data visualization using Web PubSub service.

Nettoyer les ressources

Si vous prévoyez d’utiliser d’autres démarrages rapides et didacticiels, il peut être utile de conserver ces ressources.

Quand vous n’en avez plus besoin, vous pouvez utiliser la commande Azure CLI az group delete pour supprimer le groupe de ressources ainsi que toutes les ressources associées :

az group delete --name "myResourceGroup"

Étapes suivantes