Dela via


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 självstudien lär du dig att:

  • Skapa en serverlös datavisualiseringsapp
  • Arbeta tillsammans med indata- och utdatabindningar för Web PubSub-funktioner och Azure IoT Hub
  • 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 ditt program ska få åtkomst till Azure Web PubSub-tjänsten. Åtkomstnyckeln i anslutningssträng liknar ett rotlösenord för din tjänst. Skydda alltid dina åtkomstnycklar i produktionsmiljöer. Använd Azure Key Vault för att hantera och rotera dina nycklar på ett säkert sätt och skydda anslutningen med WebPubSubServiceClient.

Undvik att distribuera åtkomstnycklar till andra användare, hårdkoda dem eller spara dem var som helst i oformaterad text som är tillgänglig för andra. Rotera dina nycklar om du tror att de har komprometterats.

Förutsättningar

  • En kodredigerare, till exempel Visual Studio Code

  • Node.js version 18.x eller senare.

    Kommentar

    Mer information om vilka versioner av Node.js som stöds finns i dokumentationen om Azure Functions-körningsversioner.

  • Azure Functions Core Tools (v3 eller senare rekommenderas) för att köra Azure Function-appar lokalt och distribuera till Azure.

  • Azure CLI för att hantera Azure-resurser.

Om du inte har en Azure-prenumeration skapar du ett kostnadsfritt Azure-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 container där Azure-resurser distribueras och hanteras. En IoT-hubb fungerar som en central meddelandehubb för dubbelriktad kommunikation mellan ditt IoT-program 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. Om du vill köra CLI-kommandona i resten av den här artikeln kopierar du kommandosyntaxen, klistrar in den i CLI-appen, redigerar variabelvärden och trycker på Enter.

    • Om du använder Cloud Shell väljer du knappen Prova 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 startar du CLI-konsolappen och loggar 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 CLI-appen kör du kommandot az group create för att skapa en resursgrupp. Följande kommando skapar en resursgrupp med namnet MyResourceGroup på platsen eastus .

    Kommentar

    Du kan också ange en annan plats. Om du vill se tillgängliga platser kör du az account list-locations. Den här snabbstarten använder eastus enligt exempelkommandot.

    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 platshållaren och de omgivande klammerparenteserna i följande kommando med det namn du valde för din IoT-hubb. Ett IoT-hubbnamn måste vara globalt unikt i Azure. Använd ditt IoT Hub-namn i resten av den här snabbstarten var du än ser platshållaren.

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

Skapa en Web PubSub-instans

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

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

az extension add --upgrade --name webpubsub

Använd kommandot Azure CLI az webpubsub create för att skapa en Web PubSub i resursgruppen som 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. Ersätt <ditt unika resursnamn> med 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

Utdata från det här kommandot visar egenskaperna för den nyligen skapade resursen. Anteckna de två egenskaperna som visas nedan:

  • Resursnamn: Det namn som du angav i parametern --name ovan.
  • hostName: I exemplet är <your-unique-resource-name>.webpubsub.azure.com/värdnamnet .

I det här läget är ditt Azure-konto det enda som har behörighet att utföra åtgärder på den nya resursen.

Skapa och köra 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 hanterar 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 under 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 hämta en url för tjänstanslutning och åtkomsttoken.

    func new -n negotiate -t HttpTrigger
    

    Uppdatera src/functions/negotiate.js för användning 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. Skydda alltid dina åtkomstnycklar i produktionsmiljöer. Använd Azure Key Vault för att hantera och rotera dina nycklar på ett säkert sätt och skydda anslutningen med WebPubSubServiceClient.

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Uppdatera src/functions/messagehandler.js för att lägga till Web PubSub-utdatabindning 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 hubbnamn som du använde när du skapade din IoT Hub.

      func settings add hubName "{YourIoTHubName}"
      
    2. Hämta tjänstanslutningssträngen för IoT Hub.

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

    Ange IOTHubConnectionStringoch 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
    

    Ange WebPubSubConnectionStringoch ersätt <webpubsub-connection-string> med värdet.

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

    Kommentar

    Funktionsutlösaren Azure Event Hub trigger som används i exemplet är beroende av Azure Storage, men du kan använda en lokal lagringsemulator 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 den lokala värdstatiska sidan genom att besöka: https://localhost:7071/api/index.

Kör enheten för att skicka data

Registrera en enhet

En enhet måste vara registrerad vid 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 enheten anslutningssträng 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
    

    Anteckna enhetens anslutningssträng, som ser ut så här:

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

Kör visualiseringswebbplatsen

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

Skärmbild av datavisualisering för flera enheter med hjälp av Web PubSub-tjänsten.

Rensa resurser

Om du planerar att fortsätta med efterföljande snabbstarter och självstudier kan du lämna kvar de här resurserna.

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

az group delete --name "myResourceGroup"

Nästa steg