Freigeben über


Tutorial: Visualisieren von IoT-Gerätedaten aus IoT Hub mit dem Azure Web PubSub-Dienst und Azure Functions

In diesem Tutorial erfahren Sie, wie Sie den Azure Web PubSub-Dienst und Azure Functions verwenden, um eine serverlose Anwendung mit Echtzeit-Datenvisualisierung über IoT Hub zu erstellen.

In diesem Tutorial lernen Sie Folgendes:

  • Erstellen einer serverlosen Datenvisualisierungs-App
  • Zusammenarbeiten mit Ein- und Ausgabebindungen von Web PubSub-Funktionen und Azure IoT Hub
  • Lokales Ausführen der Beispielfunktionen

Wichtig

Unformatierte Verbindungszeichenfolgen werden in diesem Artikel nur zu Demonstrationszwecken angezeigt.

Eine Verbindungszeichenfolge enthält die Autorisierungsinformationen, die Ihre Anwendung für den Zugriff auf den Azure Web PubSub-Dienst benötigt. Der Zugriffsschlüssel in der Verbindungszeichenfolge ähnelt einem Stammkennwort für Ihren Dienst. Schützen Sie Ihre Zugriffsschlüssel in Produktionsumgebungen immer sorgfältig. Verwenden Sie Azure Key Vault zum sicheren Verwalten und Rotieren Ihrer Schlüssel und zum Sichern Ihrer Verbindung mit WebPubSubServiceClient.

Geben Sie Zugriffsschlüssel nicht an andere Benutzer weiter, vermeiden Sie das Hartcodieren, und speichern Sie die Schlüssel nicht als Klartext, auf den andere Benutzer Zugriff haben. Rotieren Sie die Schlüssel, wenn Sie glauben, dass sie möglicherweise gefährdet sind.

Voraussetzungen

Sollten Sie über kein Azure-Abonnement verfügen, können Sie zunächst ein kostenloses Azure-Konto erstellen.

Erstellen eines IoT-Hubs

In diesem Abschnitt verwenden Sie die Azure CLI zum Erstellen eines IoT-Hubs und einer Ressourcengruppe. Eine Azure-Ressourcengruppe ist ein logischer Container, in dem Azure-Ressourcen bereitgestellt und verwaltet werden. Ein IoT-Hub fungiert als zentraler Nachrichtenhub für die bidirektionale Kommunikation zwischen Ihrer IoT-Anwendung und den Geräten.

Wenn Sie bereits über einen IoT-Hub in Ihrem Azure-Abonnement verfügen, können Sie diesen Abschnitt überspringen.

So erstellen Sie einen IoT-Hub und eine Ressourcengruppe:

  1. Starten der CLI-App. Kopieren Sie zum Ausführen der CLI-Befehle im restlichen Teil dieses Artikels die Befehlssyntax, fügen Sie diese in Ihre CLI-App ein, bearbeiten Sie die Variablenwerte und drücken Sie Enter.

    • Wählen Sie bei Verwendung von Cloud Shell die Schaltfläche Ausprobieren in den CLI-Befehlen aus, um Cloud Shell in einem geteilten Browserfenster zu starten. Alternativ können Sie Cloud Shell auch in einem separaten Browsertab öffnen.
    • Gehen Sie wie folgt vor, wenn Sie die Azure CLI lokal verwenden: Starten Sie Ihre CLI-Konsolen-App, und melden Sie sich bei der Azure CLI an.
  2. Führen Sie az extension add aus, um die Erweiterung azure-iot zu installieren bzw. auf die aktuelle Version zu aktualisieren.

    az extension add --upgrade --name azure-iot
    
  3. Führen Sie in Ihrer CLI-App den Befehl az group create aus, um eine Ressourcengruppe zu erstellen. Mit dem folgenden Befehl wird eine Ressourcengruppe mit dem Namen MyResourceGroup am Standort eastus erstellt.

    Hinweis

    Optional können Sie einen anderen Standort festlegen. Führen Sie zum Anzeigen der verfügbaren Standorte az account list-locations aus. In dieser Schnellstartanleitung wird eastus verwendet, wie im Beispielbefehl gezeigt.

    az group create --name MyResourceGroup --location eastus
    
  4. Führen Sie den Befehl az iot hub create aus, um einen IoT-Hub zu erstellen. Es kann einige Minuten dauern, bis ein IoT-Hub erstellt wurde.

    YourIotHubName: Ersetzen Sie diesen Platzhalter und die umgebenden geschweiften Klammern im folgenden Befehl durch den Namen, den Sie für Ihren IoT-Hub ausgewählt haben. Der Name eines IoT-Hubs muss in Azure global eindeutig sein. Verwenden Sie im weiteren Verlauf dieser Schnellstartanleitung den Namen des IoT-Hubs für alle Vorkommen dieses Platzhalters.

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

Erstellen einer Web PubSub-Instanz

Wenn Sie bereits über eine Web PubSub-Instanz Hub in Ihrem Azure-Abonnement verfügen, können Sie diesen Abschnitt überspringen.

Führen Sie az extension add aus, um die Erweiterung webpubsub zu installieren bzw. auf die aktuelle Version zu aktualisieren.

az extension add --upgrade --name webpubsub

Verwenden Sie den Azure CLI-Befehl az webpubsub create, um eine Web PubSub-Instanz in der erstellten Ressourcengruppe zu erstellen. Mit dem folgenden Befehl wird eine Kostenlose Web-PubSub-Ressource unter der Ressourcengruppe myResourceGroup in USA, Osten erstellt:

Wichtig

Jede Web PubSub-Ressource muss einen eindeutigen Namen haben. Ersetzen Sie in den folgenden Beispielen <your-unique-resource-name> durch den Namen Ihrer Web PubSub-Instanz.

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

In der Ausgabe dieses Befehls werden die Eigenschaften der neu erstellten Ressource angezeigt. Beachten Sie die beiden folgenden Eigenschaften:

  • Resource Name: Der Name, den Sie oben für den Parameter --name angegeben haben.
  • hostName: Im Beispiel lautet der Hostname <your-unique-resource-name>.webpubsub.azure.com/.

An diesem Punkt ist nur Ihr Azure-Konto zum Ausführen von Vorgängen für die neue Ressource autorisiert.

Erstellen und lokales Ausführen der Funktionen

  1. Erstellen Sie einen leeren Ordner für das Projekt, und führen Sie dann den folgenden Befehl im neuen Ordner aus.

    func init --worker-runtime javascript --model V4
    
  2. Erstellen Sie eine index-Funktion zum Lesen und Hosten einer statischen Webseite für Clients.

    func new -n index -t HttpTrigger
    

    Aktualisieren Sie src/functions/index.js mit dem folgenden Code, der den HTML-Inhalt als statische Website bereitstellt.

    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. Erstellen Sie eine index.html-Datei unter dem Stammordner.

    <!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. Erstellen Sie eine negotiate-Funktion, die Clients verwenden, um eine Dienstverbindungs-URL und ein Zugriffstoken abzurufen.

    func new -n negotiate -t HttpTrigger
    

    Aktualisieren Sie src/functions/negotiate.js, um WebPubSubConnection zu verwenden, die das generierte Token enthält.

    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. Erstellen Sie eine messagehandler-Funktion zum Generieren von Benachrichtigungen mithilfe der Vorlage "IoT Hub (Event Hub)".

    Unformatierte Verbindungszeichenfolgen werden in diesem Artikel nur zu Demonstrationszwecken angezeigt. Schützen Sie Ihre Zugriffsschlüssel in Produktionsumgebungen immer sorgfältig. Verwenden Sie Azure Key Vault zum sicheren Verwalten und Rotieren Ihrer Schlüssel und zum Sichern Ihrer Verbindung mit WebPubSubServiceClient.

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Aktualisieren Sie src/functions/messagehandler.js mit dem folgenden JSON-Code, um die Web PubSub-Ausgabebindung hinzuzufügen. Wir verwenden die Variable %hubName% als Hubname für eventHubName in IoT und den 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. Aktualisieren Sie die Funktionseinstellungen.

    1. Fügen Sie die Einstellung hubName hinzu, und ersetzen Sie {YourIoTHubName} durch den Hubnamen, den Sie beim Erstellen Ihrer IoT Hub-Instanz verwendet haben.

      func settings add hubName "{YourIoTHubName}"
      
    2. Rufen Sie die Dienstverbindungszeichenfolge für IoT Hub ab.

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

    Legen Sie IOTHubConnectionString fest, wobei Sie <iot-connection-string> durch den Wert ersetzen.

    func settings add IOTHubConnectionString "<iot-connection-string>"
    
    1. Rufen Sie die Verbindungszeichenfolge für Web PubSub ab.
    az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
    

    Legen Sie WebPubSubConnectionString fest, wobei Sie <webpubsub-connection-string> durch den Wert ersetzen.

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

    Hinweis

    Der im Beispiel verwendete Funktionstrigger Azure Event Hub trigger ist von Azure Storage abhängig. Sie können jedoch einen lokalen Speicheremulator verwenden, wenn die Funktion lokal ausgeführt wird. Wird eine Fehlermeldung wie There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid. angezeigt, müssen Sie den Speicheremulator herunterladen und aktivieren.

  7. Führen Sie die Funktion lokal aus.

    Jetzt können Sie Ihre lokale Funktion mit dem folgenden Befehl ausführen.

    func start
    

    Sie können die statische Seite Ihres lokalen Hosts aufrufen, indem Sie Folgendes besuchen: https://localhost:7071/api/index.

Ausführen des Geräts zum Senden von Daten

Registrieren eines Geräts

Ein Gerät muss bei Ihrer IoT Hub-Instanz registriert sein, um eine Verbindung herstellen zu können. Wenn Sie bereits ein Gerät in Ihrem IoT-Hub registriert haben, können Sie diesen Abschnitt überspringen.

  1. Führen Sie den Befehl az iot hub device-identity create in Azure Cloud Shell aus, um die Geräteidentität zu erstellen.

    YourIoTHubName: Ersetzen Sie diesen Platzhalter durch den für Ihren IoT-Hub ausgewählten Namen.

    az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
    
  2. Führen Sie den Befehl Az PowerShell module iot hub device-identity connection-string show in Azure Cloud Shell aus, um die Verbindungszeichenfolge für das soeben registrierte Gerät abzurufen:

    YourIoTHubName: Ersetzen Sie diesen Platzhalter unten durch den Namen, den Sie für Ihren IoT-Hub ausgewählt haben.

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

    Notieren Sie sich die Geräteverbindungszeichenfolge, die wie folgt aussieht:

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

Ausführen der Visualisierungswebsite

Öffnen Sie die Indexseite des Funktionshosts (http://localhost:7071/api/index), um das Echtzeitdashboard anzuzeigen. Registrieren Sie mehrere Geräte. Sie werden sehen, dass das Dashboard mehrere Geräte in Echtzeit aktualisiert. Öffnen Sie mehrere Browser, und Sie werden sehen, dass jede Seite in Echtzeit aktualisiert wird.

Screenshot: Datenvisualisierung für mehrere Geräte mithilfe des Web PubSub-Diensts

Bereinigen von Ressourcen

Falls Sie mit weiteren Schnellstartanleitungen und Tutorials fortfahren möchten, sollten Sie die Ressourcen nicht bereinigen.

Wenn Sie die Ressourcen nicht mehr benötigen, führen Sie den Azure CLI-Befehl az group delete aus, um die Ressourcengruppe und alle dazugehörigen Ressourcen zu löschen:

az group delete --name "myResourceGroup"

Nächste Schritte