Självstudie: Visualisera IoT-enhetsdata från IoT Hub med hjälp av Azure Web PubSub-tjänsten och Azure Functions

I den här självstudien får du lära dig hur du använder Tjänsten Azure Web PubSub och Azure Functions för att skapa ett serverlöst program med datavisualisering i realtid från IoT Hub.

I den här tutorialen lär du dig följande:

  • Bygg en serverlös datavisualiseringsapp
  • Arbeta tillsammans med Web PubSub-funktionens in- och utdata-bindningar och Azure IoT-hubb
  • Kör exempelfunktionerna lokalt

Viktigt

Råa anslutningssträng visas endast i den här artikeln i demonstrationssyfte.

En anslutningssträng innehåller den auktoriseringsinformation som krävs för att din applikation ska kunna få tillgång till Azure Web PubSub-tjänsten. Åtkomstnyckeln i anslutningssträng liknar ett rotlösenord för din tjänst. I produktionsmiljöer, skydda alltid dina åtkomstnycklar. Använd Azure Key Vault för att hantera och rotera dina nycklar säkert och säkra din anslutning med WebPubSubServiceClient.

Undvik att distribuera åtkomstnycklar till andra användare, hårdkoda dem eller spara dem någonstans i klartext som är åtkomlig för andra. Rotera dina nycklar om du tror att de har komprometterats.

Förutsättningar

Om du inte har något Azure-konto skapar du ett kostnadsfritt konto innan du börjar.

Skapa en IoT-hubb

I det här avsnittet använder du Azure CLI för att skapa en IoT-hubb och en resursgrupp. En Azure-resursgrupp är en logisk behållare i vilken Azure-resurser distribueras och hanteras. En IoT-hubb fungerar som en central meddelandehubb för dubbelriktad kommunikation mellan din IoT-applikation och enheterna.

Om du redan har en IoT-hubb i din Azure-prenumeration kan du hoppa över det här avsnittet.

Så här skapar du en IoT-hubb och en resursgrupp:

  1. Starta DIN CLI-app. För att köra CLI-kommandona i resten av den här artikeln, kopiera kommandosyntaxen, klistra in den i din CLI-app, redigera variabelvärdena och tryck på Enter.

    • Om du använder Cloud Shell, välj knappen Testa på CLI-kommandona för att starta Cloud Shell i ett delat webbläsarfönster. Eller så kan du öppna Cloud Shell på en separat webbläsarflik.
    • Om du använder Azure CLI lokalt, starta din CLI-konsolapp och logga in på Azure CLI.
  2. Kör az extension add för att installera eller uppgradera azure-iot-tillägget till den aktuella versionen.

    az extension add --upgrade --name azure-iot
    
  3. I din CLI-app, kör kommandot az group create för att skapa en resursgrupp. Följande kommando skapar en resursgrupp med namnet MyResourceGroup på platsen eastus .

    Anmärkning

    Du kan också ange en annan plats. För att se tillgängliga platser, kör az account list-locations. Denna snabbstart använder eastus som visas i exempelkörningen.

    az group create --name MyResourceGroup --location eastus
    
  4. Kör kommandot az iot hub create för att skapa en IoT-hubb. Det kan ta några minuter att skapa en IoT-hubb.

    YourIoTHubName. Ersätt denna platshållare och de omgivande måsvingarna i följande kommando med det namn du valde för din IoT-hubb. Ett IoT-hubbenamn måste vara unikt globalt i Azure. Använd ditt IoT-navns namn i resten av detta snabbstartsguide där du ser platshållaren.

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

Skapa en Web PubSub-instans

Om du redan har en Web PubSub-instans i ditt Azure-abonnemang kan du hoppa över det här avsnittet.

Kör az extension add för att installera eller uppgradera webpubsub-tillägget till den senaste versionen.

az extension add --upgrade --name webpubsub

Använd Azure CLI-kommandot az webpubsub create för att skapa en Web PubSub i resursgruppen du har skapat. Följande kommando skapar en Free Web PubSub-resurs under resursgruppen myResourceGroup i EastUS:

Viktigt

Varje Web PubSub-resurs måste ha ett unikt namn. Byt ut <your-unique-resource-name> mot namnet på din Web PubSub i följande exempel.

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

Output från det här kommandot visar egenskaperna för den nyss skapade resursen. Notera de två egenskaperna som listas nedan:

  • Resursnamn: Namnet du angav för --name-parametern ovan.
  • hostName: I exemplet är <your-unique-resource-name>.webpubsub.azure.com/värdnamnet .

För närvarande är ditt Azure-konto det enda som är auktoriserat att utföra några åtgärder på denna nya resurs.

Skapa och kör funktionerna lokalt

  1. Skapa en tom mapp för projektet och kör sedan följande kommando i den nya mappen.

    func init --worker-runtime javascript --model V4
    
  2. Skapa en index funktion för att läsa och vara värd för en statisk webbsida för klienter.

    func new -n index -t HttpTrigger
    

    Uppdatera src/functions/index.js med följande kod, som levererar HTML-innehållet som en statisk webbplats.

    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. Skapa en index.html-fil i rotmappen.

    <!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. Skapa en negotiate funktion som klienter använder för att få en tjänsteanslutnings-URL och åtkomsttoken.

    func new -n negotiate -t HttpTrigger
    

    Uppdatera src/functions/negotiate.js för att använda WebPubSubConnection som innehåller den genererade token.

    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. Skapa en messagehandler funktion för att generera meddelanden med hjälp av mallen "IoT Hub (Event Hub)" .

    Råa anslutningssträng visas endast i den här artikeln i demonstrationssyfte. I produktionsmiljöer, skydda alltid dina åtkomstnycklar. Använd Azure Key Vault för att hantera och rotera dina nycklar säkert och säkra din anslutning med WebPubSubServiceClient.

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Uppdatera src/functions/messagehandler.js för att lägga till Web PubSub output binding med följande JSON-kod. Vi använder variabeln %hubName% som hubbnamn för både IoT eventHubName och 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. Uppdatera funktionsinställningarna.

    1. Lägg till hubName inställning och ersätt {YourIoTHubName} med det hub-namn du använde när du skapade din IoT Hub.

      func settings add hubName "{YourIoTHubName}"
      
    2. Hämta Service Connection String för IoT Hub.

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

    Ställ in IOTHubConnectionString och ersätt <iot-connection-string> med värdet.

    func settings add IOTHubConnectionString "<iot-connection-string>"
    
    1. Hämta Anslutningssträngen för Web PubSub.
    az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
    

    Ställ in WebPubSubConnectionString och ersätt <webpubsub-connection-string> med värdet.

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

    Anmärkning

    En Azure Event Hub trigger funktionstrigger som används i exemplet har beroende på Azure Storage, men du kan använda en lokal lagringsemlulator när funktionen körs lokalt. Om du får ett fel, till exempel There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid., måste du ladda ned och aktivera Lagringsemulatorn.

  7. Kör funktionen lokalt.

    Nu kan du köra din lokala funktion med kommandot nedan.

    func start
    

    Du kan besöka din lokala värds statiska sida genom att gå till: https://localhost:7071/api/index.

Kör enheten för att skicka data

Registrera en enhet

Enheten måste registreras hos din IoT-hubb innan den kan ansluta. Om du redan har en enhet registrerad i din IoT-hubb kan du hoppa över det här avsnittet.

  1. Kör kommandot az iot hub device-identity create i Azure Cloud Shell för att skapa enhetsidentiteten.

    YourIoTHubName: Ersätt den här platshållaren med det namn du valde för din IoT-hubb.

    az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
    
  2. Kör kommandot Az PowerShell-modulen iot hub device-identity connection-string show i Azure Cloud Shell för att hämta enhetsanslutningssträngen för den enhet som du nyss registrerade:

    YourIoTHubName: Ersätt platshållaren nedan med det namn du valde för din IoT-hubb.

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

    Observera anslutningssträngen för enheten, som ser ut så här:

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

Kör visualiseringswebbplatsen

Öppna funktionsvärdens indexsida: http://localhost:7071/api/index för att visa realtidsdatanpanelen. Registrera flera enheter och du kommer att se att instrumentpanelen uppdaterar flera enheter i realtid. Öppna flera webbläsare så ser du att varje sida uppdateras i realtid.

Skärmbild av visualisering av data från flera enheter med hjälp av Web PubSub-tjänsten.

Rensa resurser

Om du planerar att fortsätta med efterföljande snabbstartsguider och handledningar, kan det vara bra att låta dessa resurser vara kvar.

När det inte längre behövs, kan du använda kommandot az group delete i Azure CLI för att ta bort resursgruppen och alla relaterade resurser.

az group delete --name "myResourceGroup"

Nästa steg