Esercitazione: Visualizzare i dati dei dispositivi IoT da hub IoT usando il servizio Web PubSub di Azure e Funzioni di Azure
In questa esercitazione si apprenderà come usare il servizio Web PubSub di Azure e Funzioni di Azure per creare un'applicazione serverless con visualizzazione dei dati in tempo reale da hub IoT.
In questa esercitazione apprenderai a:
- Creare un'app di visualizzazione dei dati serverless
- Collaborare con le associazioni di input e output della funzione Web PubSub e l'hub IoT di Azure
- Eseguire le funzioni di esempio in locale
Prerequisiti
Un editor di codice, ad esempio Visual Studio Code
Node.js, versione 18.x o successiva.
Nota
Per altre informazioni sulle versioni supportate di Node.js, vedere Funzioni di Azure documentazione sulle versioni di runtime.
Funzioni di Azure Core Tools (v3 o versione successiva preferita) per eseguire le app per le funzioni di Azure in locale e distribuirlo in Azure.
Interfaccia della riga di comando di Azure per gestire le risorse di Azure.
Se non si ha una sottoscrizione di Azure, creare un account Azure gratuito prima di iniziare.
Creare un hub IoT
In questa sezione si usa l'interfaccia della riga di comando di Azure per creare un hub IoT e un gruppo di risorse. Un gruppo di risorse di Azure è un contenitore logico in cui le risorse di Azure vengono distribuite e gestite. Un hub IoT funge da hub messaggi centrale per la comunicazione bidirezionale tra l'applicazione IoT e i dispositivi.
Se si ha già un hub IoT nella sottoscrizione di Azure, è possibile ignorare questa sezione.
Per creare un hub IoT e un gruppo di risorse:
Avviare l'app dell'interfaccia della riga di comando. Per eseguire i comandi dell'interfaccia della riga di comando nel resto di questo articolo, copiare la sintassi del comando, incollarla nell'app dell'interfaccia della riga di comando, modificare i valori delle variabili e premere
Enter
.- Se si usa Cloud Shell, selezionare il pulsante Prova nel comando dell'interfaccia della riga di comando per avviare Cloud Shell in una finestra divisa del browser. In alternativa, è possibile aprire Cloud Shell in una scheda separata del browser.
- Se si usa l'interfaccia della riga di comando di Azure in locale, avviare l'app console dell'interfaccia della riga di comando e accedere all'interfaccia della riga di comando di Azure.
Eseguire az extension add per installare o aggiornare l'estensione azure-iot alla versione corrente.
az extension add --upgrade --name azure-iot
Nell'app dell'interfaccia della riga di comando eseguire il comando az group create per creare un gruppo di risorse. Il comando seguente crea un gruppo denominato MyResourceGroup nella posizione eastus.
Nota
Facoltativamente, è possibile impostare una posizione diversa. Per visualizzare i percorsi disponibili, eseguire
az account list-locations
. Questa guida introduttiva usa eastus come illustrato nel comando di esempio.az group create --name MyResourceGroup --location eastus
Eseguire il comando az iot hub create per creare un hub IoT. La creazione di un hub IoT potrebbe richiedere alcuni minuti.
YourIotHubName. Sostituire questo segnaposto e le parentesi graffe circostanti nel comando seguente, usando il nome scelto per l'hub IoT. Un nome dell'hub IoT deve essere univoco a livello globale in Azure. Usare il nome dell'hub IoT nel resto di questa guida introduttiva ovunque venga visualizzato il segnaposto.
az iot hub create --resource-group MyResourceGroup --name {your_iot_hub_name}
Creare un'istanza di Web PubSub
Se nella sottoscrizione di Azure è già presente un'istanza Web PubSub, è possibile ignorare questa sezione.
Eseguire az extension add per installare o aggiornare l'estensione webpubsub alla versione corrente.
az extension add --upgrade --name webpubsub
Usare il comando az webpubsub create dell'interfaccia della riga di comando di Azure per creare un Web PubSub nel gruppo di risorse creato. Il comando seguente crea una risorsa Web PubSub gratuita nel gruppo di risorse myResourceGroup in EastUS:
Importante
Ogni risorsa Web PubSub deve avere un nome univoco. Sostituire <your-unique-resource-name> con il nome di Web PubSub negli esempi seguenti.
az webpubsub create --name "<your-unique-resource-name>" --resource-group "myResourceGroup" --location "EastUS" --sku Free_F1
L'output di questo comando mostra le proprietà della risorsa appena creata. Prendere nota delle due proprietà elencate di seguito:
- Nome risorsa: nome specificato al
--name
parametro precedente. - hostName: nell'esempio il nome host è
<your-unique-resource-name>.webpubsub.azure.com/
.
A questo punto, l'account Azure è l'unico autorizzato a eseguire qualsiasi operazione su questa nuova risorsa.
Creare ed eseguire le funzioni in locale
Creare una cartella vuota per il progetto e quindi eseguire il comando seguente nella nuova cartella.
func init --worker-runtime javascript --model V4
Creare una
index
funzione per leggere e ospitare una pagina Web statica per i client.func new -n index -t HttpTrigger
Eseguire l'aggiornamento
src/functions/index.js
con il codice seguente, che funge da sito statico per il contenuto HTML.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, }; } });
Creare un
index.html
file nella cartella radice.<!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>
Creare una
negotiate
funzione usata dai client per ottenere un URL di connessione del servizio e un token di accesso.func new -n negotiate -t HttpTrigger
Aggiornare
src/functions/negotiate.js
per usareWebPubSubConnection
che contiene il token generato.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')) }; }, });
Creare una
messagehandler
funzione per generare notifiche usando il"IoT Hub (Event Hub)"
modello .func new --template "Azure Event Hub trigger" --name messagehandler
Aggiornare
src/functions/messagehandler.js
per aggiungere l'associazione di output Web PubSub con il codice JSON seguente. La variabile%hubName%
viene usata come nome dell'hub sia per l'hub IoT eventHubName che per l'hub Web PubSub.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); } });
Aggiornare le impostazioni della funzione.
Aggiungere
hubName
l'impostazione e sostituire{YourIoTHubName}
con il nome dell'hub usato durante la creazione del hub IoT.func settings add hubName "{YourIoTHubName}"
Ottenere la stringa di Connessione del servizio per hub IoT.
az iot hub connection-string show --policy-name service --hub-name {YourIoTHubName} --output table --default-eventhub
Impostare
IOTHubConnectionString
, sostituendo<iot-connection-string>
con il valore .func settings add IOTHubConnectionString "<iot-connection-string>"
- Ottenere la stringa di Connessione ion per Web PubSub.
az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
Impostare
WebPubSubConnectionString
, sostituendo<webpubsub-connection-string>
con il valore .func settings add WebPubSubConnectionString "<webpubsub-connection-string>"
Nota
Il
Azure Event Hub trigger
trigger di funzione usato nell'esempio ha dipendenza da Archiviazione di Azure, ma è possibile usare un emulatore di archiviazione locale quando la funzione è in esecuzione in locale. Se viene visualizzato un errore,There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid.
ad esempio , sarà necessario scaricare e abilitare Archiviazione Emulator.Eseguire la funzione in locale.
A questo momento è possibile eseguire la funzione locale tramite il comando seguente.
func start
È possibile visitare la pagina statica dell'host locale visitando:
https://localhost:7071/api/index
.
Eseguire il dispositivo per inviare dati
Registrazione di un dispositivo
È necessario registrare un dispositivo con l'hub IoT perché questo possa connettersi. Se è già stato registrato un dispositivo nell'hub IoT, è possibile ignorare questa sezione.
Eseguire il comando az iot hub device-identity create in Azure Cloud Shell per creare l'identità del dispositivo.
YourIoTHubName: sostituire questo segnaposto con il nome scelto per l'hub IoT.
az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
Eseguire il comando Az PowerShell module iot hub device-identity connection-string show in Azure Cloud Shell per ottenere il dispositivo stringa di connessione per il dispositivo appena registrato:
YourIoTHubName: sostituire questo segnaposto con il nome scelto per l'hub IoT.
az iot hub device-identity connection-string show --hub-name {YourIoTHubName} --device-id simDevice --output table
Prendere nota del stringa di connessione del dispositivo, simile al seguente:
HostName={YourIoTHubName}.azure-devices.net;DeviceId=simDevice;SharedAccessKey={YourSharedAccessKey}
Per ottenere risultati più rapidi, simulare i dati relativi alla temperatura usando il simulatore Raspberry Pi Azure IoT Online. Incollare il stringa di connessione del dispositivo e selezionare il pulsante Esegui.
Se si dispone di un sensore Raspberry Pi fisico e BME280, è possibile misurare e segnalare valori reali di temperatura e umidità seguendo l'esercitazione Connessione Raspberry Pi a hub IoT di Azure (Node.js).
Eseguire il sito Web di visualizzazione
Aprire la pagina di indice dell'host della funzione: http://localhost:7071/api/index
per visualizzare il dashboard in tempo reale. Registrare più dispositivi e il dashboard aggiorna più dispositivi in tempo reale. Aprire più browser e si noterà che ogni pagina viene aggiornata in tempo reale.
Pulire le risorse
Se si prevede di usare le guide introduttive e le esercitazioni successive, è consigliabile non cancellare le risorse create.
Quando non sono più necessari, è possibile rimuovere il gruppo di risorse e tutte le risorse correlate tramite il comando az group delete dell'interfaccia della riga di comando di Azure:
az group delete --name "myResourceGroup"