Tutorial: Exibir dados do dispositivo IoT do Hub IoT usando o serviço o Azure Web PubSub e o Azure Functions
Neste tutorial, você aprenderá a usar o serviço Azure Web PubSub e o Azure Functions para criar um aplicativo sem servidor com exibições de dados em tempo real do Hub IoT.
Neste tutorial, você aprenderá como:
- Criar um aplicativo de exibição de dados sem servidor
- Trabalhar em conjunto com associações de entrada e saída de função do Web PubSub e Hub IoT do Azure
- Executar as funções de exemplo localmente
Pré-requisitos
Um editor de códigos, como o Visual Studio Code
Node.js, versão 18.x ou superior.
Observação
Para obter mais informações sobre as versões compatíveis do Node.js, confira a documentação das versões de runtime do Azure Functions.
Azure Functions Core Tools (v3 ou superior, preferencialmente) para executar aplicativos de função do Azure localmente e implantar no Azure.
A CLI do Azure para gerenciar recursos do Azure.
Caso você não tenha uma assinatura do Azure, crie uma conta gratuita do Azure antes de começar.
Crie um hub IoT
Nesta seção, você usa a CLI do Azure para criar um hub IoT e um grupo de recursos. Um grupo de recursos do Azure é um contêiner lógico no qual os recursos do Azure são implantados e gerenciados. Um hub IoT atua como um hub central de mensagens para comunicação bidirecional entre o aplicativo IoT e os dispositivos.
Se você já tiver um hub IoT em sua assinatura do Azure, ignore esta seção.
Para criar um hub IoT e um grupo de recursos:
Inicie o aplicativo da CLI. Para executar os comandos da CLI no restante deste artigo, copie a sintaxe do comando, cole-a no aplicativo da CLI, edite os valores de variáveis e pressione
Enter
.- Se estiver usando o Cloud Shell, selecione o botão Experimentar nos comandos da CLI para iniciar o Cloud Shell em uma janela dividida do navegador. Ou você pode abrir o Cloud Shell em uma guia separada do navegador.
- Se estiver usando a CLI do Azure localmente, inicie o aplicativo de console da CLI e faça logon na CLI do Azure.
Execute az extension add para instalar ou atualizar a extensão azure-iot para a versão atual.
az extension add --upgrade --name azure-iot
No aplicativo da CLI, execute o comando az group create para criar um grupo de recursos. O comando a seguir cria um grupo de recursos chamado MyResourceGroup na localização eastus.
Observação
Opcionalmente, você pode definir um local diferente. Para ver os locais disponíveis, execute
az account list-locations
. Este início rápido usa eastus, conforme mostrado no comando de exemplo.az group create --name MyResourceGroup --location eastus
Execute o comando az iot hub create para criar um Hub IoT. Pode levar alguns minutos para criar um Hub IoT.
YourIotHubName. Substitua esse espaço reservado e as chaves ao redor no comando a seguir, usando o nome escolhido para o hub IoT. Um nome de Hub IoT deve ser exclusivo globalmente no Azure. Use o nome do hub IoT no restante deste guia de início rápido, sempre que ver o espaço reservado.
az iot hub create --resource-group MyResourceGroup --name {your_iot_hub_name}
Criar uma instância do Web PubSub
Se você já tiver uma instância do Web PubSub em sua assinatura do Azure, ignore esta seção.
Execute az extension add para instalar ou atualizar a extensão webpubsub para a versão atual.
az extension add --upgrade --name webpubsub
Use o comando az webpubsub create da CLI do Azure para criar um Web PubSub no grupo de recursos criado. O seguinte comando cria um recurso gratuito do Web PubSub no grupo de recursos myResourceGroup no EastUS:
Importante
Cada recurso Web PubSub precisa ter um nome exclusivo. Substitua <nome-de recurso-exclusivo> pelo nome do Web PubSub nos exemplos a seguir.
az webpubsub create --name "<your-unique-resource-name>" --resource-group "myResourceGroup" --location "EastUS" --sku Free_F1
A saída deste comando mostra as propriedades do recurso recém-criado. Anote as duas propriedades listadas abaixo:
- Nome do Recurso: o nome que você forneceu ao parâmetro
--name
acima. - hostName: no exemplo, o nome do host é
<your-unique-resource-name>.webpubsub.azure.com/
.
Nesse ponto, sua conta do Azure é a única autorizada a executar qualquer operação nesse novo recurso.
Criar e executar as funções localmente
Crie uma pasta vazia para o projeto e execute o comando a seguir na nova pasta.
func init --worker-runtime javascript --model V4
Crie uma função
index
para ler e hospedar uma página da Web estática para clientes.func new -n index -t HttpTrigger
Atualize o
src/functions/index.js
com o código a seguir, que atende ao conteúdo HTML como site 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, }; } });
Crie um
index.html
arquivo na pasta raiz.<!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>
Crie uma função
negotiate
que os clientes usam para obter uma URL de conexão de serviço e um token de acesso.func new -n negotiate -t HttpTrigger
Atualize
src/functions/negotiate.js
para usarWebPubSubConnection
o token gerado.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')) }; }, });
Crie uma função
messagehandler
para gerar notificações usando o modelo"IoT Hub (Event Hub)"
.func new --template "Azure Event Hub trigger" --name messagehandler
Atualize
src/functions/messagehandler.js
para adicionar a ligação de saída Web PubSub com o seguinte código json. Usamos a variável%hubName%
como o nome do hub para hub IoT eventHubName e 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); } });
Atualizar as configurações de função.
Adicione a configuração
hubName
e substitua{YourIoTHubName}
pelo nome do hub que você usou ao criar o Hub IoT.func settings add hubName "{YourIoTHubName}"
Obtenha a Cadeia de Conexão de Serviço para o Hub IoT.
az iot hub connection-string show --policy-name service --hub-name {YourIoTHubName} --output table --default-eventhub
Defina
IOTHubConnectionString
, substituindo<iot-connection-string>
pelo valor.func settings add IOTHubConnectionString "<iot-connection-string>"
- Obtenha a Cadeia de Conexão para Web PubSub.
az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
Defina
WebPubSubConnectionString
, substituindo<webpubsub-connection-string>
pelo valor.func settings add WebPubSubConnectionString "<webpubsub-connection-string>"
Observação
O gatilho de função
Azure Event Hub trigger
usado no exemplo tem dependência no Armazenamento do Azure, mas você pode usar um emulador de armazenamento local quando a função estiver sendo executada localmente. Se você receber um erro comoThere was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid.
, precisará baixar e habilitar o Emulador de Armazenamento.Execute a função localmente.
Agora você pode executar a função local com o comando abaixo.
func start
Você pode ir até a página estática do host local acessando:
https://localhost:7071/api/index
.
Executar o dispositivo para enviar dados
Registrar um dispositivo
Um dispositivo deve ser registrado no hub IoT antes de poder se conectar. Se você já tiver um dispositivo registrado em seu hub IoT, poderá ignorar essa seção.
Execute o comando az iot hub device-identity create no Azure Cloud Shell para criar a identidade do dispositivo.
YourIoTHubName: substitua o espaço reservado pelo nome escolhido para o Hub IoT.
az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
Execute o comando Az PowerShell module iot hub device-identity connection-string show no Azure Cloud Shell, para obter a cadeia de conexão do dispositivo para o dispositivo que você acabou de registrar:
YourIoTHubName: substitua o espaço reservado abaixo pelo nome escolhido para o hub IoT.
az iot hub device-identity connection-string show --hub-name {YourIoTHubName} --device-id simDevice --output table
Tome nota da cadeia de conexão do dispositivo, que se parece com o seguinte:
HostName={YourIoTHubName}.azure-devices.net;DeviceId=simDevice;SharedAccessKey={YourSharedAccessKey}
Para obter resultados mais rápidos, simule os dados de temperatura usando o Simulador Online de IoT do Azure do Raspberry Pi. Cole na cadeia de conexão do dispositivo e selecione o botão Executar.
Se você tiver um sensor físico Raspberry Pi e BME280, poderá medir e relatar valores reais de temperatura e umidade seguindo o tutorial Conectar o Raspberry Pi ao Hub IoT do Azure (Node.js) a seguir.
Executar o site de visualização
Abra a página de índice do host de funções: http://localhost:7071/api/index
para exibir o painel em tempo real. Registre vários dispositivos e você verá que o painel atualiza vários dispositivos em tempo real. Abra vários navegadores e você verá que todas as páginas são atualizadas em tempo real.
Limpar os recursos
Se você planeja continuar a trabalhar com os tutoriais e inícios rápidos subsequentes, deixe esses recursos onde estão.
Quando não forem mais necessários, você poderá usar o comando az group delete da CLI do Azure para remover o grupo de recursos e todos os recursos relacionados:
az group delete --name "myResourceGroup"