./demos/fota_multi_file_demo.c のサンプルでは、デバイスが HTTPS 経由で複数ファイルのファームウェアアップデートパッケージをダウンロードし、ファームウェアアップデートを実行する方法を示します。
背景情報
- ファームウェアアップデート機能の詳細については、「ファームウェアアップデートの概要」をご参照ください。
- ファームウェアアップデート機能は MQTT 接続に基づいています。MQTT 接続のコードの詳細については、「MQTT 接続」をご参照ください。
- このサンプルは、「サンプル 1
./demo/fota_posix_demo.c) と比較して、以下のセクションのみが異なります。
SDK の取得方法の詳細については、「C Link SDK の取得」をご参照ください。
ステップ 1: ファームウェアアップデート機能の初期化
- ヘッダーファイルを追加します。
…… …… #include "aiot_ota_api.h" …… 基盤となる依存関係とログ出力を設定します。
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile); aiot_state_set_logcb(demo_state_logcb);- aiot_ota_init を呼び出して OTA インスタンスを作成します。
ota_handle = aiot_ota_init(); if (NULL == ota_handle) { printf("aiot_ota_init failed\r\n"); aiot_mqtt_deinit(&mqtt_handle); return -2; }
ステップ 2: ファームウェアアップデート機能の設定
aiot_ota_setopt を呼び出して、以下のオプションを設定します。
- MQTT 接続のハンドルを関連付けます。重要 OTA パラメーターを設定する前に、デバイス認証情報などのパラメーターを設定する必要があります。詳細については、「MQTT の接続パラメーターの設定」をご参照ください。
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);設定項目 例 説明 AIOT_OTAOPT_MQTT_HANDLE mqtt_handle ファームウェアアップデート機能のリクエストは MQTT 接続に基づいています。この設定項目は MQTT 接続ハンドルを関連付けます。
- ファームウェアアップデート命令メッセージのコールバックを設定します。
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, demo_ota_recv_handler);設定項目 例 説明 AIOT_OTAOPT_MQTT_HANDLER demo_ota_recv_handler デバイスが IoT Platform からファームウェアアップデート命令を受信すると、このコールバック関数が呼び出されます。
ステップ 3: 現在のデバイスバージョンの報告
デバイスが MQTT 接続を確立した後、aiot_ota_report_version を呼び出してデバイスの現在のバージョン番号を報告します。IoT Platform は、そのバージョン番号に基づいてアップデートが必要かどうかを判断します。
以下のサンプルコードでは、ファームウェアアップデート前にデバイスが報告するバージョン番号は 1.0.0 です。ご利用のアプリケーションでは、デバイスの設定エリアから実際のバージョン番号を取得し、それに応じてコードを修正する必要があります。
デバイスは、ファームウェアアップデートの前に少なくとも一度はバージョン番号を報告する必要があります。
cur_version = "1.0.0";
res = aiot_ota_report_version(ota_handle, cur_version);
if (res < STATE_SUCCESS) {
printf("aiot_ota_report_version failed: -0x%04X\r\n", -res);
}ステップ 4: アップデート命令の受信
- IoT Platform コンソールでアップデートパッケージを追加し、アップデートタスクを開始すると、IoT Platform はデバイスにアップデート命令を送信します。詳細については、「アップデートパッケージの追加」をご参照ください。
- デバイスは aiot_mqtt_recv を呼び出してメッセージを受信します。メッセージがファームウェアアップデート命令として識別されると、
demo_ota_recv_handlerコールバック関数が呼び出されます。 - コールバック関数の処理ロジックを記述します。
以下の情報を使用して、コールバック関数の処理ロジックを記述できます。- IoT Platform は、Topic
/ota/device/upgrade/${ProductKey}/${DeviceName}を介して、デバイスにファームウェアアップデートパッケージの命令を送信します。${ProductKey} と ${DeviceName} の詳細については、「デバイス認証情報の取得」をご参照ください。
- ファームウェアアップデート命令のタイプは AIOT_OTARECV_FOTA です。
void demo_ota_recv_handler(void *ota_handle, aiot_ota_recv_t *ota_msg, void *userdata) { switch (ota_msg->type) { case AIOT_OTARECV_FOTA: { uint32_t res = 0; uint16_t port = 443; uint32_t max_buffer_len = (8 * 1024); aiot_sysdep_network_cred_t cred; void *dl_handle = NULL; multi_download_status_t *download_status = NULL; if (NULL == ota_msg->task_desc) { break; } …… …… } - ファームウェアアップデート命令メッセージのデータ構造タイプは aiot_ota_recv_t です。Link SDK は受信したアップデート命令メッセージを自動的に解析します。
- サンプルコードを参照して、コールバック関数の処理ロジックを記述できます。詳細については、ステップ 5: アップデートパッケージをダウンロードしてファームウェアアップデートを実行」をご参照ください。
- IoT Platform は、Topic
ステップ 5: アップデートパッケージをダウンロードしてファームウェアアップデートを実行
demo_ota_recv_handler 関数がトリガーされると、ダウンローダーは HTTPS 経由でダウンロードリクエストを開始し、アップデートパッケージをダウンロードしてファームウェアアップデートを実行します。
- ダウンローダーを初期化します。
aiot_download_init を呼び出してdownload説明ファームウェアアップデートは、複数ファイルのアップデートパッケージをサポートしています。詳細については、「アップデートパッケージの追加」をご参照ください。
dl_handle = aiot_download_init(); if (NULL == dl_handle) { break; } if (NULL != ota_msg->task_desc->file_name) { printf("\r\nTotal file number is %d, current file id is %d, with file_name %s\r\n", ota_msg->task_desc->file_num, ota_msg->task_desc->file_id, ota_msg->task_desc->file_name); } printf("OTA target firmware version: %s, size: %u Bytes \r\n", ota_msg->task_desc->version, ota_msg->task_desc->size_total); if (NULL != ota_msg->task_desc->extra_data) { printf("extra data: %s\r\n", ota_msg->task_desc->extra_data); } memset(&cred, 0, sizeof(aiot_sysdep_network_cred_t)); cred.option = AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA; cred.max_tls_fragment = 16384; cred.x509_server_cert = ali_ca_cert; cred.x509_server_cert_len = strlen(ali_ca_cert);
- ダウンロードパラメーターを設定します。
aiot_download_setopt を呼び出して、ダウンロードタスクのパラメーターを設定します。説明ファームウェアアップデートは、複数ファイルのアップデートパッケージをサポートしています。パッケージをダウンロードする際は、各ファイルの ID、数量、ダウンロード進捗を区別する必要があります。
/* Set the download protocol to TLS. */ /* ダウンロードプロトコルを TLS に設定します。*/ aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *)(&cred)); /* Set the server port for the download. */ /* ダウンロード用のサーバーポートを設定します。*/ aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *)(&port)); /* Set the download task information. This is obtained from the task_desc member of the ota_msg input parameter. It includes the download URL, firmware size, firmware signature, and more. */ /* ダウンロードタスク情報を設定します。これは ota_msg 入力パラメーターの task_desc メンバーから取得します。ダウンロード URL、ファームウェアサイズ、ファームウェア署名などが含まれます。*/ aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *)(ota_msg->task_desc)); /* Set the callback function that the SDK calls when the downloaded content arrives. */ /* ダウンロードされたコンテンツが到着したときに SDK が呼び出すコールバック関数を設定します。*/ aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *)(demo_download_recv_handler)); /* Set the maximum cache length for a single download. The user is notified each time this memory buffer is full. */ /* 1 回のダウンロードの最大キャッシュ長を設定します。このメモリバッファーがいっぱいになるたびにユーザーに通知されます。*/ aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *)(&max_buffer_len)); /* Set the data to be shared between different calls of AIOT_DLOPT_RECV_HANDLER. For example, the sample stores the progress here. */ /* AIOT_DLOPT_RECV_HANDLER の異なる呼び出し間で共有されるデータを設定します。たとえば、サンプルではここに進行状況を保存します。*/ last_percent = malloc(sizeof(uint32_t)); if (NULL == last_percent) { aiot_download_deinit(&dl_handle); break; } memset(download_status, 0, sizeof(multi_download_status_t)); download_status->file_id = ota_msg->task_desc->file_id; download_status->file_num = ota_msg->task_desc->file_num; aiot_download_setopt(dl_handle, AIOT_DLOPT_USERDATA, (void *)download_status); /* If this is the first download task, report a progress of 0. */ /* これが最初のダウンロードタスクである場合は、進捗 0 を報告します。*/ if (0 == ota_msg->task_desc->file_id) { aiot_download_report_progress(dl_handle, 0); }
- ダウンロードリクエストを開始します。
- ダウンロードスレッド
demo_ota_download_threadを開始します。
res = pthread_create(&g_download_thread, NULL, demo_ota_download_thread, dl_handle); if (res != 0) { printf("pthread_create demo_ota_download_thread failed: %d\r\n", res); aiot_download_deinit(&dl_handle); free(download_status); } else { /* The download thread is set to the detach type. It can exit on its own after the firmware content is obtained. */ /* ダウンロードスレッドはデタッチタイプに設定されています。ファームウェアコンテンツが取得された後、自動的に終了できます。*/ pthread_detach(g_download_thread); }
demo_ota_download_threadダウンロードスレッドで aiot_download_send_request を呼び出し、指定されたアップデートパッケージストレージサーバーに HTTPS GET リクエストを送信します。
void *demo_ota_download_thread(void *dl_handle) { int32_t ret = 0; printf("\r\nstarting download thread in 2 seconds ......\r\n"); sleep(2); /* Request to download from the update package storage server. */ /* アップデートパッケージストレージサーバーからのダウンロードをリクエストします。*/ /* * TODO: The following code uses one request to get the entire firmware content. * For devices with limited resources or poor network conditions, you can also download in segments. To do this, combine the following statements: * * aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_START, ...); * aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_END, ...); * aiot_download_send_request(dl_handle); * * In this case, you need to place the combined statements in a loop and call send_request and recv multiple times. * */ /* * TODO: 以下のコードは、1 つのリクエストを使用してファームウェアコンテンツ全体を取得します。 * リソースが限られているデバイスやネットワーク状態が悪い場合は、分割してダウンロードすることもできます。そのためには、以下の文を組み合わせます: * * aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_START, ...); * aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_END, ...); * aiot_download_send_request(dl_handle); * * この場合、組み合わせた文をループ内に配置し、send_request と recv を複数回呼び出す必要があります。 * */ …… …… }AIOT_DLOPT_RANGE_START と AIOT_DLOPT_RANGE_END を使用して、アップデートパッケージを分割してダウンロードできます。
たとえば、1024 バイトのアップデートパッケージを 2 つの部分に分けてダウンロードする場合、パラメーターを次のように設定できます。- 最初の部分:
AIOT_DLOPT_RANGE_START=0、AIOT_DLOPT_RANGE_END=511 - 2 番目の部分:
AIOT_DLOPT_RANGE_START=512、AIOT_DLOPT_RANGE_END=1023
- 最初の部分:
- ダウンロードスレッド
- アップデートパッケージを受信します。
- ダウンロードリクエストが送信された後、
demo_ota_download_threadダウンロードスレッドで aiot_download_recv を呼び出してアップデートパッケージデータを受信します。受信したデータはdemo_download_recv_handlerコールバック関数をトリガーします。コールバック関数では、ダウンロードしたアップデートパッケージをデバイスのローカル記憶域またはファイルシステムに保存する必要があります。
void *demo_ota_download_thread(void *dl_handle) { …… …… aiot_download_send_request(dl_handle); // while (1) { while (should_stop == 0) { /* Receive the firmware content from the server over the network. */ /* ネットワーク経由でサーバーからファームウェアコンテンツを受信します。*/ ret = aiot_download_recv(dl_handle); /* When the entire firmware is downloaded, the return value of aiot_download_recv() is STATE_DOWNLOAD_FINISHED. Otherwise, it is the number of bytes obtained in the current call. */ /* ファームウェア全体がダウンロードされると、aiot_download_recv() の戻り値は STATE_DOWNLOAD_FINISHED になります。それ以外の場合は、現在の呼び出しで取得されたバイト数です。*/ if (STATE_DOWNLOAD_FINISHED == ret) { printf("download completed\r\n"); break; } if (STATE_DOWNLOAD_RENEWAL_REQUEST_SENT == ret) { printf("download renewal request has been sent successfully\r\n"); continue; } if (ret <= STATE_SUCCESS) { printf("download failed, error code is %d, try to send renewal request\r\n", ret); continue; } } …… …… }
demo_download_recv_handlerコールバック関数を定義して、ダウンロード完了後のパッケージの保存と更新のロジックを含めます。
説明 サンプルコードは情報を出力するだけで、アップデートパッケージの保存と書き込みのロジックは含まれていません。ご利用の環境に基づいてアップデートパッケージを保存するコードを記述し、その後ファームウェアを書き込んでファームウェアアップデートを完了する必要があります。- ファームウェアアップデートで単一ファイルのパッケージを使用する場合、ファイルを指定されたローカル記憶域の場所に書き込むことができます。
- ファームウェアアップデートで複数ファイルのパッケージを使用する場合、各ファイルは異なるストレージの場所に対応できます。各ファームウェアの
file_nameフィールドを識別する必要があります。
void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata) { uint32_t data_buffer_len = 0; int32_t last_percent = 0; int32_t percent = 0; multi_download_status_t *download_status = (multi_download_status_t *)userdata; /* Currently, only the case where packet->type is AIOT_DLRECV_HTTPBODY is supported. */ /* 現在、packet->type が AIOT_DLRECV_HTTPBODY の場合のみサポートされています。*/ if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) { return; } percent = packet->data.percent; /* userdata can store data that needs to be shared between different entries into demo_download_recv_handler(). */ /* userdata は、demo_download_recv_handler() への異なるエントリ間で共有する必要があるデータを保存できます。*/ /* Here, it is used to store the firmware download percentage from the last time this callback function was entered. */ /* ここでは、このコールバック関数が最後に入力されたときのファームウェアダウンロードのパーセンテージを保存するために使用されます。*/ if (userdata) { last_percent = (download_status->last_percent); } data_buffer_len = packet->data.len; /* If percent is negative, a packet reception exception or a digest verification error occurred. */ /* percent が負の場合、パケット受信例外またはダイジェスト検証エラーが発生しました。*/ if (percent < 0) { printf("exception: percent = %d\r\n", percent); if (userdata) { free(userdata); } return; } …… …… }
- ダウンロードリクエストが送信された後、
- ダウンロード進捗を報告します。
demo_download_recv_handlerコールバック関数で aiot_download_report_progress を呼び出して、ダウンロードの進捗状況と、書き込み失敗やネットワーク切断など、アップデートプロセス中に発生した例外を IoT Platform に報告します。- 報告された進捗の表示:
進捗は IoT Platform コンソールに表示されます。詳細については、「アップデートステータスの表示」をご参照ください。
- 正常および異常なダウンロードの進捗報告:
- ダウンロードが正常な場合、ダウンロードの進捗は整数のパーセンテージとして IoT Platform に報告されます。Link SDK は percent パラメーターの値を自動的に計算し、IoT Platform に報告します。
- ダウンロードが異常な場合、またはダウンロード後にファームウェアの書き込み例外が発生した場合は、その例外を IoT Platform に報告する必要があります。デバイスと IoT Platform 間のプロトコルエラーコードのリストについては、「aiot_ota_protocol_errcode_t」をご参照ください。
- 進捗報告の方法:
- ファームウェアアップデートで単一ファイルのパッケージを使用する場合、報告される進捗は全体の進捗です。
- ファームウェアアップデートで複数ファイルのパッケージを使用する場合、個々のファイルのダウンロード進捗を報告すると混乱を招く可能性があるため、報告しないでください。進捗を報告する際は、合計ファイルサイズを分母とし、すべてのファイルでダウンロードされた合計バイト数を分子としてパーセンテージを計算することを推奨します。その後、計算されたダウンロード進捗を報告できます。ビジネスニーズに基づいて、アップデートパッケージの進捗を報告するシナリオを開発できます。例:
- ダウンロード開始時に 0% を報告します。その後、全体のダウンロード進捗が 10% の整数倍に達した場合にのみ、最大 100% まで進捗を報告します。
- 進捗報告を簡素化するために、ダウンロード開始時に 0% を報告し、完了後に 100% を報告することもできます。これは、進捗を 2 回だけ報告することを意味します。
void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata) { …… …… /* * TODO: When a segment of firmware is successfully downloaded, the user should save the memory * starting at packet->data.buffer with a length of packet->data.len to a local storage location. * * If the burning fails, you should also call aiot_download_report_progress(handle, -4) to report the failure to IoT Platform. * Note: The error codes agreed upon with the cloud platform in the protocol are in the aiot_ota_protocol_errcode_t type. For example: * -1: indicates an update failure. * -2: indicates a download failure. * -3: indicates a verification failure. * -4: indicates a burning failure. * */ /* * TODO: ファームウェアのセグメントが正常にダウンロードされたら、ユーザーは packet->data.buffer から始まる * packet->data.len の長さのメモリをローカルのストレージの場所に保存する必要があります。 * * 書き込みに失敗した場合は、aiot_download_report_progress(handle, -4) を呼び出して、その失敗を IoT Platform に報告する必要もあります。 * 注意: プロトコルでクラウドプラットフォームと合意されたエラーコードは、aiot_ota_protocol_errcode_t 型です。例: * -1: アップデートの失敗を示します。 * -2: ダウンロードの失敗を示します。 * -3: 検証の失敗を示します。 * -4: 書き込みの失敗を示します。 * */ /* When the value of the percent input parameter is 100, it means the SDK has finished downloading the entire firmware content. */ /* percent 入力パラメーターの値が 100 の場合、SDK がファームウェアコンテンツ全体のダウンロードを完了したことを意味します。*/ if (percent == 100) { g_finished_task_num++; /* * TODO: At this point, all firmware burning should be complete. Save the current work, restart the device, and start with the new firmware. The new firmware must report the new version number to IoT Platform using the following code. For example, if updating from 1.0.0 to 1.1.0, the value of new_version is 1.1.0. aiot_ota_report_version(ota_handle, new_version); IoT Platform considers the update successful only after receiving the new version number. Otherwise, the update is considered failed. If the update fails after a successful download, you should also call aiot_download_report_progress(handle, -1) to report the failure type. */ /* * TODO: この時点で、すべてのファームウェアの書き込みが完了しているはずです。現在の作業を保存し、デバイスを再起動して、新しいファームウェアで開始します。 新しいファームウェアは、以下のコードを使用して新しいバージョン番号を IoT Platform に報告する必要があります。たとえば、1.0.0 から 1.1.0 にアップデートする場合、new_version の値は 1.1.0 です。 aiot_ota_report_version(ota_handle, new_version); IoT Platform は、新しいバージョン番号を受信した後にのみアップデートが成功したと見なします。それ以外の場合、アップデートは失敗したと見なされます。 ダウンロードが成功した後にアップデートが失敗した場合は、aiot_download_report_progress(handle, -1) を呼び出して失敗の種類を報告する必要もあります。 */ } /* Simplified output. The progress is printed and reported to the server only when the download progress has increased by 5% or more since the last report. */ /* 簡略化された出力。ダウンロードの進捗が最後の報告から 5% 以上増加した場合、または 100% の場合にのみ、進捗が出力され、サーバーに報告されます。*/ if (percent - last_percent >= 5 || percent == 100) { if (NULL != download_status) { printf("file_id %d, download %03d%% done, +%d bytes\r\n", download_status->file_id, percent, data_buffer_len); download_status->last_percent = percent; if (g_finished_task_num == download_status->file_num) { /* Considering concurrent downloads by multiple threads, report 100% progress only after all files are downloaded. */ /* 複数のスレッドによる同時ダウンロードを考慮して、すべてのファイルがダウンロードされた後にのみ 100% の進捗を報告します。*/ aiot_download_report_progress(handle, 100); } } if (percent == 100 && userdata) { free(userdata); } } } - 報告された進捗の表示:
- ダウンローダーを終了します。
アップデートパッケージのコンテンツがダウンロードされた後、aiot_download_deinit を呼び出して
downloadセッションを破棄します。その後、ダウンロードスレッドは終了します。aiot_download_deinit(&dl_handle); printf("download thread exit\n");
ステップ 6: アップデート後のバージョン番号の報告
- ファームウェアアップデートで単一ファイルのパッケージを使用し、そのパッケージにファームウェアが 1 つしか含まれていない場合は、パッケージに含まれるファームウェアのバージョン番号を IoT Platform に報告して、バージョン照合を完了します。
- ファームウェアアップデートで複数ファイルのパッケージを使用する場合、ファイルは 1 つ以上のファームウェアに対応できます。各ファイルに
a1、b1、c1のような異なるファームウェアバージョン番号がある場合は、a1b1c1のような結合されたパッケージバージョン番号を報告します。
バージョン番号の報告に関するサンプルコードについては、「ステップ 3: 現在のデバイスバージョン番号の報告」をご参照ください。
デバイスがファームウェアアップデートを完了した後、最新のバージョン番号を報告する必要があります。そうしないと、IoT Platform はファームウェアアップデートタスクが失敗したと見なします。
アップデート後にデバイスを再起動する必要がある場合は、再起動後に最新のバージョン番号を報告する必要があります。
- サンプルコードには、アップデート完了後にバージョン番号を報告するロジックは含まれていません。このロジックをコードに追加する必要があります。
ステップ 7: 切断
MQTT は通常、持続的接続を必要とするデバイスに使用されます。そのため、プログラムがこのポイントに到達することは通常ありません。
サンプルプログラムでは、メインスレッドがパラメーターの設定と接続の確立を担当します。接続が確立された後、メインスレッドは休止状態に入ることができます。
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: OTA プログラムの終了
aiot_ota_deinit を呼び出して OTA インスタンスを破棄します。
aiot_ota_deinit(&ota_handle);