Öğretici: Azure Web PubSub hizmetini ve Azure İşlevleri'ni kullanarak IoT Hub'dan IoT cihaz verilerini görselleştirme

Bu öğreticide, IoT Hub'dan gerçek zamanlı veri görselleştirmesi ile sunucusuz bir uygulama oluşturmak için Azure Web PubSub hizmetini ve Azure İşlevleri'ni kullanmayı öğreneceksiniz.

Bu eğitimde şunları öğreniyorsunuz:

  • Sunucusuz veri görselleştirme uygulaması oluşturma
  • Web PubSub işlevi giriş ve çıkış bağlamaları ile Azure IoT Hub ile birlikte çalışın.
  • Örnek işlevleri yerel olarak çalıştırma

Önemli

Ham bağlantı dizesi yalnızca tanıtım amacıyla bu makalede görünür.

bağlantı dizesi, uygulamanızın Azure Web PubSub hizmetine erişmesi için gereken yetkilendirme bilgilerini içerir. bağlantı dizesi içindeki erişim anahtarı, hizmetinizin kök parolasına benzer. Üretim ortamlarında erişim anahtarlarınızı her zaman koruyun. Anahtarlarınızı güvenli bir şekilde yönetmek ve döndürmek ve bağlantınızın WebPubSubServiceClientgüvenliğini sağlamak için Azure Key Vault kullanın.

Erişim anahtarlarını diğer kullanıcılara dağıtmaktan, sabit kodlamaktan veya başkalarının erişebileceği herhangi bir yerde düz metin olarak kaydetmekten kaçının. Ele geçirilmiş olabileceklerini düşünüyorsanız anahtarlarınızı döndürün.

Önkoşullar

Azure hesabınız yoksa, başlamadan önce ücretsiz hesap oluşturun.

IoT hub'ı oluşturma

Bu bölümde, IoT hub'ı ve kaynak grubu oluşturmak için Azure CLI'yi kullanacaksınız. Azure kaynak grubu, Azure kaynaklarının dağıtıldığı ve yönetildiği bir mantıksal kapsayıcıdır. IoT hub'ı, IoT uygulamanızla cihazlar arasında çift yönlü iletişim için merkezi bir ileti hub'ı işlevi görür.

Azure aboneliğinizde zaten bir IoT hub'ınız varsa bu bölümü atlayabilirsiniz.

IoT hub'ı ve kaynak grubu oluşturmak için:

  1. CLI uygulamanızı başlatın. Bu makalenin geri kalanında CLI komutlarını çalıştırmak için komut söz dizimini kopyalayın, CLI uygulamanıza yapıştırın, değişken değerlerini düzenleyin ve tuşuna basın Enter.

    • Cloud Shell kullanıyorsanız Cloud Shell'i bölünmüş bir tarayıcı penceresinde başlatmak için CLI komutlarında Deneyin düğmesini seçin. Veya Cloud Shell'i ayrı bir tarayıcı sekmesinde açabilirsiniz.
    • Azure CLI'yi yerel olarak kullanıyorsanız CLI konsol uygulamanızı başlatın ve Azure CLI'da oturum açın.
  2. azure-iot uzantısını yüklemek veya geçerli sürüme yükseltmek için az extension add komutunu çalıştırın.

    az extension add --upgrade --name azure-iot
    
  3. CLI uygulamanızda, bir kaynak grubu oluşturmak için az group create komutunu çalıştırın. Aşağıdaki komut eastus konumunda MyResourceGroup adlı bir kaynak grubu oluşturur.

    Uyarı

    İsteğe bağlı olarak, farklı bir konum ayarlayabilirsiniz. Kullanılabilir konumları görmek için komutunu çalıştırın az account list-locations. Bu hızlı başlangıçta örnek komutta gösterildiği gibi eastus kullanılmıştır.

    az group create --name MyResourceGroup --location eastus
    
  4. IoT hub'ı oluşturmak için az iot hub create komutunu çalıştırın. IoT hub'ı oluşturmak birkaç dakika sürebilir.

    YourIoTHubName. Aşağıdaki komutta IoT hub'ınız için seçtiğiniz adı kullanarak bu yer tutucuyu ve çevresindeki köşeli parantezleri değiştirin. IoT hub adı Azure'da genel olarak benzersiz olmalıdır. Bu hızlı başlangıcın geri kalanında yer tutucuyu gördüğünüz her yerde IoT hub'ınızın adını kullanın.

    az iot hub create --resource-group MyResourceGroup --name {YourIoTHubName}
    

Web PubSub örneği oluşturma

Azure aboneliğinizde zaten bir Web PubSub örneğiniz varsa bu bölümü atlayabilirsiniz.

Webpubsub uzantısını yüklemek veya geçerli sürüme yükseltmek için az extension add komutunu çalıştırın.

az extension add --upgrade --name webpubsub

Oluşturduğunuz kaynak grubunda bir Web PubSub oluşturmak için Azure CLI az webpubsub create komutunu kullanın. Aşağıdaki komut, EastUS'ta myResourceGroup kaynak grubu altında bir Ücretsiz Web PubSub kaynağı oluşturur:

Önemli

Her Web PubSub kaynağının benzersiz bir adı olmalıdır. Aşağıdaki örneklerde unique-resource-name< değerini Web PubSub'ınızın adıyla değiştirin>.

az webpubsub create --name "<your-unique-resource-name>" --resource-group "myResourceGroup" --location "EastUS" --sku Free_F1

Bu komutun çıktısı yeni oluşturulan kaynağın özelliklerini gösterir. Aşağıda listelenen iki özelliği not edin:

  • Kaynak Adı: Yukarıdaki parametreye --name sağladığınız ad.
  • hostName: Örnekte ana bilgisayar adı şeklindedir <your-unique-resource-name>.webpubsub.azure.com/.

Bu noktada, azure hesabınız bu yeni kaynak üzerinde herhangi bir işlem gerçekleştirme yetkisi olan tek hesaptır.

İşlevleri yerel olarak oluşturma ve çalıştırma

  1. Proje için boş bir klasör oluşturun ve ardından yeni klasörde aşağıdaki komutu çalıştırın.

    func init --worker-runtime javascript --model V4
    
  2. İstemciler için statik bir index web sayfasını okumak ve barındırmak için bir işlev oluşturun.

    func new -n index -t HttpTrigger
    

    HTML içeriğini statik bir site olarak sunan aşağıdaki kodla güncelleştirin src/functions/index.js .

    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, 
            };
        }
    });
    
  3. Kök klasörün altında bir index.html dosya oluşturun.

    <!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>
    
  4. negotiate İstemcilerin hizmet bağlantısı URL'si ve erişim belirteci almak için kullandığı bir işlev oluşturun.

    func new -n negotiate -t HttpTrigger
    

    src/functions/negotiate.js'yi, oluşturulan belirteci içeren WebPubSubConnection'i kullanacak şekilde güncelleştir.

    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')) };
        },
    });
    
  5. Şablonu kullanarak messagehandler bildirim oluşturmak için bir "IoT Hub (Event Hub)" işlev oluşturun.

    Ham bağlantı dizesi yalnızca tanıtım amacıyla bu makalede görünür. Üretim ortamlarında erişim anahtarlarınızı her zaman koruyun. Anahtarlarınızı güvenli bir şekilde yönetmek ve döndürmek ve bağlantınızın WebPubSubServiceClientgüvenliğini sağlamak için Azure Key Vault kullanın.

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Güncelleştirin src/functions/messagehandler.js ve Web PubSub çıkış bağlamasını aşağıdaki JSON koduyla ekleyin. Hem IoT eventHubName hem de Web PubSub hub'ı için hub adı olarak değişken %hubName% kullanıyoruz.

      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);
          }
      });
      
  6. İşlev ayarlarını güncelleştirin.

    1. hubName ayarını ekleyin ve {YourIoTHubName} değerini IoT Hub'ınızı oluştururken kullandığınız hub adıyla değiştirin.

      func settings add hubName "{YourIoTHubName}"
      
    2. IoT Hub için Hizmet Bağlantı Dizesi'ni alın.

    az iot hub connection-string show --policy-name service --hub-name {YourIoTHubName} --output table --default-eventhub
    

    IOTHubConnectionString ayarlayın, <iot-connection-string>'yi değerle değiştirerek.

    func settings add IOTHubConnectionString "<iot-connection-string>"
    
    1. Web PubSub için Bağlantı Dizesini alın.
    az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
    

    WebPubSubConnectionString ayarlayın, <webpubsub-connection-string>'yi değerle değiştirerek.

    func settings add WebPubSubConnectionString "<webpubsub-connection-string>"
    

    Uyarı

    Örnekte Azure Event Hub trigger kullanılan işlev tetikleyicisinin Azure Depolama'ya bağımlılığı vardır, ancak işlev yerel olarak çalışırken yerel bir depolama öykünücüsü kullanabilirsiniz. Eğer şu tür bir hata alırsanız: There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid., Depolama Öykünücüsünü indirmeniz ve etkinleştirmeniz gerekir.

  7. İşlevi yerel olarak çalıştırın.

    Artık yerel işlevinizi aşağıdaki komutla çalıştırabilirsiniz.

    func start
    

    Yerel ana bilgisayar statik sayfanızı şu adresi ziyaret ederek ziyaret edebilirsiniz: https://localhost:7071/api/index.

Verileri göndermek için cihazı çalıştırma

Cihaz kaydetme

Bir cihazın bağlanabilmesi için IoT hub’ınıza kaydedilmesi gerekir. IoT hub'ınıza kayıtlı bir cihazınız varsa bu bölümü atlayabilirsiniz.

  1. Cihaz kimliğini oluşturmak için Azure Cloud Shell'de az iot hub device-identity create komutunu çalıştırın.

    YourIoTHubName: Bu yer tutucuyu IoT hub'ınız için seçtiğiniz adla değiştirin.

    az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
    
  2. Azure Cloud Shell'de Az PowerShell modülü iot hub device-identity connection-string show komutunu çalıştırarak az önce kaydettiğiniz cihaz için cihaz bağlantı dizesini alın:

    YourIoTHubName: Aşağıdaki yer tutucuyu IoT hub'ınız için seçtiğiniz adla değiştirin.

    az iot hub device-identity connection-string show --hub-name {YourIoTHubName} --device-id simDevice --output table
    

    Aşağıdaki gibi görünen cihaz bağlantı dizesini not edin:

    HostName={YourIoTHubName}.azure-devices.net;DeviceId=simDevice;SharedAccessKey={YourSharedAccessKey}

  • En hızlı sonuçlar için Raspberry Pi Azure IoT Online Simulator'ı kullanarak sıcaklık verilerinin simülasyonunu başlatın. Cihaz bağlantı dizesini yapıştırın ve Çalıştır düğmesini seçin.

  • Fiziksel Raspberry Pi ve BME280 algılayıcınız varsa Raspberry Pi'yi Azure IoT Hub'a bağlama (Node.js) öğreticisini izleyerek gerçek sıcaklık ve nem değerlerini ölçebilir ve raporlayabilirsiniz.

Görselleştirme web sitesini çalıştırma

İşlev ana bilgisayar dizini sayfasını http://localhost:7071/api/index açın ve gerçek zamanlı panoyu görüntüleyin. Birden çok cihaz kaydettiğinizde panonun birden çok cihazı gerçek zamanlı olarak güncelleştirdiğini görürsünüz. Birden çok tarayıcı açtığınızda her sayfanın gerçek zamanlı olarak güncelleştirildiğini görürsünüz.

Web PubSub hizmetini kullanan birden çok cihaz veri görselleştirmesinin ekran görüntüsü.

Kaynakları temizle

Sonraki hızlı başlangıçlar ve öğreticilerle çalışmaya devam etmeyi planlıyorsanız, bu kaynakları yerinde bırakmayı tercih edebilirsiniz.

Artık gerekli olmadığında, kaynak grubunu ve tüm ilgili kaynakları kaldırmak için Azure CLI az group delete komutunu kullanabilirsiniz:

az group delete --name "myResourceGroup"

Sonraki Adımlar