デバイスをリモート監視ソリューション アクセラレータに接続する (Linux)
このチュートリアルでは、次のテレメトリを、リモート監視のソリューション アクセラレータに送信する Chiller デバイスを実装します。
- 気温
- 圧力
- 湿度
わかりやすくするために、コードでは、Chiller に対してサンプル テレメトリ値を生成します。 このサンプルを拡張するには、実際のセンサーをデバイスに接続し、実際のテレメトリを送信します。
サンプル デバイスでは、次の操作も行います。
- メタデータをソリューションに送信して、機能を記述する。
- ソリューションの [デバイス] ページからトリガーされたアクションに応答する。
- ソリューションの [デバイス] ページから送信された構成変更に応答する。
このチュートリアルを完了するには、アクティブな Azure アカウントが必要になります。 アカウントがない場合は、無料試用アカウントを数分で作成することができます。 詳細については、「Azure の無料試用版サイト」を参照してください。
開始する前に
デバイス用のコードを作成する前に、リモート監視ソリューション アクセラレータをデプロイし、そのソリューションに新しい実在のデバイスを追加します。
リモート監視ソリューション アクセラレータをデプロイする
このチュートリアルで作成する Chiller デバイスは、リモート監視ソリューション アクセラレータのインスタンスにデータを送信します。 リモート監視ソリューション アクセラレータを Azure アカウントにまだプロビジョニングしていない場合は、「Deploy the remote monitoring solution accelerator (リモート監視ソリューション アクセラレータをデプロイする)」を参照してください
リモート監視ソリューションのデプロイ プロセスが完了したら、 [起動] をクリックしてブラウザーでソリューション ダッシュボードを開きます。
デバイスをリモート監視ソリューションに追加する
Note
ソリューションにデバイスを既に追加している場合は、この手順を省略して構いません。 ただし、次の手順では、デバイスの接続文字列が必要です。 デバイスの接続文字列は、Azure Portal から、または az iot CLI ツールを使用して取得できます。
デバイスがソリューション アクセラレータに接続するには、有効な資格情報を使用して IoT Hub に対してデバイス自身の ID を証明する必要があります。 ソリューションにデバイスを追加するときに、これらの資格情報を含むデバイスの接続文字列を保存する機会が与えられます。 このチュートリアルの後半で、クライアント アプリケーションにデバイスの接続文字列を含めます。
デバイスをリモート監視ソリューションに追加するには、ソリューションの [デバイス エクスプローラー] ページで次の手順を実行します。
[+ 新規デバイス] を選択し、 [デバイスの種類] で [実際] を選択します。
デバイス ID として「Physical-chiller」と入力します。 [対称キー] オプションと [キーの自動生成] オプションを選択します。
[適用] を選択します。 デバイス ID、主キー、接続文字列の主キーの値をメモします。
これで、実在のデバイスをリモート監視ソリューション アクセラレータに追加して、デバイスの接続文字列をメモできました。 以降のセクションでは、デバイスの接続文字列を使用してソリューションに接続するクライアント アプリケーションを実装します。
クライアント アプリケーションでは、組み込みの Chiller デバイス モデルが実装されます。 ソリューション アクセラレータのデバイス モデルは、デバイスについて次の情報を指定します。
- デバイスがソリューションにレポートするプロパティ。 たとえば、Chiller デバイスは、そのファームウェアと位置に関する情報をレポートします。
- デバイスがソリューションに送信するテレメトリの種類。 たとえば、Chiller デバイスは、温度、湿度、および気圧の値を送信します。
- ソリューションからスケジュールしてデバイスで実行できるメソッド。 たとえば、Chiller デバイスは、Reboot、FirmwareUpdate、EmergencyValveRelease、および IncreasePressure メソッドを実装する必要があります。
このチュートリアルでは、実デバイスをリモート監視ソリューション アクセラレータに接続する方法を示します。
制限付きのデバイス上で実行される多くの組み込みアプリケーションと同様に、デバイス アプリケーションのためのクライアント コードは C で書かれています。このチュートリアルでは、Ubuntu (Linux) を実行しているコンピューターでアプリケーションを構築します。
デバイスをシミュレートする場合は、「新しいシミュレートされたデバイスの作成とテスト」を参照してください。
前提条件
この攻略ガイドの手順を完了するには、Ubuntu バージョン 15.04 以降が実行されているデバイスが必要です。 次に進む前に、Linux 開発環境を設定します。
コードを表示する
このガイドで使用されるサンプル コードは、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 のビルド プロセスの一環として構築されます。
remote_monitoring.c ファイルを編集し、
<connectionstring>
を、ソリューション アクセラレータにデバイスを追加したときにこのハウツー ガイドの冒頭でメモしたデバイス接続文字列に置き換えます。Azure IoT C SDK リポジトリリポジトリの複製されたコピーのルートに移動し、次のコマンドを実行してクライアント アプリケーションを構築します。
mkdir cmake cd cmake cmake ../ make
クライアント アプリケーションを実行し、テレメトリを IoT Hub に送信します。
./samples/solutions/remote_monitoring_client/remote_monitoring_client
コンソールには、次のメッセージが表示されます。
- アプリケーションは、ソリューション アクセラレータにサンプル テレメトリを送信します。
- ソリューションのダッシュ ボードから呼び出されたメソッドに応答します。
デバイス テレメトリを表示する
デバイスから送信されたテレメトリは、ソリューションの [デバイス エクスプローラー] ページで表示できます。
プロビジョニングしたデバイスを、 [デバイス エクスプローラー] ページのデバイスの一覧から選択します。 パネルには、デバイス テレメトリのプロットなど、デバイスに関する情報が表示されます。
[気圧] を選択して、テレメトリの表示を変更します。
デバイスに関する診断情報を表示するには、下へスクロールして、 [診断] に移動します。
デバイスを操作する
デバイスでメソッドを呼び出すには、リモート監視ソリューションの [デバイス エクスプローラー] ページを使用します。 たとえば、リモート監視ソリューションで、Chiller デバイスが Reboot メソッドを実装しています。
[デバイス] を選択して、ソリューションの [デバイス エクスプローラー] ページに移動します。
プロビジョニングしたデバイスを、 [デバイス エクスプローラー] ページのデバイスの一覧から選択します。
デバイスで呼び出すことができるメソッドの一覧を表示するには、 [ジョブ] を選択してから、 [メソッド] を選択します。 複数のデバイスで実行するジョブのスケジュールを設定するために、一覧から複数のデバイスを選択することができます。 [ジョブ] パネルには、選択したすべてのデバイスに共通のメソッドの型が表示されます。
[再起動] を選択し、ジョブ名を RebootPhysicalChiller に変更して、 [適用] を選択します。
シミュレートされたデバイスがメソッドを処理しているとき、デバイス コードを実行しているコンソールに一連のメッセージが表示されます。
注意
ソリューションでジョブの状態を追跡するには、 [View Job Status]\(ジョブ状態の表示\) を選択します。
次のステップ
「リモート監視の構成済みソリューションのカスタマイズ」の記事では、ソリューション アクセラレータをカスタマイズする方法をいくつか説明します。