Raspberry Pi デバイスをリモート監視ソリューション アクセラレータに接続する (Node.js)

このチュートリアルでは、次のテレメトリを、リモート監視のソリューション アクセラレータに送信する Chiller デバイスを実装します。

  • 気温
  • 圧力
  • 湿度

わかりやすくするために、コードでは、Chiller に対してサンプル テレメトリ値を生成します。 このサンプルを拡張するには、実際のセンサーをデバイスに接続し、実際のテレメトリを送信します。

サンプル デバイスでは、次の操作も行います。

  • メタデータをソリューションに送信して、機能を記述する。
  • ソリューションの [デバイス] ページからトリガーされたアクションに応答する。
  • ソリューションの [デバイス] ページから送信された構成変更に応答する。

このチュートリアルを完了するには、アクティブな Azure アカウントが必要になります。 アカウントがない場合は、無料試用アカウントを数分で作成することができます。 詳細については、「Azure の無料試用版サイト」を参照してください。

開始する前に

デバイス用のコードを作成する前に、リモート監視ソリューション アクセラレータをデプロイし、そのソリューションに新しい実在のデバイスを追加します。

リモート監視ソリューション アクセラレータをデプロイする

このチュートリアルで作成する Chiller デバイスは、リモート監視ソリューション アクセラレータのインスタンスにデータを送信します。 リモート監視ソリューション アクセラレータを Azure アカウントにまだプロビジョニングしていない場合は、「Deploy the remote monitoring solution accelerator (リモート監視ソリューション アクセラレータをデプロイする)」を参照してください

リモート監視ソリューションのデプロイ プロセスが完了したら、 [起動] をクリックしてブラウザーでソリューション ダッシュボードを開きます。

ソリューション ダッシュボード

デバイスをリモート監視ソリューションに追加する

注意

ソリューションにデバイスを既に追加している場合は、この手順を省略して構いません。 ただし、次の手順では、デバイスの接続文字列が必要です。 デバイスの接続文字列は、Azure Portal から、または az iot CLI ツールを使用して取得できます。

デバイスがソリューション アクセラレータに接続するには、有効な資格情報を使用して IoT Hub に対してデバイス自身の ID を証明する必要があります。 ソリューションにデバイスを追加するときに、これらの資格情報を含むデバイスの接続文字列を保存する機会が与えられます。 このチュートリアルの後半で、クライアント アプリケーションにデバイスの接続文字列を含めます。

デバイスをリモート監視ソリューションに追加するには、ソリューションの [デバイス エクスプローラー] ページで次の手順を実行します。

  1. [+ 新規デバイス] を選択し、 [デバイスの種類][実際] を選択します。

    実デバイスの追加

  2. デバイス ID として「Physical-chiller」と入力します。 [対称キー] オプションと [キーの自動生成] オプションを選択します。

    デバイス オプションを選択する

  3. [適用] を選択します。 デバイス ID主キー接続文字列の主キーの値をメモします。

    資格情報の取得

これで、実在のデバイスをリモート監視ソリューション アクセラレータに追加して、デバイスの接続文字列をメモできました。 以降のセクションでは、デバイスの接続文字列を使用してソリューションに接続するクライアント アプリケーションを実装します。

クライアント アプリケーションでは、組み込みの Chiller デバイス モデルが実装されます。 ソリューション アクセラレータのデバイス モデルは、デバイスについて次の情報を指定します。

  • デバイスがソリューションにレポートするプロパティ。 たとえば、Chiller デバイスは、そのファームウェアと位置に関する情報をレポートします。
  • デバイスがソリューションに送信するテレメトリの種類。 たとえば、Chiller デバイスは、温度、湿度、および気圧の値を送信します。
  • ソリューションからスケジュールしてデバイスで実行できるメソッド。 たとえば、Chiller デバイスは、RebootFirmwareUpdateEmergencyValveRelease、および IncreasePressure メソッドを実装する必要があります。

このチュートリアルでは、実デバイスをリモート監視ソリューション アクセラレータに接続する方法を示します。 このチュートリアルでは、リソースの制約が少ない環境に適したオプションである Node.js を使用します。

デバイスをシミュレートする場合は、「新しいシミュレートされたデバイスの作成とテスト」を参照してください。

必要なハードウェア

Raspberry Pi でコマンド ラインにリモート接続するためのデスクトップ コンピューター。

Raspberry Pi 3 用 Microsoft IoT スタート キットまたは同等のコンポーネント。 このチュートリアルでは、キット内の次のものを使用します。

  • Raspberry Pi 3
  • microSD カード (NOOBS をインストール済み)
  • USB ミニ ケーブル
  • イーサネット ケーブル

必要なデスクトップ ソフトウェア

Raspberry Pi でコマンド ラインにリモートでアクセスするための SSH クライアントがデスクトップ コンピューターに必要です。

  • Windows には SSH クライアントは含まれていません。 PuTTY を使用することをお勧めします。
  • ほとんどの Linux ディストリビューションと Mac OS には、コマンド ライン SSH ユーティリティが含まれています。 詳細については、「SSH Using Linux or Mac OS (Linux または Mac OS を使用した SSH 接続)」を参照してください。

必要な Raspberry Pi ソフトウェア

まだインストールしていない場合は、Raspberry Pi に Node.js 4.0.0 以上をインストールします。 次の手順では、Node.js v6 を Raspberry Pi にインストールする方法を示します。

  1. ssh を使用して Raspberry Pi に接続します。 詳細については、Raspberry Pi の Web サイトSSH (Secure Shell) のセクションを参照してください。

  2. 次のコマンドを使用して Raspberry Pi を更新します。

    sudo apt-get update
    
  3. 次のコマンドを使用して、既存の Node.js のインストールを Raspberry Pi から削除します。

    sudo apt-get remove nodered -y
    sudo apt-get remove nodejs nodejs-legacy -y
    sudo apt-get remove npm  -y
    
  4. 次のコマンドを使用して、Node.js v6 を Raspberry Pi にダウンロードしてインストールします。

    curl -sL https://deb.nodesource.com/setup_6.x | sudo bash -
    sudo apt-get install nodejs npm
    
  5. 次のコマンドを使用して、Node.js v6.11.4 が正常にインストールされたことを確認します。

    node --version
    

Node.js ソリューションを作成する

Raspberry Pi への ssh 接続を使用して、次の手順を実行します。

  1. remotemonitoring という名前のフォルダーを Raspberry Pi のホーム フォルダーで作成します。 コマンド ラインでこのフォルダーに移動します。

    cd ~
    mkdir remotemonitoring
    cd remotemonitoring
    
  2. 次のコマンドを実行して、サンプル アプリケーションを完成させるために必要なパッケージをダウンロードしてインストールします。

    npm install async azure-iot-device azure-iot-device-mqtt
    
  3. remotemonitoring フォルダーで、remote_monitoring.js という名前のファイルを作成します。 このファイルをテキスト エディターで開きます。 Raspberry Pi では、nano または vi テキスト エディターを使用できます。

  4. remote_monitoring.js ファイルに次の require ステートメントを追加します。

    var Protocol = require('azure-iot-device-mqtt').Mqtt;
    var Client = require('azure-iot-device').Client;
    var Message = require('azure-iot-device').Message;
    var async = require('async');
    
  5. require ステートメントの後に次の変数宣言を追加します。 プレースホルダー値 {device connection string} を、リモート監視ソリューションでプロビジョニングしたデバイス用の値に置き換えます。

    var connectionString = '{device connection string}';
    
  6. 基本のテレメトリをいくつか定義するために、次の変数を追加します。

    var temperature = 50;
    var temperatureUnit = 'F';
    var humidity = 50;
    var humidityUnit = '%';
    var pressure = 55;
    var pressureUnit = 'psig';
    
  7. プロパティ値を定義するには、次の変数を追加します。

    var schema = "real-chiller;v1";
    var deviceType = "RealChiller";
    var deviceFirmware = "1.0.0";
    var deviceFirmwareUpdateStatus = "";
    var deviceLocation = "Building 44";
    var deviceLatitude = 47.638928;
    var deviceLongitude = -122.13476;
    var deviceOnline = true;
    
  8. ソリューションに送信する、報告対象プロパティを定義するには、次の変数を追加します。 これらのプロパティには、Web UI に表示するメタデータが含まれます。

    var reportedProperties = {
      "SupportedMethods": "Reboot,FirmwareUpdate,EmergencyValveRelease,IncreasePressure",
      "Telemetry": {
        [schema]: ""
      },
      "Type": deviceType,
      "Firmware": deviceFirmware,
      "FirmwareUpdateStatus": deviceFirmwareUpdateStatus,
      "Location": deviceLocation,
      "Latitude": deviceLatitude,
      "Longitude": deviceLongitude,
      "Online": deviceOnline
    }
    
  9. 操作結果を出力するには、次のヘルパー関数を追加します。

    function printErrorFor(op) {
        return function printError(err) {
            if (err) console.log(op + ' error: ' + err.toString());
        };
    }
    
  10. テレメトリの値をランダム化するために使用する次のヘルパー関数を追加します。

    function generateRandomIncrement() {
        return ((Math.random() * 2) - 1);
    }
    
  11. ソリューションからのダイレクト メソッドの呼び出しを処理するため、次の汎用関数を追加します。 この関数は、呼び出されたダイレクト メソッドに関する情報を表示しますが、このサンプルではデバイスを変更しません。 ソリューションは、次のようにデバイスで動作するダイレクト メソッドを使用します。

    function onDirectMethod(request, response) {
      // Implement logic asynchronously here.
      console.log('Simulated ' + request.methodName);
    
      // Complete the response
      response.send(200, request.methodName + ' was called on the device', function (err) {
        if (err) console.error('Error sending method response :\n' + err.toString());
        else console.log('200 Response to method \'' + request.methodName + '\' sent successfully.');
      });
    }
    
  12. ソリューションからの FirmwareUpdate ダイレクト メソッドの呼び出しを処理するために、次の関数を追加します。 この関数は、ダイレクト メソッドのペイロードで渡されたパラメーターを検証し、ファームウェア更新シミュレーションを非同期に実行します。

    function onFirmwareUpdate(request, response) {
      // Get the requested firmware version from the JSON request body
      var firmwareVersion = request.payload.Firmware;
      var firmwareUri = request.payload.FirmwareUri;
    
      // Ensure we got a firmware values
      if (!firmwareVersion || !firmwareUri) {
        response.send(400, 'Missing firmware value', function(err) {
          if (err) console.error('Error sending method response :\n' + err.toString());
          else console.log('400 Response to method \'' + request.methodName + '\' sent successfully.');
        });
      } else {
        // Respond the cloud app for the device method
        response.send(200, 'Firmware update started.', function(err) {
          if (err) console.error('Error sending method response :\n' + err.toString());
          else {
            console.log('200 Response to method \'' + request.methodName + '\' sent successfully.');
    
            // Run the simulated firmware update flow
            runFirmwareUpdateFlow(firmwareVersion, firmwareUri);
          }
        });
      }
    }
    
  13. ソリューションに進行状況を報告する実行時間の長いファームウェア更新フローをシミュレートするために、次の関数を追加します。

    // Simulated firmwareUpdate flow
    function runFirmwareUpdateFlow(firmwareVersion, firmwareUri) {
      console.log('Simulating firmware update flow...');
      console.log('> Firmware version passed: ' + firmwareVersion);
      console.log('> Firmware URI passed: ' + firmwareUri);
      async.waterfall([
        function (callback) {
          console.log("Image downloading from " + firmwareUri);
          var patch = {
            FirmwareUpdateStatus: 'Downloading image..'
          };
          reportUpdateThroughTwin(patch, callback);
          sleep(10000, callback);
        },
        function (callback) {
          console.log("Downloaded, applying firmware " + firmwareVersion);
          deviceOnline = false;
          var patch = {
            FirmwareUpdateStatus: 'Applying firmware..',
            Online: false
          };
          reportUpdateThroughTwin(patch, callback);
          sleep(8000, callback);
        },
        function (callback) {
          console.log("Rebooting");
          var patch = {
            FirmwareUpdateStatus: 'Rebooting..'
          };
          reportUpdateThroughTwin(patch, callback);
          sleep(10000, callback);
        },
        function (callback) {
          console.log("Firmware updated to " + firmwareVersion);
          deviceOnline = true;
          var patch = {
            FirmwareUpdateStatus: 'Firmware updated',
            Online: true,
            Firmware: firmwareVersion
          };
          reportUpdateThroughTwin(patch, callback);
          callback(null);
        }
      ], function(err) {
        if (err) {
          console.error('Error in simulated firmware update flow: ' + err.message);
        } else {
          console.log("Completed simulated firmware update flow");
        }
      });
    
      // Helper function to update the twin reported properties.
      function reportUpdateThroughTwin(patch, callback) {
        console.log("Sending...");
        console.log(JSON.stringify(patch, null, 2));
        client.getTwin(function(err, twin) {
          if (!err) {
            twin.properties.reported.update(patch, function(err) {
              if (err) callback(err);
            });      
          } else {
            if (err) callback(err);
          }
        });
      }
    
      function sleep(milliseconds, callback) {
        console.log("Simulate a delay (milleseconds): " + milliseconds);
        setTimeout(function () {
          callback(null);
        }, milliseconds);
      }
    }
    
  14. ソリューションにテレメトリを送信する次のコードを追加します。 クライアント アプリでは、次のようにメッセージ スキーマを識別するためのプロパティをメッセージに追加します。

    function sendTelemetry(data, schema) {
      if (deviceOnline) {
        var d = new Date();
        var payload = JSON.stringify(data);
        var message = new Message(payload);
        message.properties.add('iothub-creation-time-utc', d.toISOString());
        message.properties.add('iothub-message-schema', schema);
    
        console.log('Sending device message data:\n' + payload);
        client.sendEvent(message, printErrorFor('send event'));
      } else {
        console.log('Offline, not sending telemetry');
      }
    }
    
  15. クライアント インスタンスを作成するために次のコードを追加します。

    var client = Client.fromConnectionString(connectionString, Protocol);
    
  16. 次の処理を行うために、以下のコードを追加します。

    • 接続を開きます。

    • 必要なプロパティのハンドラーを設定します。

    • 報告されたプロパティを送信します。

    • ダイレクト メソッドのハンドラーを登録します。 このサンプルでは、ファームウェア更新ダイレクト メソッドに別のハンドラーを使用しています。

    • テレメトリの送信を開始します。

      client.open(function (err) {
      if (err) {
        printErrorFor('open')(err);
      } else {
        // Create device Twin
        client.getTwin(function (err, twin) {
          if (err) {
            console.error('Could not get device twin');
          } else {
            console.log('Device twin created');
      
            twin.on('properties.desired', function (delta) {
              // Handle desired properties set by solution
              console.log('Received new desired properties:');
              console.log(JSON.stringify(delta));
            });
      
            // Send reported properties
            twin.properties.reported.update(reportedProperties, function (err) {
              if (err) throw err;
              console.log('Twin state reported');
            });
      
            // Register handlers for all the method names we are interested in.
            // Consider separate handlers for each method.
            client.onDeviceMethod('Reboot', onDirectMethod);
            client.onDeviceMethod('FirmwareUpdate', onFirmwareUpdate);
            client.onDeviceMethod('EmergencyValveRelease', onDirectMethod);
            client.onDeviceMethod('IncreasePressure', onDirectMethod);
          }
        });
      
        // Start sending telemetry
        var sendDeviceTelemetry = setInterval(function () {
          temperature += generateRandomIncrement();
          pressure += generateRandomIncrement();
          humidity += generateRandomIncrement();
          var data = {
            'temperature': temperature,
            'temperature_unit': temperatureUnit,
            'humidity': humidity,
            'humidity_unit': humidityUnit,
            'pressure': pressure,
            'pressure_unit': pressureUnit
          };
          sendTelemetry(data, schema)
        }, 5000);
      
        client.on('error', function (err) {
          printErrorFor('client')(err);
          if (sendTemperatureInterval) clearInterval(sendTemperatureInterval);
          if (sendHumidityInterval) clearInterval(sendHumidityInterval);
          if (sendPressureInterval) clearInterval(sendPressureInterval);
          client.close(printErrorFor('client.close'));
        });
      }
      });
      
  17. 変更を remote_monitoring.js ファイルに保存します。

  18. Raspberry Pi 上のコマンド プロンプトで次のコマンドを実行して、サンプル アプリケーションを起動します。

    node remote_monitoring.js
    

デバイス テレメトリを表示する

デバイスから送信されたテレメトリは、ソリューションの [デバイス エクスプローラー] ページで表示できます。

  1. プロビジョニングしたデバイスを、 [デバイス エクスプローラー] ページのデバイスの一覧から選択します。 パネルには、デバイス テレメトリのプロットなど、デバイスに関する情報が表示されます。

    デバイスの詳細を確認する

  2. [気圧] を選択して、テレメトリの表示を変更します。

    気圧テレメトリを表示する

  3. デバイスに関する診断情報を表示するには、下へスクロールして、 [診断] に移動します。

    デバイスの診断を表示する

デバイスを操作する

デバイスでメソッドを呼び出すには、リモート監視ソリューションの [デバイス エクスプローラー] ページを使用します。 たとえば、リモート監視ソリューションで、Chiller デバイスが Reboot メソッドを実装しています。

  1. [デバイス] を選択して、ソリューションの [デバイス エクスプローラー] ページに移動します。

  2. プロビジョニングしたデバイスを、 [デバイス エクスプローラー] ページのデバイスの一覧から選択します。

    実際のデバイスを選択する

  3. デバイスで呼び出すことができるメソッドの一覧を表示するには、 [ジョブ] を選択してから、 [メソッド] を選択します。 複数のデバイスで実行するジョブのスケジュールを設定するために、一覧から複数のデバイスを選択することができます。 [ジョブ] パネルには、選択したすべてのデバイスに共通のメソッドの型が表示されます。

  4. [再起動] を選択し、ジョブ名を RebootPhysicalChiller に変更して、 [適用] を選択します。

    ファームウェア更新のスケジュール

  5. シミュレートされたデバイスがメソッドを処理しているとき、デバイス コードを実行しているコンソールに一連のメッセージが表示されます。

Note

ソリューションでジョブの状態を追跡するには、 [View Job Status]\(ジョブ状態の表示\) を選択します。

次のステップ

「リモート監視の構成済みソリューションのカスタマイズ」の記事では、ソリューション アクセラレータをカスタマイズする方法をいくつか説明します。