Bagikan melalui


Tutorial: Memvisualisasikan data perangkat IoT dari IoT Hub menggunakan layanan Azure Web PubSub dan Azure Functions

Dalam tutorial ini, Anda akan mempelajari cara menggunakan layanan Azure Web PubSub dan Azure Functions untuk membangun aplikasi tanpa server dengan visualisasi data real time dari IoT Hub.

Dalam tutorial ini, Anda akan mempelajari cara:

  • Membangun aplikasi visualisasi data tanpa server
  • Bekerja sama dengan pengikatan input dan output fungsi Web PubSub dan hub Azure IoT
  • Menjalankan fungsi sampel secara lokal

Penting

String koneksi mentah muncul dalam artikel ini hanya untuk tujuan demonstrasi.

String koneksi menyertakan informasi otorisasi yang diperlukan agar aplikasi Anda mengakses layanan Azure Web PubSub. Kunci akses di dalam string koneksi mirip dengan kata sandi root untuk layanan Anda. Di lingkungan produksi, selalu lindungi kunci akses Anda. Gunakan Azure Key Vault untuk mengelola dan memutar kunci Anda dengan aman dan mengamankan koneksi Anda dengan WebPubSubServiceClient.

Hindari mendistribusikan kunci akses ke pengguna lain, melakukan hard-coding, atau menyimpannya di mana saja dalam teks biasa yang dapat diakses orang lain. Putar kunci Anda jika Anda yakin bahwa kunci tersebut mungkin telah disusupi.

Prasyarat

Jika Anda tidak memiliki Langganan Azure, buat Akun gratis Azure sebelum memulai.

Membuat IoT Hub

Di bagian ini, Anda menggunakan Azure CLI untuk membuat hub IoT dan grup sumber daya. Grup sumber daya Azure adalah kontainer logis tempat sumber daya Azure disebarkan dan dikelola. Hub IoT bertindak sebagai hub pesan pusat untuk komunikasi dua arah antara aplikasi IoT Anda dan perangkat.

Jika sudah memiliki IoT hub di langganan Azure, Anda dapat melewati bagian ini.

Untuk membuat hub IoT dan grup sumber daya:

  1. Luncurkan aplikasi CLI Anda. Untuk menjalankan perintah CLI di sisa artikel ini, salin sintaks perintah, tempelkan ke aplikasi CLI Anda, edit nilai variabel, dan tekan Enter.

    • Jika Anda menggunakan Cloud Shell, pilih tombol Cobalah pada perintah CLI untuk meluncurkan Cloud Shell di jendela browser terpisah. Atau Anda dapat membuka Cloud Shell di tab browser terpisah.
    • Jika Anda menggunakan Azure CLI secara lokal, buka aplikasi konsol CLI Anda dan masuk ke Azure CLI.
  2. Jalankan az extension add untuk memasang atau meningkatkan ekstensi azure-iot ke versi saat ini.

    az extension add --upgrade --name azure-iot
    
  3. Di aplikasi CLI Anda, jalankan perintah az group create untuk membuat grup sumber daya. Perintah berikut membuat grup sumber daya bernama MyResourceGroup di lokasi eastus.

    Catatan

    Secara opsional, Anda dapat mengatur lokasi yang berbeda. Untuk melihat lokasi yang tersedia, jalankan az account list-locations. Mulai cepat ini menggunakan eastus seperti yang ditunjukkan dalam perintah contoh.

    az group create --name MyResourceGroup --location eastus
    
  4. Jalankan perintah az iot hub create untuk membuat hub IoT. Mungkin perlu waktu beberapa menit untuk membuat hub IoT.

    YourIotHubName. Ganti tempat penampung ini dan kurung kurawal di sekitarnya dalam perintah berikut, menggunakan nama yang Anda pilih untuk hub IoT Anda. Nama hub IoT harus unik secara global di Azure. Gunakan nama hub IoT Anda di sisa mulai cepat ini di mana pun Anda melihat tempat penampung.

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

Membuat instans Web PubSub

Jika Anda sudah memiliki instans Web PubSub di langganan Azure, Anda dapat melewati bagian ini.

Jalankan az extension add untuk menginstal atau meningkatkan ekstensi webpubsub ke versi saat ini.

az extension add --upgrade --name webpubsub

Gunakan perintah az webpubsub create Azure CLI untuk membuat Web PubSub di grup sumber daya yang telah Anda buat. Perintah berikut membuat sumber daya Web PubSub Gratis di bawah grup sumber daya myResourceGroup di EastUS:

Penting

Setiap sumber daya Web PubSub harus memiliki nama yang unik. Ganti <your-unique-resource-name> dengan nama Web PubSub Anda dalam contoh berikut.

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

Keluaran dari perintah ini menunjukkan properti sumber daya yang baru dibuat. Perhatikan dua properti yang tercantum di bawah:

  • Nama Sumber Daya: Nama yang Anda berikan untuk parameter --name di atas.
  • hostName: Dalam contoh, nama host adalah <your-unique-resource-name>.webpubsub.azure.com/.

Pada titik ini, akun Azure Anda adalah satu-satunya yang berwenang untuk melakukan operasi apa pun di sumber daya baru ini.

Membuat dan menjalankan fungsi secara lokal

  1. Buat folder kosong untuk proyek, lalu jalankan perintah berikut di folder baru.

    func init --worker-runtime javascript --model V4
    
  2. Buat fungsi index untuk membaca dan menghosting halaman web statis untuk klien.

    func new -n index -t HttpTrigger
    

    Perbarui src/functions/index.js dengan kode berikut, yang melayani konten HTML sebagai situs statis.

    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. Buat index.html file di bawah folder akar.

    <!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. Buat negotiate fungsi yang digunakan klien untuk mendapatkan URL koneksi layanan dan token akses.

    func new -n negotiate -t HttpTrigger
    

    Perbarui src/functions/negotiate.js untuk menggunakan WebPubSubConnection yang berisi token yang dihasilkan.

    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. Buat messagehandler fungsi untuk menghasilkan pemberitahuan dengan menggunakan "IoT Hub (Event Hub)" templat.

    String koneksi mentah muncul dalam artikel ini hanya untuk tujuan demonstrasi. Di lingkungan produksi, selalu lindungi kunci akses Anda. Gunakan Azure Key Vault untuk mengelola dan memutar kunci Anda dengan aman dan mengamankan koneksi Anda dengan WebPubSubServiceClient.

     func new --template "Azure Event Hub trigger" --name messagehandler
    
    • Perbarui src/functions/messagehandler.js untuk menambahkan pengikatan output Web PubSub dengan kode json berikut. Kami menggunakan variabel %hubName% sebagai nama hub untuk hub IoT eventHubName dan 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);
          }
      });
      
  6. Perbarui pengaturan Fungsi.

    1. Tambahkan hubName pengaturan dan ganti {YourIoTHubName} dengan nama hub yang Anda gunakan saat membuat IoT Hub Anda.

      func settings add hubName "{YourIoTHubName}"
      
    2. Dapatkan String Koneksi Layanan untuk IoT Hub.

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

    Atur IOTHubConnectionString, ganti <iot-connection-string> dengan nilai .

    func settings add IOTHubConnectionString "<iot-connection-string>"
    
    1. Dapatkan String Koneksi untuk Web PubSub.
    az webpubsub key show --name "<your-unique-resource-name>" --resource-group "<your-resource-group>" --query primaryConnectionString
    

    Atur WebPubSubConnectionString, ganti <webpubsub-connection-string> dengan nilai .

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

    Catatan

    Pemicu Azure Event Hub trigger fungsi yang digunakan dalam sampel memiliki dependensi pada Azure Storage, tetapi Anda dapat menggunakan emulator penyimpanan lokal saat fungsi berjalan secara lokal. Jika Anda mendapatkan kesalahan seperti There was an error performing a read operation on the Blob Storage Secret Repository. Please ensure the 'AzureWebJobsStorage' connection string is valid., Anda harus mengunduh dan mengaktifkan Storage Emulator.

  7. Jalankan fungsi secara lokal.

    Sekarang Anda dapat menjalankan fungsi lokal Anda dengan perintah di bawah ini.

    func start
    

    Anda dapat mengunjungi halaman statis host lokal Anda dengan mengunjungi: https://localhost:7071/api/index.

Jalankan perangkat untuk mengirim data

Mendaftarkan perangkat

Perangkat harus terdaftar dengan hub IoT Anda sebelum dapat terhubung. Jika sudah memiliki perangkat yang terdaftar di IoT hub, Anda dapat melewati bagian ini.

  1. Jalankan perintah az iot hub device-identity create di Azure Cloud Shell untuk membuat identitas perangkat.

    YourIoTHubName: Ganti tempat penampung ini dengan nama yang Anda pilih untuk hub IoT Anda.

    az iot hub device-identity create --hub-name {YourIoTHubName} --device-id simDevice
    
  2. Jalankan perintah Az PowerShell modul iot hub device-identity connection-string show di Azure Cloud Shell untuk mendapatkan perangkat string koneksi untuk perangkat yang baru saja Anda daftarkan:

    YourIoTHubName: Ganti placeholder di bawah dengan nama yang Anda pilih untuk Azure IoT Hub Anda.

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

    Catat perangkat string koneksi, yang terlihat seperti ini:

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

Menjalankan situs web visualisasi

Buka halaman indeks host fungsi: http://localhost:7071/api/index untuk melihat dasbor real-time. Daftarkan beberapa perangkat dan Anda akan melihat dasbor memperbarui beberapa perangkat secara real time. Buka beberapa browser dan Anda akan melihat setiap halaman diperbarui secara real-time.

Cuplikan layar visualisasi data beberapa perangkat menggunakan layanan Web PubSub.

Membersihkan sumber daya

Jika berencana untuk melanjutkan bekerja dengan mulai cepat dan tutorial berikutnya, Anda mungkin ingin membiarkan sumber daya ini tetap di tempatnya.

Jika tidak lagi dibutuhkan, gunakan perintah az group delete Azure CLI untuk menghapus grup sumber daya, dan semua sumber daya terkait:

az group delete --name "myResourceGroup"

Langkah berikutnya