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
Ważne
Nieprzetworzone parametry połączenia są wyświetlane tylko w tym artykule w celach demonstracyjnych.
Parametry połączenia zawiera informacje o autoryzacji wymagane przez aplikację w celu uzyskania dostępu do usługi Azure Web PubSub. Klucz dostępu wewnątrz parametry połączenia jest podobny do hasła głównego usługi. W środowiskach produkcyjnych zawsze chroń klucze dostępu. Użyj usługi Azure Key Vault, aby bezpiecznie zarządzać kluczami i obracać je oraz zabezpieczać połączenie za pomocą usługi WebPubSubServiceClient
.
Unikaj dystrybuowania kluczy dostępu do innych użytkowników, kodowania ich lub zapisywania ich w dowolnym miejscu w postaci zwykłego tekstu, który jest dostępny dla innych użytkowników. Obracanie kluczy, jeśli uważasz, że mogły one zostać naruszone.
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 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:
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
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.
Uruchom polecenie az extension add , aby zainstalować lub uaktualnić rozszerzenie azure-iot do bieżącej wersji.
az extension add --upgrade --name azure-iot
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
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
Utwórz pusty folder dla projektu, a następnie uruchom następujące polecenie w nowym folderze.
func init --worker-runtime javascript --model V4
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, }; } });
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>
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')) }; }, });
Utwórz funkcję do generowania
messagehandler
powiadomień przy użyciu szablonu"IoT Hub (Event Hub)"
.Nieprzetworzone parametry połączenia są wyświetlane tylko w tym artykule w celach demonstracyjnych. W środowiskach produkcyjnych zawsze chroń klucze dostępu. Użyj usługi Azure Key Vault, aby bezpiecznie zarządzać kluczami i obracać je oraz zabezpieczać połączenie za pomocą usługi
WebPubSubServiceClient
.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); } });
Zaktualizuj ustawienia funkcji.
Dodaj
hubName
ustawienie i zastąp{YourIoTHubName}
element nazwą centrum użytą podczas tworzenia centrum IoT Hub.func settings add hubName "{YourIoTHubName}"
Pobierz parametry połączenia usługi dla usługi IoT Hub.
az iot hub connection-string show --policy-name service --hub-name {YourIoTHubName} --output table --default-eventhub
Ustaw
IOTHubConnectionString
wartość , zastępując<iot-connection-string>
element wartością .func settings add IOTHubConnectionString "<iot-connection-string>"
- Pobierz parametry połączenia dla usługi Web PubSub.
az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
Ustaw
WebPubSubConnectionString
wartość , 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 jakThere 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.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ę.
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
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}
Aby uzyskać najszybsze wyniki, symuluj dane temperatury przy użyciu symulatora urządzenia Raspberry Pi Azure IoT Online. Wklej parametry połączenia urządzenia i wybierz przycisk Uruchom.
Jeśli masz fizyczny czujnik Raspberry Pi i BME280, możesz mierzyć i zgłaszać rzeczywiste wartości temperatury i wilgotności, postępując zgodnie z samouczkiem Łączenie urządzenia Raspberry Pi z usługą Azure IoT Hub (Node.js).
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.
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"