このトピックでは、Link SDK for C の ./demos/data_model_basic_demo.c ファイルを例に、Link SDK API を使用してデバイスに Thing Specification Language (TSL) を実装する方法を説明します。
背景
ステップ 1:初期化
-
ヘッダーファイルをインクルードします。
…… …… #include "aiot_dm_api.h" 基になる依存関係を追加し、ログ出力機能を設定します。
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile); aiot_state_set_logcb(demo_state_logcb);-
aiot_dm_init を呼び出して、データモデルのクライアントインスタンスを作成し、初期化します。
dm_handle = aiot_dm_init(); if (dm_handle == NULL) { printf("aiot_dm_init failed"); return -1;}
ステップ 2: 機能の設定
aiot_dm_setopt を呼び出して、次の機能を設定します。
-
MQTT 接続ハンドルを関連付けます。
重要TSL 機能パラメーターを設定する前に、デバイス認証情報などの必須パラメーターが設定されていることを確認してください。 詳細については、「MQTT 接続パラメーターを設定する」をご参照ください。
-
サンプルコード:
aiot_dm_setopt(dm_handle, AIOT_DMOPT_MQTT_HANDLE, mqtt_handle); -
関連パラメータ:
パラメータ
値の例
説明
AIOT_DMOPT_MQTT_HANDLE
mqtt_handle
MQTT 接続ハンドルを関連付けます。 TSL リクエストはこの接続を使用します。
-
-
TSL メッセージを処理するコールバック関数を実装します。
-
TSL メッセージのコールバック関数を定義します。
static void demo_dm_recv_handler(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_handler, type = %d\r\n", recv->type); switch (recv->type) { /* プロパティ報告、イベント報告、期待プロパティ値の取得、または期待プロパティ値の削除のコマンドへの応答 */ case AIOT_DMRECV_GENERIC_REPLY: { demo_dm_recv_generic_reply(dm_handle, recv, userdata); } break; /* プロパティの設定 */ case AIOT_DMRECV_PROPERTY_SET: { demo_dm_recv_property_set(dm_handle, recv, userdata); } break; /* サービスを非同期に呼び出す */ case AIOT_DMRECV_ASYNC_SERVICE_INVOKE: { demo_dm_recv_async_service_invoke(dm_handle, recv, userdata); } break; /* サービスを同期的に呼び出す */ case AIOT_DMRECV_SYNC_SERVICE_INVOKE: { demo_dm_recv_sync_service_invoke(dm_handle, recv, userdata); } break; /* ダウンストリームバイナリデータ */ case AIOT_DMRECV_RAW_DATA: { demo_dm_recv_raw_data(dm_handle, recv, userdata); } break; /* バイナリ形式でサービスを同期的に呼び出す。このメッセージには、バイナリデータに加えて rrpc_id が含まれます。 */ case AIOT_DMRECV_RAW_SYNC_SERVICE_INVOKE: { demo_dm_recv_raw_sync_service_invoke(dm_handle, recv, userdata); } break; /* IoT Platform からのアップストリームバイナリデータへの応答 */ case AIOT_DMRECV_RAW_DATA_REPLY: { demo_dm_recv_raw_data_reply(dm_handle, recv, userdata); } break; default: break; } } -
TSL メッセージのコールバック関数を登録します。
-
サンプルコード:
aiot_dm_setopt(dm_handle, AIOT_DMOPT_RECV_HANDLER, (void *)demo_dm_recv_handler); -
関連パラメータ:
パラメータ
値の例
説明
AIOT_DMOPT_RECV_HANDLER
demo_dm_recv_handler
SDK は、TSL メッセージを受信すると、このコールバック関数を呼び出します。
-
-
-
IoT Platform が応答メッセージを送信するかどうかを設定します。
この設定は、IoT Platform がデバイスメッセージを受信した後に応答メッセージを送信するかどうかを指定します。
-
サンプルコード:
uint8_t post_reply = 1; …… …… aiot_dm_setopt(dm_handle, AIOT_DMOPT_POST_REPLY, (void *)&post_reply); -
関連パラメータ:
パラメータ
値の例
説明
AIOT_DMOPT_POST_REPLY
1
有効な値:
-
1: 応答メッセージを送信します。
-
0: 応答メッセージを送信しません。
-
-
AIOT_DMOPT_POST_REPLY パラメーターを
1に設定した場合、応答メッセージを処理するロジックを実装する必要があります。ビジネス要件に基づいてロジックを実装できます。サンプルコードではログを出力するのみです。
-
[ICA 標準データフォーマット (Alink JSON)]:製品を作成する際、データフォーマット パラメータを ICA 標準データフォーマット (Alink JSON) に設定します。
レスポンスメッセージは
recv->data.generic_reply構造体で返され、Alink データ形式を使用します。詳細については、「デバイスプロパティ、イベント、およびサービス」をご参照ください。static void demo_dm_recv_generic_reply(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata){ printf("demo_dm_recv_generic_reply msg_id = %d, code = %d, data = %.*s, message = %.*s\r\n", recv->data.generic_reply.msg_id, recv->data.generic_reply.code, recv->data.generic_reply.data_len, recv->data.generic_reply.data, recv->data.generic_reply.message_len, recv->data.generic_reply.message); } -
[パススルー/カスタムフォーマット]:IoT Platform コンソールの製品設定ページで、データフォーマット パラメータを パススルー/カスタム に設定します。
レスポンスメッセージは
recv->data.raw_data構造体で返されます。メッセージを送受信する前に、IoT Platform に解析スクリプトをアップロードする必要があります。詳細については、「メッセージの解析」をご参照ください。static void demo_dm_recv_raw_data_reply(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_raw_data_reply, receive reply for up_raw msg, data len = %d\r\n", recv->data.raw_data.data_len); }
-
-
ステップ 3: プロパティのレポート
デバイスが MQTT 接続を確立した後、aiot_dm_send を呼び出して IoT Platform にプロパティメッセージを送信します。プロパティメッセージを aiot_dm_msg_t 構造体に格納し、aiot_dm_send に渡します。
次のいずれかのデバイスデータ形式に基づいて、プロパティメッセージを報告するコードを記述してください。
[ICA 標準データフォーマット (Alink JSON)]
-
TSL モデルを追加します。
-
プロパティを報告する前に、IoT Platform コンソールにログインし、[製品詳細] ページで [機能定義] タブに移動して、デフォルトモジュールまたは
demo_extra_blockなどのカスタムモジュールの TSL モデルにプロパティを追加します。詳細については、「TSL モデルを手動で追加する」をご参照ください。次の表は、サンプルコードに対応する TSL モデルプロパティを示しています。
TSL モジュール名
機能名
識別子
タイプ
データ定義
読み取り/書き込みタイプ
デフォルトモジュール
ナイトライトスイッチ 1
LightSwitch
bool
-
0:オン
-
1:オフ
読み取り/書き込み
電源
Power
text
データ長:10,240
読み取り/書き込み
動作電流
WF
int32
-
値の範囲:0–10
-
ステップサイズ:1
-
単位:A
読み取り/書き込み
demo_extra_block
ナイトライトスイッチ 2
NightLightSwitch
bool
-
0:オン
-
1:オフ
読み取り/書き込み
-
-
この例の TSL ファイルをインポートすることもできます。手順については、「TSL モデルを一括で追加」をご参照ください。
-
-
message プロパティの内容を設定します。
-
サンプルコード:
-
デフォルトモジュール:
/* 単一のプロパティをレポートします。 */ demo_send_property_post(dm_handle, "{\"LightSwitch\": 0}"); /* 複数のプロパティをまとめてレポートします。 */ demo_send_property_batch_post(dm_handle, "{\"properties\":{\"Power\": [ {\"value\":\"on\",\"time\":1612684518000}],\"WF\": [{\"value\": 3,\"time\":1612684518000}]}}"); -
カスタムモジュール:
demo_send_property_post(dm_handle, "{\"demo_extra_block:NightLightSwitch\": 1}");
-
-
サンプルメッセージの説明:
-
報告された TSL モデルメッセージは JSON 形式です。次の表は、サンプルメッセージとそれに対応する Alink データ形式を示しています。詳細については、「デバイスプロパティを報告する」をご参照ください。
説明必要なのはデバイスデータのみです。これは、Alink データフォーマットの params パラメーターの値です。Link SDK が、レポートするメッセージのカプセル化を処理します。
メッセージタイプ
サンプルメッセージ
Alink データフォーマット
単一プロパティ
{\"LightSwitch\": 0}{ "id": "123", "version": "1.0", "sys":{ "ack":0 }, "params": { "LightSwitch": 0 }, "method": "thing.event.property.post" }複数プロパティ
{\"properties\":{\"Power\": [ {\"value\":\"on\",\"time\":1612684518000}],\"WF\": [{\"value\": 3,\"time\":1612684518000}]}}{ "id": 123, "version": "1.0", "sys":{ "ack":0 }, "method": "thing.event.property.batch.post", "params": { "properties": { "Power": [{ "value": "on", "time": 1612684518000 }, ], "WF": [{ "value": 3, "time": 1612684518000 }, ] }, } -
プロパティを一括でレポートすると、IoT Platform は応答メッセージを送信します。トピック
/sys/a18wP******/LightSwitch/thing/event/property/batch/post_replyをサブスクライブして、IoT Platform がメッセージを受信したことを確認します。サンプルコード:
aiot_mqtt_sub(mqtt_handle, "/sys/a18wP******/LightSwitch/thing/event/property/batch/post_reply", NULL, 1, NULL);各項目は次のとおりです。
a1oGs ******はデバイスのProductKeyです。LightSwitchは、デバイスのDeviceNameです。
-
-
-
プロパティを報告するための関数を定義します。
/* 単一のプロパティをレポートします。 */ int32_t demo_send_property_post(void *dm_handle, char *params) { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_PROPERTY_POST; msg.data.property_post.params = params; return aiot_dm_send(dm_handle, &msg); } /* 複数のプロパティをまとめてレポートします。 */ int32_t demo_send_property_batch_post(void *dm_handle, char *params) { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_PROPERTY_BATCH_POST; msg.data.property_batch_post.params = params; return aiot_dm_send(dm_handle, &msg); }
[カスタムデータフォーマット]
-
TSL モデルのデータ解析スクリプトを IoT Platform にアップロードします。
詳細については、「カスタムデータフォーマットのメッセージ」をご参照ください。
-
バイナリメッセージを報告するためのデバイスコードを記述します。
サンプルコード:
{ aiot_dm_msg_t msg; uint8_t raw_data[] = {0x01, 0x02}; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_RAW_DATA; msg.data.raw_data.data = raw_data; msg.data.raw_data.data_len = sizeof(raw_data); aiot_dm_send(dm_handle, &msg); }
TSL モデルを使用して通信する場合は、データトラフィックがしきい値を超えないようにしてください。
-
通信制限の詳細については、「接続と通信」をご参照ください。
メッセージ数がしきい値を超えた場合は、IoT Platformコンソールにログインして、蓄積されたメッセージを表示します。 詳細については、「コンシューマーグループの表示と監視」をご参照ください。
ステップ 4: プロパティの設定
IoT Platform クラウド API を呼び出し、SetDeviceProperty または SetDevicesProperty を使用してプロパティ設定コマンドをデバイスに送信します。 デバイスは aiot_mqtt_recv を呼び出してコマンドを受信し、demo_dm_recv_handler コールバック関数でそのコマンドを処理します。
次の情報を使用して、コールバック関数の処理ロジックを記述します。
-
注:
-
受信したメッセージは、aiot_dm_recv_handler_t 型の入力パラメーターとして渡され、aiot_dm_recv_t 構造体を使用して指定した場所に格納されます。
-
受信したメッセージ内のプロパティ設定コマンドの内容を含むフィールドは、次の表に示すように、デバイスデータ形式によって異なります。
デバイスデータ形式
メッセージフィールド
説明
[ICA 標準データ形式 (Alink JSON)]
recv->data.property_set.paramsAlink データ形式の params フィールドの値と一致します。詳細については、「プロパティメッセージ」をご参照ください。
[パススルー/カスタム形式]
recv->data.raw_dataデバイスはデータを解析する必要があります。詳細については、「パススルー/カスタム形式のメッセージ」をご参照ください。
-
-
推奨処理ロジック:
-
デバイスプロパティを更新します。
-
更新されたプロパティをレポートします。
-
-
サンプルコードの処理ロジック:
サンプルコードは、受信したメッセージを出力するだけです。設定されたプロパティを報告するデモを確認するには、
TODOコメントの下にあるコードブロックは、/*と*/の記号を削除してコメントアウトを解除してください。-
[ICA 標準データ形式 (Alink JSON)]
static void demo_dm_recv_property_set(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_property_set msg_id = %ld, params = %.*s\r\n", (unsigned long)recv->data.property_set.msg_id, recv->data.property_set.params_len, recv->data.property_set.params); /* TODO: 次のコードは、IoT Platform からのプロパティ設定コマンドに応答する方法を示しています。動作を確認するには、コメントを解除してください。 */ /* { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_PROPERTY_SET_REPLY; msg.data.property_set_reply.msg_id = recv->data.property_set.msg_id; msg.data.property_set_reply.code = 200; msg.data.property_set_reply.data = "{}"; int32_t res = aiot_dm_send(dm_handle, &msg); if (res < 0) { printf("aiot_dm_send failed\r\n"); } } */ } -
[パススルー/カスタム形式]
static void demo_dm_recv_raw_data(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_raw_data 生データ len = %d\r\n", recv->data.raw_data.data_len); /* TODO: 次のコードは、バイナリ形式でデータを送信する方法を示しています。この機能を使用するには、対応するパススルースクリプトを IoT Platform にデプロイする必要があります。 */ /* { aiot_dm_msg_t msg; uint8_t raw_data[] = {0x01, 0x02}; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_RAW_DATA; msg.data.raw_data.data = raw_data; msg.data.raw_data.data_len = sizeof(raw_data); aiot_dm_send(dm_handle, &msg); } */ }
-
ステップ 5:イベントの報告
デバイスが MQTT 接続を確立した後、aiot_dm_send を呼び出してイベントメッセージを IoT Platform に送信します。メッセージは aiot_dm_msg_t 構造体に格納され、パラメーターとして aiot_dm_send に渡されます。
次のいずれかのデバイスデータ形式に基づいて、イベントを報告するコードを記述します。
-
デバイスが[カスタムフォーマット]を使用している場合、イベントを報告するインターフェイスはプロパティのインターフェイスと同じです。詳細については、「ステップ 3 のカスタムフォーマットでメッセージを報告する手順」をご参照ください。
-
デバイスが[ICA 標準データフォーマット (Alink JSON)] を使用している場合は、次の手順に従ってデバイス側のコードを記述します。
-
TSL モデルを追加します。
-
イベントを報告する前に、IoT Platform コンソールにログインし、[製品詳細] ページで [機能定義] タブに移動し、製品のイベントを追加します。詳細については、「TSL モデルを手動で追加する」をご参照ください。
次の表では、サンプルコードで使用されている TSL モデルイベントについて説明します。
機能名
識別子
タイプ
出力パラメーター
エラー
Error
故障
-
パラメーター名:エラーコード
-
パラメーター識別子: ErrorCode
-
データ型:列挙型
-
列挙項目:
-
0:デバイスハードウェア障害
-
1:デバイスソフトウェア障害
-
2:不明なエラー
-
-
-
この例の TSL ファイルをインポートすることもできます。手順については、「TSL モデルを一括で追加」をご参照ください。
-
-
イベントメッセージの内容を設定します。
-
サンプルコード:
demo_send_event_post(dm_handle, "Error", "{\"ErrorCode\": 0}"); -
サンプルメッセージの説明:
デバイスは TSL モデルメッセージを JSON 形式で報告します。次の表に、サンプルメッセージとそれに対応する Alink データフォーマットを示します。詳細については、「デバイスイベントの報告」をご参照ください。
説明必要なのはデバイスデータのみです。これは、Alink データフォーマットの params パラメーターの値です。Link SDK が、レポートするメッセージのカプセル化を処理します。
サンプルペイロード
Alink フォーマット
{\"ErrorCode\": 0}{ "id": "123", "version": "1.0", "params": { "ErrorCode": 0 }, "method": "thing.event.Error.post" }
-
-
イベントメッセージを報告する関数を定義します。
int32_t demo_send_event_post(void *dm_handle, char *event_id, char *params) { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_EVENT_POST; msg.data.event_post.event_id = event_id; msg.data.event_post.params = params; return aiot_dm_send(dm_handle, &msg); }
ステップ 6: サービス呼び出し
IoT Platform のクラウド API InvokeThingService または InvokeThingsService を呼び出して、サービス呼び出しコマンドをデバイスに送信できます。デバイスは aiot_mqtt_recv を呼び出してコマンドを受信し、demo_dm_recv_handler コールバック関数の設定に基づいて対応するアクションを実行します。
次の情報を使用して、コールバック関数の処理ロジックを記述します。
-
使用上の注意:
-
受信したメッセージは、aiot_dm_recv_handler_t 型の入力パラメーターとして渡され、aiot_dm_recv_t 構造体を使用して指定した場所に格納されます。
-
サービス呼び出しコマンドの内容は、デバイスデータ形式によって異なります。次の表は、コマンド内容が格納されるフィールドを示しています。
デバイスデータ形式
同期フィールド
非同期フィールド
説明
[ICA 標準データ形式 (Alink JSON)]
recv->data.sync_service_invokerecv->data.async_service_invokeこのフィールドのデータは、Alink データの params パラメーターの値と同じ形式です。詳細については、「サービスメッセージ」をご参照ください。
[パススルー/カスタム形式]
recv->data.raw_service_invokerecv->data.raw_dataコマンド内容を解析するには、解析スクリプトをアップロードする必要があります。詳細については、「パススルー/カスタム形式のメッセージ」をご参照ください。
-
コールバック関数から応答を送信する代わりに、別の関数を使用して応答を処理する場合は、応答メッセージ内の次のパラメーターの値が、IoT Platform からのリクエストの値と一致することを確認してください。
-
同期呼び出し:
rrpc_idとmsg_id -
非同期呼び出し:
msg_id
-
-
IoT Platform にサービス応答を送信する際は、タイムアウト期間に注意してください。
-
同期呼び出し: 同期サービスメッセージを受信した後、8 秒以内に応答を送信する必要があります。そうしないと、デバイスがメッセージを受信した場合でも、IoT Platform は呼び出しが失敗したと見なします。
-
非同期呼び出し: ビジネス要件に基づいて、非同期呼び出しのカスタムタイムアウトロジックを定義できます。IoT Platform は制限を課しません。
-
-
-
推奨される処理ロジック:
-
サービスコマンドを実行します。
-
IoT Platform にサービス応答を返します。
-
-
サンプルコードの処理ロジック:
サンプルコードは、受信したメッセージを出力するだけです。設定されたプロパティを報告するデモを確認するには、
TODOコメントの下にあるコードブロックは、/*と*/の記号を削除してコメントアウトを解除してください。-
[ICA 標準データ形式 (Alink JSON)]
-
同期呼び出し:
static void demo_dm_recv_sync_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_sync_service_invoke msg_id = %ld, rrpc_id = %s, service_id = %s, params = %.*s\r\n", (unsigned long)recv->data.sync_service_invoke.msg_id, recv->data.sync_service_invoke.rrpc_id, recv->data.sync_service_invoke.service_id, recv->data.sync_service_invoke.params_len, recv->data.sync_service_invoke.params); /* TODO: 次のコードは、IoT Platform からの同期サービス呼び出しに応答する方法を示しています。コメントを解除すると、動作を確認できます。 */ /* { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_SYNC_SERVICE_REPLY; msg.data.sync_service_reply.rrpc_id = recv->data.sync_service_invoke.rrpc_id; msg.data.sync_service_reply.msg_id = recv->data.sync_service_invoke.msg_id; msg.data.sync_service_reply.code = 200; msg.data.sync_service_reply.service_id = "SetLightSwitchTimer"; msg.data.sync_service_reply.data = "{}"; int32_t res = aiot_dm_send(dm_handle, &msg); if (res < 0) { printf("aiot_dm_send failed\r\n"); } } */ } -
非同期呼び出し:
static void demo_dm_recv_async_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_async_service_invoke msg_id = %ld, service_id = %s, params = %.*s\r\n", (unsigned long)recv->data.async_service_invoke.msg_id, recv->data.async_service_invoke.service_id, recv->data.async_service_invoke.params_len, recv->data.async_service_invoke.params); /* TODO: 次のコードは、IoT Platform からの非同期サービス呼び出しに応答する方法を示しています。コメントを解除すると、動作を確認できます。 */ /* { aiot_dm_msg_t msg; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_ASYNC_SERVICE_REPLY; msg.data.async_service_reply.msg_id = recv->data.async_service_invoke.msg_id; msg.data.async_service_reply.code = 200; msg.data.async_service_reply.service_id = "ToggleLightSwitch"; msg.data.async_service_reply.data = "{\"dataA\": 20}"; int32_t res = aiot_dm_send(dm_handle, &msg); if (res < 0) { printf("aiot_dm_send failed\r\n"); } } */ }
-
-
[パススルー/カスタム形式]
-
非同期バイナリサービス呼び出しの処理ロジックは、バイナリプロパティ設定の場合と同じです。詳細については、「カスタムトピックメッセージの解析」をご参照ください。
-
次のサンプルコードは、同期バイナリサービス呼び出しの処理ロジックを示しています。
static void demo_dm_recv_raw_sync_service_invoke(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata) { printf("demo_dm_recv_raw_sync_service_invoke raw sync service rrpc_id = %s, data_len = %d\r\n", recv->data.raw_service_invoke.rrpc_id, recv->data.raw_service_invoke.data_len); /* TODO: 次のコードは、IoT Platform からの同期サービス呼び出しに応答する方法を示しています。コメントを解除すると、動作を確認できます。 */ /* { aiot_dm_msg_t msg; uint8_t raw_data[] = {0x01, 0x02}; memset(&msg, 0, sizeof(aiot_dm_msg_t)); msg.type = AIOT_DMMSG_RAW_SERVICE_REPLY; msg.data.raw_service_reply.rrpc_id = recv->data.raw_service_invoke.rrpc_id; msg.data.raw_service_reply.data = raw_data; msg.data.raw_service_reply.data_len = sizeof(raw_data); aiot_dm_send(dm_handle, &msg); } */ }
-
-
ステップ 7:切断
MQTT接続は、永続的に接続されたままのデバイスに適用されます。 IoT Platformからデバイスを手動で切断できます。
この例では、メインスレッドを使用してパラメータを設定し、接続を確立します。 接続が確立されたら、メインスレッドを一時停止できます。
aiot_mqtt_disconnect操作を呼び出して、デバイスをIoT Platformから切断します。
res = aiot_mqtt_disconnect(mqtt_handle);
if (res < STATE_SUCCESS) {
aiot_mqtt_deinit(&mqtt_handle);
printf("aiot_mqtt_disconnect failed: -0x % 04X\n", -res);
return -1;
}
ステップ 8:プログラムの終了
aiot_dm_deinit を呼び出して、データモデル クライアントインスタンスを破棄し、そのリソースを解放します。
res = aiot_dm_deinit(&dm_handle);
if (res < STATE_SUCCESS) {
printf("aiot_dm_deinit failed: -0x%04X\n", -res);
return -1;
}