Condividi tramite


Esercitazione: Visualizzare i dati dei dispositivi IoT da hub IoT usando il servizio Web PubSub di Azure e Funzioni di Azure

In questa esercitazione si apprenderà come usare il servizio Web PubSub di Azure e Funzioni di Azure per creare un'applicazione serverless con visualizzazione dei dati in tempo reale da hub IoT.

In questa esercitazione apprenderai a:

  • Creare un'app di visualizzazione dei dati serverless
  • Collaborare con le associazioni di input e output della funzione Web PubSub e l'hub IoT di Azure
  • Eseguire le funzioni di esempio in locale

Prerequisiti

Se non si ha una sottoscrizione di Azure, creare un account Azure gratuito prima di iniziare.

Creare un hub IoT

In questa sezione si usa l'interfaccia della riga di comando di Azure per creare un hub IoT e un gruppo di risorse. Un gruppo di risorse di Azure è un contenitore logico in cui le risorse di Azure vengono distribuite e gestite. Un hub IoT funge da hub messaggi centrale per la comunicazione bidirezionale tra l'applicazione IoT e i dispositivi.

Se si ha già un hub IoT nella sottoscrizione di Azure, è possibile ignorare questa sezione.

Per creare un hub IoT e un gruppo di risorse:

  1. Avviare l'app dell'interfaccia della riga di comando. Per eseguire i comandi dell'interfaccia della riga di comando nel resto di questo articolo, copiare la sintassi del comando, incollarla nell'app dell'interfaccia della riga di comando, modificare i valori delle variabili e premere Enter.

    • Se si usa Cloud Shell, selezionare il pulsante Prova nel comando dell'interfaccia della riga di comando per avviare Cloud Shell in una finestra divisa del browser. In alternativa, è possibile aprire Cloud Shell in una scheda separata del browser.
    • Se si usa l'interfaccia della riga di comando di Azure in locale, avviare l'app console dell'interfaccia della riga di comando e accedere all'interfaccia della riga di comando di Azure.
  2. Eseguire az extension add per installare o aggiornare l'estensione azure-iot alla versione corrente.

    az extension add --upgrade --name azure-iot
    
  3. Nell'app dell'interfaccia della riga di comando eseguire il comando az group create per creare un gruppo di risorse. Il comando seguente crea un gruppo denominato MyResourceGroup nella posizione eastus.

    Nota

    Facoltativamente, è possibile impostare una posizione diversa. Per visualizzare i percorsi disponibili, eseguire az account list-locations. Questa guida introduttiva usa eastus come illustrato nel comando di esempio.

    az group create --name MyResourceGroup --location eastus
    
  4. Eseguire il comando az iot hub create per creare un hub IoT. La creazione di un hub IoT potrebbe richiedere alcuni minuti.

    YourIotHubName. Sostituire questo segnaposto e le parentesi graffe circostanti nel comando seguente, usando il nome scelto per l'hub IoT. Un nome dell'hub IoT deve essere univoco a livello globale in Azure. Usare il nome dell'hub IoT nel resto di questa guida introduttiva ovunque venga visualizzato il segnaposto.

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

Creare un'istanza di Web PubSub

Se nella sottoscrizione di Azure è già presente un'istanza Web PubSub, è possibile ignorare questa sezione.

Eseguire az extension add per installare o aggiornare l'estensione webpubsub alla versione corrente.

az extension add --upgrade --name webpubsub

Usare il comando az webpubsub create dell'interfaccia della riga di comando di Azure per creare un Web PubSub nel gruppo di risorse creato. Il comando seguente crea una risorsa Web PubSub gratuita nel gruppo di risorse myResourceGroup in EastUS:

Importante

Ogni risorsa Web PubSub deve avere un nome univoco. Sostituire <your-unique-resource-name> con il nome di Web PubSub negli esempi seguenti.

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

L'output di questo comando mostra le proprietà della risorsa appena creata. Prendere nota delle due proprietà elencate di seguito:

  • Nome risorsa: nome specificato al --name parametro precedente.
  • hostName: nell'esempio il nome host è <your-unique-resource-name>.webpubsub.azure.com/.

A questo punto, l'account Azure è l'unico autorizzato a eseguire qualsiasi operazione su questa nuova risorsa.

Creare ed eseguire le funzioni in locale

  1. Creare una cartella vuota per il progetto e quindi eseguire il comando seguente nella nuova cartella.

    func init --worker-runtime javascript --model V4
    
  2. Creare una index funzione per leggere e ospitare una pagina Web statica per i client.

    func new -n index -t HttpTrigger
    

    Eseguire l'aggiornamento src/functions/index.js con il codice seguente, che funge da sito statico per il contenuto HTML.

    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. Creare un index.html file nella cartella radice.

    <!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. Creare una negotiate funzione usata dai client per ottenere un URL di connessione del servizio e un token di accesso.

    func new -n negotiate -t HttpTrigger
    

    Aggiornare src/functions/negotiate.js per usare WebPubSubConnection che contiene il token generato.

    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. Creare una messagehandler funzione per generare notifiche usando il "IoT Hub (Event Hub)" modello .

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Aggiornare src/functions/messagehandler.js per aggiungere l'associazione di output Web PubSub con il codice JSON seguente. La variabile %hubName% viene usata come nome dell'hub sia per l'hub IoT eventHubName che per l'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. Aggiornare le impostazioni della funzione.

    1. Aggiungere hubName l'impostazione e sostituire {YourIoTHubName} con il nome dell'hub usato durante la creazione del hub IoT.

      func settings add hubName "{YourIoTHubName}"
      
    2. Ottenere la stringa di Connessione del servizio per hub IoT.

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

    Impostare IOTHubConnectionString, sostituendo <iot-connection-string> con il valore .

    func settings add IOTHubConnectionString "<iot-connection-string>"
    
    1. Ottenere la stringa di Connessione ion per Web PubSub.
    az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
    

    Impostare WebPubSubConnectionString, sostituendo <webpubsub-connection-string> con il valore .

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

    Nota

    Il Azure Event Hub trigger trigger di funzione usato nell'esempio ha dipendenza da Archiviazione di Azure, ma è possibile usare un emulatore di archiviazione locale quando la funzione è in esecuzione in locale. Se viene visualizzato un errore, There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid.ad esempio , sarà necessario scaricare e abilitare Archiviazione Emulator.

  7. Eseguire la funzione in locale.

    A questo momento è possibile eseguire la funzione locale tramite il comando seguente.

    func start
    

    È possibile visitare la pagina statica dell'host locale visitando: https://localhost:7071/api/index.

Eseguire il dispositivo per inviare dati

Registrazione di un dispositivo

È necessario registrare un dispositivo con l'hub IoT perché questo possa connettersi. Se è già stato registrato un dispositivo nell'hub IoT, è possibile ignorare questa sezione.

  1. Eseguire il comando az iot hub device-identity create in Azure Cloud Shell per creare l'identità del dispositivo.

    YourIoTHubName: sostituire questo segnaposto con il nome scelto per l'hub IoT.

    az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
    
  2. Eseguire il comando Az PowerShell module iot hub device-identity connection-string show in Azure Cloud Shell per ottenere il dispositivo stringa di connessione per il dispositivo appena registrato:

    YourIoTHubName: sostituire questo segnaposto con il nome scelto per l'hub IoT.

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

    Prendere nota del stringa di connessione del dispositivo, simile al seguente:

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

  • Per ottenere risultati più rapidi, simulare i dati relativi alla temperatura usando il simulatore Raspberry Pi Azure IoT Online. Incollare il stringa di connessione del dispositivo e selezionare il pulsante Esegui.

  • Se si dispone di un sensore Raspberry Pi fisico e BME280, è possibile misurare e segnalare valori reali di temperatura e umidità seguendo l'esercitazione Connessione Raspberry Pi a hub IoT di Azure (Node.js).

Eseguire il sito Web di visualizzazione

Aprire la pagina di indice dell'host della funzione: http://localhost:7071/api/index per visualizzare il dashboard in tempo reale. Registrare più dispositivi e il dashboard aggiorna più dispositivi in tempo reale. Aprire più browser e si noterà che ogni pagina viene aggiornata in tempo reale.

Screenshot of multiple devices data visualization using Web PubSub service.

Pulire le risorse

Se si prevede di usare le guide introduttive e le esercitazioni successive, è consigliabile non cancellare le risorse create.

Quando non sono più necessari, è possibile rimuovere il gruppo di risorse e tutte le risorse correlate tramite il comando az group delete dell'interfaccia della riga di comando di Azure:

az group delete --name "myResourceGroup"

Passaggi successivi