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

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

  • 気温
  • 圧力
  • 湿度

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

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

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

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

開始する前に

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

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

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

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

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

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

Note

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

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

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

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

    実デバイスの追加

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

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

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

    資格情報の取得

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

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

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

このチュートリアルでは、実デバイスをリモート監視ソリューション アクセラレータに接続する方法を示します。

制限のあるデバイス上で実行されるほとんどの組み込みアプリケーションと同様、デバイス アプリケーションのためのクライアント コードは C で記述されています。このチュートリアルでは、Windows を実行しているコンピューターでデバイス クライアント アプリケーションを構築します。

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

前提条件

このハウツー ガイドの手順を完了するには、Windows 開発環境の設定に関するページに示されている手順に従って、必要な開発ツールとライブラリを Windows コンピューターに追加します。

コードを表示する

このガイドで使用されるサンプル コードは、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;
}

サンプルのビルドと実行

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

  2. Build the C SDK in Windows」 (Windows で C SDK を構築する) の手順に従って、SDK とリモート監視クライアント アプリケーションを構築します。

  3. ソリューションを構築するために使用したコマンド プロンプトで、次のコマンドを実行します。

    samples\solutions\remote_monitoring_client\Release\remote_monitoring_client.exe
    

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

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

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

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

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

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

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

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

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

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

デバイスを操作する

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

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

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

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

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

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

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

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

注意

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

次のステップ

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