次の方法で共有


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

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

  • 気温
  • 圧力
  • 湿度

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

サンプル デバイスも次のとおりです。

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

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

開始する前に

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

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

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

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

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

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

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

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

リモート監視ソリューションにデバイスを追加するには、ソリューションの Device Explorer ページで次の手順を実行します。

  1. [新しいデバイス] を選択し、次に [Real] を [デバイスの種類] として選択します。

    実デバイス を追加する

  2. デバイス ID として 物理冷却装置 を入力します。 [対称キー ] および [キーの自動生成 ] オプションを選択します。

    デバイス オプションの選択

  3. [ 適用] を選択します。 次に、デバイス ID主キー、および接続文字列の主キー の値 を書き留めてください。

    資格情報の取得

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

クライアント アプリケーションは、組み込みの Chiller デバイス モデルを実装します。 ソリューション アクセラレータ デバイス モデルでは、デバイスに関する次の項目を指定します。

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

このチュートリアルでは、実際のデバイスをリモート監視ソリューション アクセラレータに接続する方法について説明します。 制約付きデバイスで実行されるほとんどの埋め込みアプリケーションと同様に、Raspberry Pi デバイス アプリケーションのクライアント コードは C で記述されます。このチュートリアルでは、Raspbian OS を実行している Raspberry Pi 上にアプリケーションをビルドします。

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

必要なハードウェア

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

Microsoft IoT Starter Kit for Raspberry Pi 3 または同等のコンポーネント。 このチュートリアルでは、キットの次の項目を使用します。

  • Raspberry Pi 3
  • MicroSD カード (NOOBS 付き)
  • USB ミニ ケーブル
  • イーサネット ケーブル

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

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

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

必要な Raspberry Pi ソフトウェア

この記事では、Raspberry Pi に最新バージョンの Raspbian OS がインストールされていることを前提としています。

次の手順では、ソリューション アクセラレータに接続する C アプリケーションをビルドするために Raspberry Pi を準備する方法を示します。

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

  2. Raspberry Pi を更新するには、次のコマンドを使用します。

    sudo apt-get update
    
  3. このハウツー ガイドの手順を完了するには、 Linux 開発環境をセットアップ して必要な開発ツールとライブラリを Raspberry Pi に追加する手順に従います。

コードを表示する

このガイドで使用 サンプル コードは、Azure IoT C SDK GitHub リポジトリで入手できます。

ソース コードをダウンロードしてプロジェクトを準備する

プロジェクトを準備するには、GitHub から Azure IoT C SDK リポジトリ を複製またはダウンロードします。

サンプルは、samples/solutions/remote_monitoring_client フォルダーにあります。

テキスト エディターの samples/solutions/remote_monitoring_client フォルダーにある remote_monitoring.c ファイルを開きます。

コードチュートリアル

このセクションでは、サンプル コードの重要な部分の一部と、リモート監視ソリューション アクセラレータとの関連について説明します。

次のスニペットは、デバイスの機能を説明する報告されるプロパティがどのように定義されているかを示しています。 これらのプロパティには次のものが含まれます。

  • ソリューション アクセラレータがデバイスをマップに追加できるようにするデバイスの場所。
  • 現在のファームウェアのバージョン。
  • デバイスがサポートするメソッドの一覧。
  • デバイスによって送信されるテレメトリ メッセージのスキーマ。
typedef struct MESSAGESCHEMA_TAG
{
    char* name;
    char* format;
    char* fields;
} MessageSchema;

typedef struct TELEMETRYSCHEMA_TAG
{
    MessageSchema messageSchema;
} TelemetrySchema;

typedef struct TELEMETRYPROPERTIES_TAG
{
    TelemetrySchema temperatureSchema;
    TelemetrySchema humiditySchema;
    TelemetrySchema pressureSchema;
} TelemetryProperties;

typedef struct CHILLER_TAG
{
    // Reported properties
    char* protocol;
    char* supportedMethods;
    char* type;
    char* firmware;
    FIRMWARE_UPDATE_STATUS firmwareUpdateStatus;
    char* location;
    double latitude;
    double longitude;
    TelemetryProperties telemetry;

    // Manage firmware update process
    char* new_firmware_version;
    char* new_firmware_URI;
} Chiller;

このサンプルには、Parson ライブラリを使用してこのデータ構造をシリアル化する serializeToJson 関数が含まれています。

このサンプルには、クライアントがソリューション アクセラレータと対話する場合にコンソールに情報を出力するコールバック関数がいくつか含まれています。

  • connection_status_callback
  • send_confirm_callback
  • reported_state_callback
  • device_method_callback (デバイスメソッドコールバック)

次のスニペットは、device_method_callback 関数を示しています。 この関数は、ソリューション アクセラレータからメソッド呼び出しを受信したときに実行するアクションを決定します。 この関数は、userContextCallback パラメーター内の Chiller データ構造への参照を受け取ります。 userContextCallback の値は、コールバック関数が main 関数で構成されるときに設定されます。

static int device_method_callback(const char* method_name, const unsigned char* payload, size_t size, unsigned char** response, size_t* response_size, void* userContextCallback)
{
    Chiller *chiller = (Chiller *)userContextCallback;

    int result;

    (void)printf("Direct method name:    %s\r\n", method_name);

    (void)printf("Direct method payload: %.*s\r\n", (int)size, (const char*)payload);

    if (strcmp("Reboot", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Rebooting\" }")
    }
    else if (strcmp("EmergencyValveRelease", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Releasing emergency valve\" }")
    }
    else if (strcmp("IncreasePressure", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Increasing pressure\" }")
    }
    else if (strcmp("FirmwareUpdate", method_name) == 0)
    {
        if (chiller->firmwareUpdateStatus != IDLE)
        {
            (void)printf("Attempt to invoke firmware update out of order\r\n");
            MESSAGERESPONSE(400, "{ \"Response\": \"Attempting to initiate a firmware update out of order\" }")
        }
        else
        {
            getFirmwareUpdateValues(chiller, payload);

            if (chiller->new_firmware_version != NULL && chiller->new_firmware_URI != NULL)
            {
                // Create a thread for the long-running firmware update process.
                THREAD_HANDLE thread_apply;
                THREADAPI_RESULT t_result = ThreadAPI_Create(&thread_apply, do_firmware_update, chiller);
                if (t_result == THREADAPI_OK)
                {
                    (void)printf("Starting firmware update thread\r\n");
                    MESSAGERESPONSE(201, "{ \"Response\": \"Starting firmware update thread\" }")
                }
                else
                {
                    (void)printf("Failed to start firmware update thread\r\n");
                    MESSAGERESPONSE(500, "{ \"Response\": \"Failed to start firmware update thread\" }")
                }
            }
            else
            {
                (void)printf("Invalid method payload\r\n");
                MESSAGERESPONSE(400, "{ \"Response\": \"Invalid payload\" }")
            }
        }
    }
    else
    {
        // All other entries are ignored.
        (void)printf("Method not recognized\r\n");
        MESSAGERESPONSE(400, "{ \"Response\": \"Method not recognized\" }")
    }

    return result;
}

ソリューション アクセラレータがファームウェア更新メソッドを呼び出すと、サンプルは JSON ペイロードを逆シリアル化し、バックグラウンド スレッドを開始して更新プロセスを完了します。 次のスニペットは、スレッドで実行される do_firmware_update を示しています。

/*
 This is a thread allocated to process a long-running device method call.
 It uses device twin reported properties to communicate status values
 to the Remote Monitoring solution accelerator.
*/
static int do_firmware_update(void *param)
{
    Chiller *chiller = (Chiller *)param;
    printf("Running simulated firmware update: URI: %s, Version: %s\r\n", chiller->new_firmware_URI, chiller->new_firmware_version);

    printf("Simulating download phase...\r\n");
    chiller->firmwareUpdateStatus = DOWNLOADING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    printf("Simulating apply phase...\r\n");
    chiller->firmwareUpdateStatus = APPLYING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    printf("Simulating reboot phase...\r\n");
    chiller->firmwareUpdateStatus = REBOOTING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    size_t size = strlen(chiller->new_firmware_version) + 1;
    (void)memcpy(chiller->firmware, chiller->new_firmware_version, size);

    chiller->firmwareUpdateStatus = IDLE;
    sendChillerReportedProperties(chiller);

    return 0;
}

次のスニペットは、クライアントがソリューション アクセラレータにテレメトリ メッセージを送信する方法を示しています。 メッセージのプロパティには、ソリューション アクセラレータがダッシュボードにテレメトリを表示するのに役立つメッセージ スキーマが含まれています。

static void send_message(IOTHUB_DEVICE_CLIENT_HANDLE handle, char* message, char* schema)
{
    IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(message);
    if (message_handle != NULL)
    {
        // Set system properties
        (void)IoTHubMessage_SetMessageId(message_handle, "MSG_ID");
        (void)IoTHubMessage_SetCorrelationId(message_handle, "CORE_ID");
        (void)IoTHubMessage_SetContentTypeSystemProperty(message_handle, "application%2fjson");
        (void)IoTHubMessage_SetContentEncodingSystemProperty(message_handle, "utf-8");

        // Set application properties
        MAP_HANDLE propMap = IoTHubMessage_Properties(message_handle);
        (void)Map_AddOrUpdate(propMap, "$$MessageSchema", schema);
        (void)Map_AddOrUpdate(propMap, "$$ContentType", "JSON");

        time_t now = time(0);
        struct tm* timeinfo;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4996) /* Suppress warning about possible unsafe function in Visual Studio */
#endif
        timeinfo = gmtime(&now);
#ifdef _MSC_VER
#pragma warning(pop)
#endif
        char timebuff[50];
        strftime(timebuff, 50, "%Y-%m-%dT%H:%M:%SZ", timeinfo);
        (void)Map_AddOrUpdate(propMap, "$$CreationTimeUtc", timebuff);

        IoTHubDeviceClient_SendEventAsync(handle, message_handle, send_confirm_callback, NULL);

        IoTHubMessage_Destroy(message_handle);
    }
}

サンプル main 関数は次のとおりです。

  • SDK サブシステムを初期化してシャットダウンします。
  • Chiller データ構造を初期化します。
  • 報告されたプロパティをソリューション アクセラレータに送信します。
  • デバイス メソッドコールバック関数を構成します。
  • シミュレートされたテレメトリ値をソリューション アクセラレータに送信します。
int main(void)
{
    srand((unsigned int)time(NULL));
    double minTemperature = 50.0;
    double minPressure = 55.0;
    double minHumidity = 30.0;
    double temperature = 0;
    double pressure = 0;
    double humidity = 0;

    (void)printf("This sample simulates a Chiller device connected to the Remote Monitoring solution accelerator\r\n\r\n");

    // Used to initialize sdk subsystem
    (void)IoTHub_Init();

    (void)printf("Creating IoTHub handle\r\n");
    // Create the iothub handle here
    device_handle = IoTHubDeviceClient_CreateFromConnectionString(connectionString, MQTT_Protocol);
    if (device_handle == NULL)
    {
        (void)printf("Failure creating IotHub device. Hint: Check your connection string.\r\n");
    }
    else
    {
        // Setting connection status callback to get indication of connection to iothub
        (void)IoTHubDeviceClient_SetConnectionStatusCallback(device_handle, connection_status_callback, NULL);

        Chiller chiller;
        memset(&chiller, 0, sizeof(Chiller));
        chiller.protocol = "MQTT";
        chiller.supportedMethods = "Reboot,FirmwareUpdate,EmergencyValveRelease,IncreasePressure";
        chiller.type = "Chiller";
        size_t size = strlen(initialFirmwareVersion) + 1;
        chiller.firmware = malloc(size);
        if (chiller.firmware == NULL)
        {
            (void)printf("Chiller Firmware failed to allocate memory.\r\n");
        }
        else
        {
            memcpy(chiller.firmware, initialFirmwareVersion, size);
            chiller.firmwareUpdateStatus = IDLE;
            chiller.location = "Building 44";
            chiller.latitude = 47.638928;
            chiller.longitude = -122.13476;
            chiller.telemetry.temperatureSchema.messageSchema.name = "chiller-temperature;v1";
            chiller.telemetry.temperatureSchema.messageSchema.format = "JSON";
            chiller.telemetry.temperatureSchema.messageSchema.fields = "{\"temperature\":\"Double\",\"temperature_unit\":\"Text\"}";
            chiller.telemetry.humiditySchema.messageSchema.name = "chiller-humidity;v1";
            chiller.telemetry.humiditySchema.messageSchema.format = "JSON";
            chiller.telemetry.humiditySchema.messageSchema.fields = "{\"humidity\":\"Double\",\"humidity_unit\":\"Text\"}";
            chiller.telemetry.pressureSchema.messageSchema.name = "chiller-pressure;v1";
            chiller.telemetry.pressureSchema.messageSchema.format = "JSON";
            chiller.telemetry.pressureSchema.messageSchema.fields = "{\"pressure\":\"Double\",\"pressure_unit\":\"Text\"}";

            sendChillerReportedProperties(&chiller);

            (void)IoTHubDeviceClient_SetDeviceMethodCallback(device_handle, device_method_callback, &chiller);

            while (1)
            {
                temperature = minTemperature + ((double)(rand() % 10) + 5);
                pressure = minPressure + ((double)(rand() % 10) + 5);
                humidity = minHumidity + ((double)(rand() % 20) + 5);

                if (chiller.firmwareUpdateStatus == IDLE)
                {
                    (void)printf("Sending sensor value Temperature = %f %s,\r\n", temperature, "F");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"temperature\":%.2f,\"temperature_unit\":\"F\"}", temperature);
                    send_message(device_handle, msgText, chiller.telemetry.temperatureSchema.messageSchema.name);


                    (void)printf("Sending sensor value Pressure = %f %s,\r\n", pressure, "psig");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"pressure\":%.2f,\"pressure_unit\":\"psig\"}", pressure);
                    send_message(device_handle, msgText, chiller.telemetry.pressureSchema.messageSchema.name);


                    (void)printf("Sending sensor value Humidity = %f %s,\r\n", humidity, "%");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"humidity\":%.2f,\"humidity_unit\":\"%%\"}", humidity);
                    send_message(device_handle, msgText, chiller.telemetry.humiditySchema.messageSchema.name);
                }

                ThreadAPI_Sleep(5000);
            }

            (void)printf("\r\nShutting down\r\n");

            // Clean up the iothub sdk handle and free resources
            IoTHubDeviceClient_Destroy(device_handle);
            free(chiller.firmware);
            free(chiller.new_firmware_URI);
            free(chiller.new_firmware_version);
        }
    }
    // Shutdown the sdk subsystem
    IoTHub_Deinit();

    return 0;
}

アプリケーションの構築と実行

次の手順では、CMake を使用してクライアント アプリケーションをビルドする方法について説明します。 リモート監視クライアント アプリケーションは、SDK のビルド プロセスの一部としてビルドされます。

  1. remote_monitoring.c ファイルを編集して、<connectionstring> を、ソリューション アクセラレータにデバイスを追加したときにこのハウツー ガイドの冒頭で説明したデバイス接続文字列に置き換えます。

  2. Azure IoT C SDK リポジトリ リポジトリの複製されたコピーのルートに移動し、次のコマンドを実行してクライアント アプリケーションをビルドします。

    mkdir cmake
    cd cmake
    cmake ../
    make
    
  3. クライアント アプリケーションを実行し、IoT Hub にテレメトリを送信します。

    ./samples/solutions/remote_monitoring_client/remote_monitoring_client
    

    コンソールには、次のようにメッセージが表示されます。

    • アプリケーションは、サンプル テレメトリをソリューション アクセラレータに送信します。
    • ソリューション ダッシュボードから呼び出されたメソッドに応答します。

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

デバイスから送信されたテレメトリは、ソリューションの Device Explorer ページで確認できます。

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

    デバイスの詳細 を表示する

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

    圧力テレメトリの を表示する

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

    デバイス診断 を表示する

デバイスで操作する

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

  1. デバイス を選択して、ソリューションの Device Explorer ページに移動します。

  2. Device Explorer ページのデバイスの一覧でプロビジョニングしたデバイスを選択します。

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

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

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

    ファームウェア更新プログラムの をスケジュールする

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

ソリューション内のジョブの状態を追跡するには、[ジョブの状態の表示] 選択します。

次のステップ

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