Delen via


Zelfstudie: IoT-apparaatgegevens van IoT Hub visualiseren met behulp van de Azure Web PubSub-service en Azure Functions

In deze zelfstudie leert u hoe u de Azure Web PubSub-service en Azure Functions gebruikt om een serverloze toepassing te bouwen met realtime gegevensvisualisatie van IoT Hub.

In deze zelfstudie leert u het volgende:

  • Een serverloze app voor gegevensvisualisatie bouwen
  • Samenwerken met invoer- en uitvoerbindingen van de Web PubSub-functie en Azure IoT Hub
  • De voorbeeldfuncties lokaal uitvoeren

Belangrijk

Onbewerkte verbindingsreeks worden alleen in dit artikel weergegeven voor demonstratiedoeleinden.

Een verbindingsreeks bevat de autorisatiegegevens die nodig zijn voor uw toepassing voor toegang tot de Azure Web PubSub-service. De toegangssleutel in de verbindingsreeks is vergelijkbaar met een hoofdwachtwoord voor uw service. Beveilig uw toegangssleutels altijd in productieomgevingen. Gebruik Azure Key Vault om uw sleutels veilig te beheren en te roteren en uw verbinding te beveiligen.WebPubSubServiceClient

Vermijd het distribueren van toegangssleutels naar andere gebruikers, het coderen ervan of het opslaan van ze ergens in tekst zonder opmaak die toegankelijk is voor anderen. Draai uw sleutels als u denkt dat ze mogelijk zijn aangetast.

Vereisten

  • Een code-editor zoals Visual Studio Code

  • Node.js versie 18.x of hoger.

    Notitie

    Zie de documentatie over runtimeversies van Azure Functions voor meer informatie over de ondersteunde versies van Node.js.

  • Azure Functions Core Tools (v3 of hoger) om Azure Function-apps lokaal uit te voeren en te implementeren in Azure.

  • De Azure CLI voor het beheren van Azure-resources.

Als u geen Azure-abonnement hebt, kunt u een gratis Azure-account maken voordat u begint.

Een IoT Hub maken

In deze sectie gebruikt u Azure CLI om een IoT-hub en een resourcegroep te maken. Een Azure-resourcegroep is een logische container waarin Azure-resources worden geïmplementeerd en beheerd. Een IoT-hub fungeert als een centrale berichtenhub voor bidirectionele communicatie tussen uw IoT-toepassing en de apparaten.

Als u al een IoT-hub in uw Azure-abonnement hebt, kunt u deze sectie overslaan.

Een IoT-hub en een resourcegroep maken:

  1. Start de CLI-app. Als u de CLI-opdrachten in de rest van dit artikel wilt uitvoeren, kopieert u de syntaxis van de opdracht, plakt u deze in uw CLI-app, bewerkt u variabelewaarden en drukt u op Enter.

    • Als u Cloud Shell gebruikt, selecteert u de knop Uitproberen in de CLI-opdrachten om Cloud Shell te starten in een gesplitst browservenster. U kunt de Cloud Shell ook openen in een afzonderlijk browsertabblad.
    • Als u Azure CLI lokaal gebruikt, start u uw CLI-console-app en meldt u zich aan bij Azure CLI.
  2. Voer az extension add uit om de azure-iot-extensie te installeren of bij te werken naar de huidige versie.

    az extension add --upgrade --name azure-iot
    
  3. Voer in uw CLI-app de opdracht az group create uit om een resourcegroep te maken. Met de volgende opdracht wordt een resourcegroep met de naam MyResourceGroup gemaakt op de locatie VS - oost.

    Notitie

    U kunt desgewenst een andere locatie instellen. Als u de beschikbare locaties wilt zien, voert u de opdracht uit az account list-locations. In deze quickstart wordt gebruikgemaakt van eastus , zoals wordt weergegeven in de voorbeeldopdracht.

    az group create --name MyResourceGroup --location eastus
    
  4. Voer de opdracht az iot hub create uit om een IoT-hub te maken. Het kan enkele minuten duren voordat een IoT-hub is gemaakt.

    YourIotHubName. Vervang deze tijdelijke aanduiding en de omringende accolades in de volgende opdracht met behulp van de naam die u hebt gekozen voor uw IoT-hub. De naam van de IoT-hub moet wereldwijd uniek zijn in Azure. Gebruik de naam van uw IoT-hub in de rest van deze quickstart, waar u de tijdelijke aanduiding ook ziet.

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

Een Web PubSub-exemplaar maken

Als u al een Web PubSub-exemplaar in uw Azure-abonnement hebt, kunt u deze sectie overslaan.

Voer az extension add uit om de webpubsub-extensie te installeren of bij te werken naar de huidige versie.

az extension add --upgrade --name webpubsub

Gebruik de azure CLI az webpubsub create command om een Web PubSub te maken in de resourcegroep die u hebt gemaakt. Met de volgende opdracht maakt u een gratis Web PubSub-resource onder resourcegroep myResourceGroup in EastUS:

Belangrijk

Elke Web PubSub-resource moet een unieke naam hebben. Vervang <uw unieke resourcenaam> door de naam van uw Web PubSub in de volgende voorbeelden.

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

In de uitvoer van deze opdracht ziet u eigenschappen van de zojuist gemaakte resource. Let op de onderstaande twee eigenschappen:

  • Resourcenaam: de naam die u hebt opgegeven voor de --name bovenstaande parameter.
  • hostName: In het voorbeeld is <your-unique-resource-name>.webpubsub.azure.com/de hostnaam .

Op dit moment is uw Azure-account de enige die gemachtigd is om bewerkingen uit te voeren op deze nieuwe resource.

De functies lokaal maken en uitvoeren

  1. Maak een lege map voor het project en voer vervolgens de volgende opdracht uit in de nieuwe map.

    func init --worker-runtime javascript --model V4
    
  2. Maak een index functie voor het lezen en hosten van een statische webpagina voor clients.

    func new -n index -t HttpTrigger
    

    Werk src/functions/index.js bij met de volgende code, die de HTML-inhoud als een statische site dient.

    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. Maak een index.html bestand onder de hoofdmap.

    <!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. Maak een negotiate functie die clients gebruiken om een serviceverbindings-URL en toegangstoken op te halen.

    func new -n negotiate -t HttpTrigger
    

    Werk src/functions/negotiate.js bij voor gebruik WebPubSubConnection dat het gegenereerde token bevat.

    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. Maak een messagehandler functie om meldingen te genereren met behulp van de "IoT Hub (Event Hub)" sjabloon.

    Onbewerkte verbindingsreeks worden alleen in dit artikel weergegeven voor demonstratiedoeleinden. Beveilig uw toegangssleutels altijd in productieomgevingen. Gebruik Azure Key Vault om uw sleutels veilig te beheren en te roteren en uw verbinding te beveiligen.WebPubSubServiceClient

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Werk src/functions/messagehandler.js bij om Web PubSub-uitvoerbinding toe te voegen met de volgende json-code. We gebruiken een variabele %hubName% als hubnaam voor zowel IoT eventHubName als Web PubSub-hub.

      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. Werk de functie-instellingen bij.

    1. Voeg instelling toe hubName en vervang deze door {YourIoTHubName} de hubnaam die u hebt gebruikt bij het maken van uw IoT Hub.

      func settings add hubName "{YourIoTHubName}"
      
    2. Haal de serviceverbindingsreeks voor IoT Hub op.

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

    Stel IOTHubConnectionStringde waarde in en vervang deze <iot-connection-string> door de waarde.

    func settings add IOTHubConnectionString "<iot-connection-string>"
    
    1. Haal de verbindingsreeks voor Web PubSub op.
    az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
    

    Stel WebPubSubConnectionStringde waarde in en vervang deze <webpubsub-connection-string> door de waarde.

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

    Notitie

    De Azure Event Hub trigger functietrigger die in het voorbeeld wordt gebruikt, is afhankelijk van Azure Storage, maar u kunt een lokale opslagemulator gebruiken wanneer de functie lokaal wordt uitgevoerd. Als u een fout krijgt, zoals There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid., moet u Opslagemulator downloaden en inschakelen.

  7. Voer de functie lokaal uit.

    U kunt nu uw lokale functie uitvoeren met behulp van de onderstaande opdracht.

    func start
    

    U kunt de statische pagina van uw lokale host bezoeken door naar: https://localhost:7071/api/index.

Het apparaat uitvoeren om gegevens te verzenden

Een apparaat registreren

Een apparaat moet zijn geregistreerd bij uw IoT-hub voordat het verbinding kan maken. Als u al een apparaat hebt geregistreerd in uw IoT-hub, kunt u deze sectie overslaan.

  1. Voer de opdracht az iot hub device-identity create uit in Azure Cloud Shell om de apparaat-id te maken.

    YourIoTHubName: vervang deze tijdelijke aanduiding door de naam die u hebt gekozen voor uw IoT-hub.

    az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
    
  2. Voer de opdracht voor de ioT-hub-apparaat-id-verbindingsreeks van de Az PowerShell-module uit in Azure Cloud Shell om het apparaat op te halen verbindingsreeks voor het apparaat dat u zojuist hebt geregistreerd:

    YourIoTHubName: vervang deze tijdelijke aanduiding door een door u gekozen naam voor de IoT-hub.

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

    Noteer het apparaat verbindingsreeks. Dit ziet er als volgt uit:

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

De visualisatiewebsite uitvoeren

Open de indexpagina van de functiehost: http://localhost:7071/api/index om het realtime dashboard weer te geven. Registreer meerdere apparaten en u ziet dat het dashboard in realtime meerdere apparaten bijwerken. Open meerdere browsers en u ziet dat elke pagina in realtime wordt bijgewerkt.

Schermopname van gegevensvisualisatie van meerdere apparaten met behulp van de Web PubSub-service.

Resources opschonen

Als u van plan bent om verder te gaan met volgende snelstarts en zelfstudies, kunt u deze resources intact laten.

U kunt de opdracht az group delete van Azure CLI gebruiken om de resourcegroep en alle gerelateerde resources te verwijderen wanneer u deze niet meer nodig hebt.

az group delete --name "myResourceGroup"

Volgende stappen