Samouczek: wizualizowanie danych urządzenia IoT z usługi IoT Hub przy użyciu usługi Azure Web PubSub i usługi Azure Functions

Z tego samouczka dowiesz się, jak używać usługi Azure Web PubSub i usługi Azure Functions do tworzenia aplikacji bezserwerowej z wizualizacją danych w czasie rzeczywistym z usługi IoT Hub.

Z tego samouczka dowiesz się, jak wykonywać następujące czynności:

  • Tworzenie aplikacji do wizualizacji danych bezserwerowych
  • Współpraca z powiązaniami wejściowymi i wyjściowymi funkcji Web PubSub oraz usługą Azure IoT Hub
  • Uruchamianie przykładowych funkcji lokalnie

Wymagania wstępne

  • Edytor kodu, taki jak Visual Studio Code

  • Node.js, wersja 18.x lub nowsza.

    Uwaga

    Aby uzyskać więcej informacji na temat obsługiwanych wersji środowiska Node.js, zobacz dokumentację wersji środowiska uruchomieniowego usługi Azure Functions.

  • Azure Functions Core Tools (w wersji 3 lub nowszej) do uruchamiania aplikacji funkcji platformy Azure lokalnie i wdrażania na platformie Azure.

  • Interfejs wiersza polecenia platformy Azure do zarządzania zasobami platformy Azure.

Jeśli nie masz subskrypcji platformy Azure, przed rozpoczęciem utwórz bezpłatne konto platformy Azure.

Tworzenie centrum IoT

W tej sekcji użyjesz interfejsu wiersza polecenia platformy Azure do utworzenia centrum IoT i grupy zasobów. Grupa zasobów platformy Azure to logiczny kontener przeznaczony do wdrażania zasobów platformy Azure i zarządzania nimi. Centrum IoT działa jako centralne centrum komunikatów na potrzeby dwukierunkowej komunikacji między aplikacją IoT a urządzeniami.

Jeśli masz już centrum IoT w subskrypcji platformy Azure, możesz pominąć tę sekcję.

Aby utworzyć centrum IoT i grupę zasobów:

  1. Uruchom aplikację interfejsu wiersza polecenia. Aby uruchomić polecenia interfejsu wiersza polecenia w pozostałej części tego artykułu, skopiuj składnię polecenia, wklej ją do aplikacji interfejsu wiersza polecenia, edytuj wartości zmiennych i naciśnij klawisz Enter.

    • Jeśli używasz usługi Cloud Shell, wybierz przycisk Wypróbuj w poleceniach interfejsu wiersza polecenia, aby uruchomić usługę Cloud Shell w podzielonym oknie przeglądarki. Możesz też otworzyć usługę Cloud Shell na osobnej karcie przeglądarki.
    • Jeśli używasz interfejsu wiersza polecenia platformy Azure lokalnie, uruchom aplikację konsolową interfejsu wiersza polecenia i zaloguj się do interfejsu wiersza polecenia platformy Azure.
  2. Uruchom polecenie az extension add , aby zainstalować lub uaktualnić rozszerzenie azure-iot do bieżącej wersji.

    az extension add --upgrade --name azure-iot
    
  3. W aplikacji interfejsu wiersza polecenia uruchom polecenie az group create , aby utworzyć grupę zasobów. Następujące polecenie tworzy grupę zasobów o nazwie MyResourceGroup w lokalizacji eastus .

    Uwaga

    Opcjonalnie możesz ustawić inną lokalizację. Aby wyświetlić dostępne lokalizacje, uruchom polecenie az account list-locations. W tym przewodniku Szybki start użyto jednostki eastus , jak pokazano w przykładowym poleceniu.

    az group create --name MyResourceGroup --location eastus
    
  4. Uruchom polecenie az iot hub create, aby utworzyć centrum IoT Hub. Utworzenie centrum IoT Hub może potrwać kilka minut.

    YourIotHubName. Zastąp ten symbol zastępczy i otaczające nawiasy klamrowe w poniższym poleceniu, używając nazwy wybranej dla centrum IoT. Nazwa centrum IoT musi być globalnie unikatowa na platformie Azure. Użyj nazwy centrum IoT w pozostałej części tego przewodnika Szybki start wszędzie tam, gdzie widzisz symbol zastępczy.

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

Tworzenie wystąpienia usługi Web PubSub

Jeśli masz już wystąpienie usługi Web PubSub w subskrypcji platformy Azure, możesz pominąć tę sekcję.

Uruchom polecenie az extension add , aby zainstalować lub uaktualnić rozszerzenie webpubsub do bieżącej wersji.

az extension add --upgrade --name webpubsub

Użyj polecenia az webpubsub create interfejsu wiersza polecenia platformy Azure, aby utworzyć internetowy pubSub w utworzonej grupie zasobów. Następujące polecenie tworzy zasób Free Web PubSub w grupie zasobów myResourceGroup w regionie EastUS:

Ważne

Każdy zasób Web PubSub musi mieć unikatową nazwę. Zastąp <ciąg your-unique-resource-name> nazwą usługi Web PubSub w poniższych przykładach.

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

Dane wyjściowe tego polecenia pokazują właściwości nowo utworzonego zasobu. Zanotuj dwie poniższe właściwości:

  • Nazwa zasobu: nazwa podana powyżej parametru --name .
  • hostName: w przykładzie nazwa hosta to <your-unique-resource-name>.webpubsub.azure.com/.

W tym momencie Twoje konto platformy Azure jest jedynym autoryzowanym do wykonywania jakichkolwiek operacji na tym nowym zasobie.

Tworzenie i uruchamianie funkcji lokalnie

  1. Utwórz pusty folder dla projektu, a następnie uruchom następujące polecenie w nowym folderze.

    func init --worker-runtime javascript --model V4
    
  2. index Utwórz funkcję do odczytu i hostowania statycznej strony internetowej dla klientów.

    func new -n index -t HttpTrigger
    

    Zaktualizuj src/functions/index.js za pomocą następującego kodu, który służy do zawartości HTML jako witryny statycznej.

    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. index.html Utwórz plik w folderze głównym.

    <!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. Utwórz funkcję używaną negotiate przez klientów do uzyskiwania adresu URL połączenia z usługą i tokenu dostępu.

    func new -n negotiate -t HttpTrigger
    

    Zaktualizuj src/functions/negotiate.js , aby używać WebPubSubConnection tego tokenu, który zawiera wygenerowany 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. Utwórz funkcję do generowania messagehandler powiadomień przy użyciu szablonu "IoT Hub (Event Hub)" .

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Zaktualizuj src/functions/messagehandler.js polecenie , aby dodać powiązanie wyjściowe Web PubSub przy użyciu następującego kodu json. Używamy zmiennej %hubName% jako nazwy centrum dla centrum IoT eventHubName i 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. Zaktualizuj ustawienia funkcji.

    1. Dodaj hubName ustawienie i zastąp {YourIoTHubName} element nazwą centrum użytą podczas tworzenia centrum IoT Hub.

      func settings add hubName "{YourIoTHubName}"
      
    2. Pobierz ciąg Połączenie ion usługi dla usługi IoT Hub.

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

    Ustaw IOTHubConnectionStringwartość , zastępując <iot-connection-string> element wartością .

    func settings add IOTHubConnectionString "<iot-connection-string>"
    
    1. Pobierz ciąg Połączenie ion dla usługi Web PubSub.
    az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
    

    Ustaw WebPubSubConnectionStringwartość , zastępując <webpubsub-connection-string> element wartością .

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

    Uwaga

    Azure Event Hub trigger Wyzwalacz funkcji używany w przykładzie ma zależność od usługi Azure Storage, ale możesz użyć lokalnego emulatora magazynu, gdy funkcja jest uruchomiona lokalnie. Jeśli wystąpi błąd, taki jak There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid., musisz pobrać i włączyć emulator usługi Storage.

  7. Uruchom funkcję lokalnie.

    Teraz możesz uruchomić funkcję lokalną za pomocą poniższego polecenia.

    func start
    

    Możesz odwiedzić stronę statyczną hosta lokalnego, odwiedzając stronę: https://localhost:7071/api/index.

Uruchamianie urządzenia w celu wysyłania danych

Rejestrowanie urządzenia

Zanim urządzenie będzie mogło nawiązać połączenie, należy je najpierw zarejestrować w centrum IoT. Jeśli masz już urządzenie zarejestrowane w centrum IoT Hub, możesz pominąć tę sekcję.

  1. Uruchom polecenie az iot hub device-identity create w usłudze Azure Cloud Shell, aby utworzyć tożsamość urządzenia.

    YourIoTHubName: zastąp ten symbol zastępczy nazwą wybraną dla centrum IoT.

    az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
    
  2. Uruchom polecenie az modułu iot hub device-identity connection-string show w usłudze Azure Cloud Shell, aby pobrać urządzenie parametry połączenia dla właśnie zarejestrowanego urządzenia:

    YourIoTHubName: zamień ten symbol zastępczy poniżej na wybraną nazwę centrum IoT Hub.

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

    Zanotuj parametry połączenia urządzenia, które wygląda następująco:

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

Uruchamianie witryny internetowej wizualizacji

Otwórz stronę indeksu hosta funkcji: http://localhost:7071/api/index aby wyświetlić pulpit nawigacyjny w czasie rzeczywistym. Zarejestruj wiele urządzeń i zobaczysz, że pulpit nawigacyjny aktualizuje wiele urządzeń w czasie rzeczywistym. Otwórz wiele przeglądarek i zobaczysz, że każda strona jest aktualizowana w czasie rzeczywistym.

Screenshot of multiple devices data visualization using Web PubSub service.

Czyszczenie zasobów

Jeśli planujesz korzystać z kolejnych przewodników Szybki start i samouczków, pozostaw te zasoby na swoim miejscu.

Gdy grupa zasobów i wszystkie powiązane zasoby nie będą już potrzebne, możesz użyć polecenia az group delete interfejsu wiersza polecenia platformy Azure:

az group delete --name "myResourceGroup"

Następne kroki