Aracılığıyla paylaş


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

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

Bu öğreticide aşağıdakilerin nasıl yapılacağını öğreneceksiniz:

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

Önkoşullar

Azure aboneliğiniz yoksa başlamadan önce birücretsiz Azure hesabı 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 az group create komutunu çalıştırarak bir kaynak grubu oluşturun. Aşağıdaki komut eastus konumunda MyResourceGroup adlı bir kaynak grubu oluşturur.

    Dekont

    İ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üme ayraçlarını 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 {your_iot_hub_name}
    

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
    

    Oluşturulan belirteci içeren kullanılacak WebPubSubConnection şekilde güncelleştirinsrc/functions/negotiate.js.

    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 "IoT Hub (Event Hub)" bildirim oluşturmak için bir messagehandler işlev oluşturun.

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Web PubSub çıkış bağlamasını aşağıdaki json koduyla eklemek için güncelleştirinsrc/functions/messagehandler.js. 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. Ayarı ekleyin hubName ve değerini IoT Hub'ınızı oluştururken kullandığınız hub adıyla değiştirin {YourIoTHubName} .

      func settings add hubName "{YourIoTHubName}"
      
    2. IoT Hub için Hizmet Bağlan ion Dizesini alın.

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

    değerini değeriyle değiştirerek <iot-connection-string> ayarlayınIOTHubConnectionString.

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

    değerini değeriyle değiştirerek <webpubsub-connection-string> ayarlayınWebPubSubConnectionString.

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

    Dekont

    Örnekte Azure Event Hub trigger kullanılan işlev tetikleyicisinin Azure Depolama bağımlılığı vardır, ancak işlev yerel olarak çalışırken yerel bir depolama öykünücüsü kullanabilirsiniz. gibi There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid.bir hata alırsanız, Depolama Öykünücüsü'ne 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. Az PowerShell module iot hub device-identity connection-string show komutunu Azure Cloud Shell'de çalıştırarak az önce kaydettiğiniz cihazın cihaz bağlantı dizesi 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
    

    Cihaz bağlantı dizesi not edin ve şuna benzer:

    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ı dizesi 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 (Node.js) Bağlan öğreticisini izleyerek gerçek sıcaklık ve nem değerlerini ölçebilir ve raporlayabilirsiniz.

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

gerçek zamanlı panoyu görüntülemek için işlev ana bilgisayar dizini sayfasını http://localhost:7071/api/index açın. 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.

Screenshot of multiple devices data visualization using Web PubSub service.

Kaynakları temizleme

Sonraki hızlı başlangıç ve öğreticilerle çalışmaya devam etmeyi planlıyorsanız, bu kaynakları yerinde bırakmanız yararlı olabilir.

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