Tutorial: Visualización de datos de dispositivos IoT desde IoT Hub mediante el servicio Azure Web PubSub y Azure Functions
En este tutorial, aprenderá a usar el servicio Azure Web PubSub y Azure Functions para compilar una aplicación sin servidor con visualización en tiempo real desde IoT Hub.
En este tutorial, aprenderá a:
- Compilar una aplicación de visualización de datos sin servidor
- Trabajar junto con los enlaces de entrada y salida de funciones de Web PubSub y Azure IoT Hub
- Ejecutar localmente funciones de ejemplo.
Importante
Las cadenas de conexión sin procesar solo aparecen en este artículo con fines de demostración.
Una cadena de conexión incluye la información de autorización necesaria para que la aplicación acceda al servicio Azure Web PubSub. La clave de acceso dentro de la cadena de conexión es similar a una contraseña raíz para el servicio. En entornos de producción, proteja siempre las claves de acceso. Use Azure Key Vault para administrar y rotar las claves de forma segura y proteja la conexión con WebPubSubServiceClient
.
Evite distribuirlas a otros usuarios, codificarlas de forma rígida o guardarlas en un archivo de texto sin formato al que puedan acceder otros usuarios. Rote sus claves si cree que se han puesto en peligro.
Requisitos previos
Un editor de código como Visual Studio Code
Node.js, versión 18.x o superior.
Nota:
Para más información sobre las versiones de Node.js que se admiten, consulte la documentación de las versiones del runtime de Azure Functions.
Azure Functions Core Tools (se prefieren la versión 3 o superiores) para ejecutar aplicaciones de Azure Functions localmente y realizar la implementación en Azure.
La CLI de Azure para administrar recursos de Azure.
Si no tiene una suscripción a Azure, cree una cuenta gratuita de Azure antes de empezar.
Crear un centro de IoT
En esta sección, usará la CLI de Azure para crear un centro de IoT y un grupo de recursos. Un grupo de recursos de Azure es un contenedor lógico en el que se implementan y se administran los recursos de Azure. Un centro de IoT funciona como centro de mensajes unificado para la comunicación bidireccional entre la aplicación de IoT y los dispositivos.
Si ya tiene un centro de IoT en su suscripción de Azure, puede omitir esta sección.
Para crear un centro de IoT y un grupo de recursos:
Inicie la aplicación de la CLI. Para ejecutar los comandos de la CLI en el resto de este artículo, copie la sintaxis de comando, péguela en la aplicación de la CLI, edite los valores de las variables y presione
Enter
.- Si usa Cloud Shell, seleccione el botón Probar en los comandos de la CLI para iniciar Cloud Shell en una ventana dividida del explorador. O bien, puede abrir Cloud Shell en una pestaña del explorador independiente.
- Si usa la CLI de Azure localmente, inicie la aplicación de consola de la CLI e inicie sesión en la CLI de Azure.
Ejecute az extension add para instalar o actualizar la extensión azure-iot a la versión actual.
az extension add --upgrade --name azure-iot
En la aplicación de la CLI, ejecute el comando az group create para crear un grupo de recursos. El siguiente comando crea un grupo de recursos denominado MyResourceGroup en la ubicación eastus.
Nota
Opcionalmente, puede establecer otra ubicación. Para ver las ubicaciones disponibles, ejecute
az account list-locations
. En este inicio rápido se usa eastus, como se muestra en el comando de ejemplo.az group create --name MyResourceGroup --location eastus
Ejecute el comando az iot hub create para crear una instancia de IoT Hub. La creación de una instancia de IoT Hub puede tardar unos minutos.
YourIotHubName. Reemplace este marcador de posición y las llaves circundantes en el siguiente comando, con el nombre que eligió para el centro de IoT. Un nombre de centro de IoT debe ser único globalmente en Azure. Use el nombre del centro de IoT a lo largo de este inicio rápido cuando vea el marcador de posición.
az iot hub create --resource-group MyResourceGroup --name {your_iot_hub_name}
Creación de una instancia de Web PubSub
Si ya tiene una instancia de Azure Web PubSub en su suscripción de Azure, puede omitir esta sección.
Ejecute az extension add para instalar o actualizar la extensión webpubsub en la versión actual.
az extension add --upgrade --name webpubsub
Use el comando az webpubsub create de la CLI de Azure para crear una instancia de Web PubSub en el grupo de recursos que ha creado. El comando siguiente crea un recurso Free de Web PubSub en el grupo de recursos myResourceGroup en EastUS:
Importante
Cada recurso de Web PubSub debe tener un nombre único. Reemplace <your-unique-resource-name> por el nombre de Web PubSub en los ejemplos siguientes.
az webpubsub create --name "<your-unique-resource-name>" --resource-group "myResourceGroup" --location "EastUS" --sku Free_F1
La salida de este comando muestra las propiedades del recurso que acaba de crear. Tome nota de las dos propiedades siguientes:
- Nombre del recurso: nombre que proporcionó al parámetro
--name
anterior. - hostName: en el ejemplo, el nombre de host es
<your-unique-resource-name>.webpubsub.azure.com/
.
En este momento, su cuenta de Azure es la única autorizada para realizar operaciones en este nuevo recurso.
Creación y ejecución locales de las funciones
Cree una carpeta vacía para el proyecto y ejecute el siguiente comando en la nueva carpeta.
func init --worker-runtime javascript --model V4
Cree una función
index
para leer y hospedar una página web estática para los clientes.func new -n index -t HttpTrigger
Actualice
src/functions/index.js
con el código siguiente, que sirve el contenido HTML como un sitio estático.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, }; } });
Cree un archivo
index.html
en la carpeta raíz.<!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>
Cree una función
negotiate
que los clientes usarán para obtener una dirección URL de conexión de servicio y un token de acceso.func new -n negotiate -t HttpTrigger
Actualice
src/functions/negotiate.js
para usarWebPubSubConnection
que contenga el token generado.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')) }; }, });
Cree una función
messagehandler
para generar notificaciones con la plantilla"IoT Hub (Event Hub)"
.Las cadenas de conexión sin procesar solo aparecen en este artículo con fines de demostración. En entornos de producción, proteja siempre las claves de acceso. Use Azure Key Vault para administrar y rotar las claves de forma segura y proteja la conexión con
WebPubSubServiceClient
.func new --template "Azure Event Hub trigger" --name messagehandler
Actualice
src/functions/messagehandler.js
para agregar enlace de salida de Web PubSub con el código JSON siguiente. Usamos la variable%hubName%
como nombre de centro para IoT eventHubName como para el centro de 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); } });
Actualización de la configuración de la función.
Agregue el valor
hubName
y reemplace{YourIoTHubName}
por el nombre del centro que usó al crear el centro de IoT.func settings add hubName "{YourIoTHubName}"
Obtenga la cadena de conexión del servicio del centro de IoT.
az iot hub connection-string show --policy-name service --hub-name {YourIoTHubName} --output table --default-eventhub
Establezca
IOTHubConnectionString
, reemplazando<iot-connection-string>
con el valor.func settings add IOTHubConnectionString "<iot-connection-string>"
- Obtenga la cadena de conexión para Web PubSub.
az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
Establezca
WebPubSubConnectionString
, reemplazando<webpubsub-connection-string>
con el valor.func settings add WebPubSubConnectionString "<webpubsub-connection-string>"
Nota
El desencadenador de funciones
Azure Event Hub trigger
usado en el ejemplo tiene dependencia de Azure Storage, pero puede usar un emulador de almacenamiento local cuando la función se ejecute localmente. Si se produce un error comoThere was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid.
, tendrá que descargar y habilitar el emulador de Storage.Ejecute la función de forma local.
Ya puede ejecutar la función local mediante el comando siguiente.
func start
Puede visitar la página estática del host local visitando:
https://localhost:7071/api/index
.
Ejecución del dispositivo para enviar datos
Registrar un dispositivo
Debe registrar un dispositivo con IoT Hub antes de poder conectarlo. Si ya tiene un dispositivo registrado en el centro de IoT, puede omitir esta sección.
Ejecute el comando az iot hub device-identity create en Azure Cloud Shell para crear la identidad del dispositivo.
YourIoTHubName: reemplace este marcador de posición por el nombre elegido para el centro de IoT.
az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
Ejecute el comando Az PowerShell module iot hub device-identity connection-string show en Azure Cloud Shell para obtener la cadena de conexión del dispositivo que acaba de registrar:
YourIoTHubName: reemplace este marcador de posición por el nombre elegido para el centro de IoT.
az iot hub device-identity connection-string show --hub-name {YourIoTHubName} --device-id simDevice --output table
Anote la cadena de conexión del dispositivo, que se parecerá a esta:
HostName={YourIoTHubName}.azure-devices.net;DeviceId=simDevice;SharedAccessKey={YourSharedAccessKey}
Para obtener resultados lo más rápidamente posible, simule los datos de temperatura mediante el simulador Raspberry Pi Azure IoT Online Simulator. Pegue la cadena de conexión del dispositivo y seleccione el botón Run (Ejecutar).
Si tiene una Raspberry Pi y un sensor BME280 físicos, puede medir y notificar valores reales de temperatura y humedad siguiendo el tutorial de conexión de Raspberry Pi a Azure IoT Hub (Node.js).
Ejecución del sitio web de visualización
Abra la página de índice del host de funciones: http://localhost:7071/api/index
para ver el panel en tiempo real. Registre varios dispositivos y podrá ver que el panel actualiza varios dispositivos en tiempo real. Abra varios exploradores y podrá ver que todas las páginas se actualizan en tiempo real.
Limpieza de recursos
Si tiene pensado seguir trabajando en otras guías de inicio rápido y tutoriales, considere la posibilidad de dejar estos recursos activos.
Cuando ya no se necesiten, puede usar el comando az group delete de la CLI de Azure para quitar el grupo de recursos y todos los recursos relacionados.
az group delete --name "myResourceGroup"