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:
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.
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
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
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
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
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, }; } });
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>
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ändningWebPubSubConnection
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')) }; }, });
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); } });
Uppdatera funktionsinställningarna.
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}"
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
IOTHubConnectionString
och ersätt<iot-connection-string>
med värdet.func settings add IOTHubConnectionString "<iot-connection-string>"
- 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
WebPubSubConnectionString
och 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 exempelThere 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.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.
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
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}
För snabbaste resultat simulerar du temperaturdata med hjälp av Raspberry Pi Azure IoT Online-simulatorn. Klistra in enheten anslutningssträng och välj knappen Kör.
Om du har en fysisk Raspberry Pi- och BME280-sensor kan du mäta och rapportera verkliga temperatur- och luftfuktighetsvärden genom att följa självstudiekursen Anslut Raspberry Pi till Azure IoT Hub (Node.js).
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.
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"