Sdílet prostřednictvím


Výukový program: Vizualizace dat ze zařízení IoT z IoT Hub pomocí služby Azure Web PubSub a Azure Functions

V tomto návodu se naučíte používat službu Azure Web PubSub a Azure Functions k vytvoření bezserverové aplikace s vizualizací dat v reálném čase z IoT Hubu.

V tomto návodu se naučíte, jak:

  • Vytvoření aplikace pro vizualizaci dat bez serveru
  • Spolupráce se vstupními a výstupními vazbami funkce Web PubSub a Azure IoT Hubem
  • Spusťte ukázkové funkce lokálně

Důležité

Nespracované připojovací řetězce se v tomto článku zobrazují pouze pro demonstrační účely.

Připojovací řetězec obsahuje informace o autorizaci potřebné pro to, aby vaše aplikace měla přístup ke službě Azure Web PubSub. Přístupový klíč uvnitř připojovacího řetězce je podobný kořenovému heslu pro vaši službu. V produkčním prostředí vždy chraňte své přístupové klíče. Použijte Azure Key Vault k bezpečné správě a rotaci klíčů a zabezpečte své připojení pomocí WebPubSubServiceClient.

Vyhněte se poskytování přístupových klíčů jiným uživatelům, jejich pevné kodování nebo ukládání kdekoli v prostém textu, který je přístupný ostatním. Otočte klíče, pokud se domníváte, že mohly být ohroženy.

Požadavky

Pokud nemáte účet Azure, vytvořte si bezplatný účet před tím, než začnete.

Vytvořte IoT hub

V této sekci použijete Azure CLI k vytvoření IoT hubu a skupiny prostředků. Skupina prostředků Azure je logický kontejner, do kterého se nasazují a spravují prostředky Azure. IoT hub funguje jako centrální uzel pro oboustrannou komunikaci mezi vaší IoT aplikací a zařízeními.

Pokud už ve svém předplatném Azure máte centrum IoT, můžete tuto část přeskočit.

K vytvoření centra IoT a skupiny prostředků:

  1. Spusťte aplikaci příkazového řádku (CLI). K provedení příkazů CLI ve zbytku tohoto článku zkopírujte syntaxi příkazu, vložte ji do své aplikace CLI, upravte hodnoty proměnných a stiskněte Enter.

    • Pokud používáte Cloud Shell, vyberte tlačítko Vyzkoušet u příkazů CLI pro spuštění Cloud Shell ve složeném okně prohlížeče. Nebo můžete otevřít Cloud Shell v samostatné záložce prohlížeče.
    • Pokud používáte Azure CLI místně, spusťte svou konzolovou aplikaci CLI a přihlaste se do Azure CLI.
  2. Spusťte az extension add, abyste nainstalovali nebo upgradovali rozšíření azure-iot na aktuální verzi.

    az extension add --upgrade --name azure-iot
    
  3. V aplikaci CLI spusťte příkaz az group create k vytvoření skupiny prostředků. Následující příkaz vytvoří skupinu prostředků s názvem MyResourceGroup na místě eastus.

    Poznámka

    Případně můžete nastavit jinou polohu. Pro zobrazení dostupných umístění spusťte az account list-locations. V tomto rychlém startu se používá eastus , jak je znázorněno v ukázkovém příkazu.

    az group create --name MyResourceGroup --location eastus
    
  4. Spusťte příkaz az iot hub create k vytvoření IoT centra. Může trvat několik minut, než se vytvoří centrum IoT.

    YourIoTHubName. Nahraďte tuto zástupnou hodnotu a okolní složené závorky v následujícím příkazu pomocí názvu, který jste si vybrali pro váš IoT hub. Název IoT hubu musí být v Azure globálně jedinečný. Použijte název svého IoT hubu v celé této úvodní příručce, kdykoli uvidíte zástupný symbol.

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

Vytvořte instanci Web PubSub

Pokud už ve svém předplatném Azure máte instanci Web PubSub, můžete tuto část přeskočit.

Spusťte az extension add k instalaci nebo upgradu rozšíření webpubsub na aktuální verzi.

az extension add --upgrade --name webpubsub

K vytvoření Web PubSub ve vámi vytvořené skupině prostředků použijte příkaz Azure CLI az webpubsub create. Následující příkaz vytvoří prostředek Web PubSub v úrovni Free ve skupině prostředků myResourceGroup v oblasti EastUS:

Důležité

Každý prostředek Web PubSub musí mít jedinečný název. Nahraďte <your-unique-resource-name> názvem vaší Web PubSub v následujících příkladech.

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

Výstup tohoto příkazu ukazuje vlastnosti nově vytvořeného zdroje. Všimněte si dvou vlastností uvedených níže:

  • Název zdroje: Název, který jste zadali do výše uvedeného parametru --name.
  • hostName: V příkladu je <your-unique-resource-name>.webpubsub.azure.com/název hostitele .

V tuto chvíli je váš účet Azure jediný, který má povolení provádět jakékoli operace s tímto novým zdrojem.

Vytvořte a spusťte funkce lokálně

  1. Vytvořte prázdnou složku pro projekt a poté spusťte následující příkaz v nové složce.

    func init --worker-runtime javascript --model V4
    
  2. Vytvořte funkci index pro čtení a hostování statické webové stránky pro klienty.

    func new -n index -t HttpTrigger
    

    Aktualizujte src/functions/index.js následujícím kódem, který poskytuje HTML obsah jako statický web.

    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. Vytvořte soubor index.html v kořenové složce.

    <!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. Vytvořte funkci negotiate, kterou klienti používají k získání URL služby a přístupového tokenu.

    func new -n negotiate -t HttpTrigger
    

    Aktualizujte src/functions/negotiate.js, aby používal WebPubSubConnection, který obsahuje vygenerovaný 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. Vytvořte funkci messagehandler pro generování oznámení s použitím šablony "IoT Hub (Event Hub)".

    Nespracované připojovací řetězce se v tomto článku zobrazují pouze pro demonstrační účely. V produkčním prostředí vždy chraňte své přístupové klíče. Použijte Azure Key Vault k bezpečné správě a rotaci klíčů a zabezpečte své připojení pomocí WebPubSubServiceClient.

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Aktualizujte src/functions/messagehandler.js a přidejte výstupní vazbu Web PubSub s následujícím kódem JSON. Používáme proměnnou %hubName% jako název hubu pro IoT eventHubName i pro 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. Aktualizujte nastavení funkce.

    1. Přidejte hubName nastavení a nahraďte {YourIoTHubName} názvem centra, který jste použili při vytváření svého IoT Hubu.

      func settings add hubName "{YourIoTHubName}"
      
    2. Získejte připojovací řetězec služby pro IoT Hub.

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

    Nastavte IOTHubConnectionString, nahraďte <iot-connection-string> hodnotou.

    func settings add IOTHubConnectionString "<iot-connection-string>"
    
    1. Získejte připojovací řetězec pro Web PubSub.
    az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
    

    Nastavte WebPubSubConnectionString, nahraďte <webpubsub-connection-string> hodnotou.

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

    Poznámka

    Funkce Azure Event Hub trigger použitá v příkladu závisí na Azure Storage, ale můžete použít emulátor místního úložiště, když funkce běží lokálně. Pokud se zobrazí například chyba There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid., budete si muset stáhnout a povolit emulátor úložiště.

  7. Spusťte funkci lokálně.

    Teď můžete místní funkci spustit pomocí následujícího příkazu.

    func start
    

    Můžete navštívit statickou stránku svého místního hostitele návštěvou: https://localhost:7071/api/index.

Spusťte zařízení, abyste odeslali data

Zaregistrujte zařízení

Zařízení musí být zaregistrováno ve vašem IoT centru, než se může připojit. Pokud již máte zařízení registrované ve svém IoT hubu, můžete tuto část přeskočit.

  1. Spusťte příkaz az iot hub device-identity create v Azure Cloud Shell k vytvoření identity zařízení.

    YourIoTHubName: Nahraďte tento zástupný text názvem, který jste vybrali pro svůj IoT hub.

    az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
    
  2. Spusťte příkaz Az PowerShell module iot hub device-identity connection-string show v Azure Cloud Shell, abyste získali řetězec připojení zařízení pro zařízení, které jste právě zaregistrovali.

    YourIoTHubName: Nahraďte níže uvedený zástupný text názvem, který jste vybrali pro své IoT centrum.

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

    Poznamenejte si připojovací řetězec zařízení, který vypadá takto:

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

Spusťte vizualizační web

Otevřete indexovou stránku hostitele funkcí: http://localhost:7071/api/index pro zobrazení dashboardu v reálném čase. Zaregistrujte více zařízení a uvidíte, jak se v reálném čase aktualizuje panel s více zařízeními. Otevřete několik prohlížečů a uvidíte, že každá stránka je aktualizována v reálném čase.

Snímek obrazovky vizualizace dat z více zařízení pomocí služby Web PubSub.

Vyčistit zdroje

Pokud chcete pokračovat v práci s dalšími rychlými návody a kurzy, můžete chtít tyto prostředky zachovat.

Když již není potřeba, můžete použít příkaz az group delete v Azure CLI k odstranění skupiny prostředků a všech souvisejících zdrojů.

az group delete --name "myResourceGroup"

Další kroky