./demos/fota_multi_file_demo.c
協助裝置端使用HTTPS協議,下載含多個升級檔案的OTA升級包,實現裝置的OTA升級。
背景資訊
擷取SDK的詳細資料,請參見擷取C Link SDK。
步驟一:初始化OTA功能
添加標頭檔。
…… …… #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; }
步驟二:配置OTA功能
調用aiot_ota_setopt,配置以下功能。
關聯MQTT串連的控制代碼。
重要在配置OTA參數前,請確保已配置裝置認證資訊等相關參數。具體操作,請參見MQTT配置串連參數。
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);
配置項
樣本
說明
AIOT_OTAOPT_MQTT_HANDLE
mqtt_handle
OTA功能的請求基於MQTT串連,通過該配置項,關聯MQTT串連控制代碼。
配置OTA升級指令訊息的回調。
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, demo_ota_recv_handler);
配置項
樣本
說明
AIOT_OTAOPT_MQTT_HANDLER
demo_ota_recv_handler
當裝置收到來自物聯網平台的OTA升級指令,調用該回呼函數。
步驟三:上報裝置目前的版本號
裝置建立MQTT串連後,調用aiot_ota_report_version,上報當前裝置的版本號碼。物聯網平台根據版本號碼,判斷是否需要升級。
以下範例程式碼中,OTA升級前裝置上報的版本號碼為1.0.0
,在實際業務中,您需從裝置的配置區擷取實際的版本號碼,並執行編寫代碼。
裝置進行OTA升級前,需至少上報一次版本號碼。
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);
}
步驟四:接收升級指令
在物聯網平台添加升級包,並發起升級任務後,物聯網平台向裝置端下發升級指令。
具體操作,請參見添加升級包。
裝置端調用aiot_mqtt_recv接收訊息,當訊息被識別為OTA升級指令後,調用回呼函數
demo_ota_recv_handler
。- 編寫回呼函數的處理邏輯。您可以參考以下內容,編寫回呼函數的處理邏輯:
物聯網平台通過Topic
/ota/device/upgrade/${ProductKey}/${DeviceName}
,向裝置下發OTA升級包指令。${ProductKey}和${DeviceName}的詳細說明,請參見擷取裝置認證資訊。
- OTA升級指令的類型為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; } …… …… }
OTA升級指令訊息的資料結構類型為aiot_ota_recv_t,Link SDK自動解析收到的升級指令訊息。
- 您可以參考範例程式碼,編寫回呼函數的處理邏輯,請參見步驟五:下載升級包並進行OTA升級。
步驟五:下載升級包並進行OTA升級
裝置端收到物聯網平台推送的升級包訊息後,不會自動下載升級包,您需要調用Link SDK提供的API啟動下載。
觸發函數demo_ota_recv_handler
後,基於HTTPS協議,下載器發起下載請求,然後接收升級包,實現OTA升級。
- 初始化下載器。調用aiot_download_init,建立
download
說明OTA升級支援含多個檔案的OTA升級包,更多資訊,請參見添加升級包。
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,配置下載任務的相關參數。說明
OTA升級支援下載含多個檔案的OTA升級包,因此在下載時需注意區分每個檔案的ID、數量和下載進度。
/* 設定下載時為TLS下載 */ aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *)(&cred)); /* 設定下載時訪問的伺服器連接埠號碼 */ aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *)(&port)); /* 設定下載的任務資訊, 通過輸入參數 ota_msg 中的 task_desc 成員得到, 內含下載地址, 韌體大小, 韌體簽名等 */ aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *)(ota_msg->task_desc)); /* 設定下載內容到達時, SDK將調用的回呼函數 */ aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *)(demo_download_recv_handler)); /* 設定單次下載最大的緩衝長度, 每當這個長度的記憶體讀滿了後會通知使用者 */ aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *)(&max_buffer_len)); /* 設定 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); /* 如果是第一個下載任務, 則上報進度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 { /* 下載線程被設定為 detach 類型, 韌體內容擷取完畢後可自主退出 */ 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); /* 向升級包儲存伺服器請求下載 */ /* * 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位元組的升級包分兩次下載,兩次的參數可分別設定為:
第一次:
AIOT_DLOPT_RANGE_START=0
,AIOT_DLOPT_RANGE_END=511
第二次:
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) { /* 從網路收取伺服器回應的韌體內容 */ ret = aiot_download_recv(dl_handle); /* 韌體全部下載完時, 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
,編寫升級包下載後的存放和升級邏輯。說明 範例程式碼僅做列印處理,未配置升級包的儲存和燒錄邏輯,您需根據環境自行儲存升級包,最後運行升級包,完成OTA升級。- 如果OTA升級使用單個檔案的OTA升級包,您可以將檔案燒寫到指定的本機存放區位置。
- 如果OTA升級使用多個檔案的OTA升級包,每個檔案可對應不同的儲存地址,您需識別每個韌體的
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; /* 目前只支援 packet->type 為 AIOT_DLRECV_HTTPBODY 的情況 */ if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) { return; } percent = packet->data.percent; /* userdata可以存放 demo_download_recv_handler() 的不同次進入之間, 需要共用的資料 */ /* 這裡用來存放上一次進入本回呼函數時, 下載的韌體進度百分比 */ if (userdata) { last_percent = (download_status->last_percent); } data_buffer_len = packet->data.len; /* 如果 percent 為負數, 說明發生了收包異常或者digest校正錯誤 */ if (percent < 0) { printf("exception: percent = %d\r\n", percent); if (userdata) { free(userdata); } return; } …… …… }
- 下載請求發送後,通過下載線程
- 上報下載進度。
通過定義回呼函數
demo_download_recv_handler
,調用aiot_download_report_progress,向物聯網平台上報下載進度,以及升級過程中發生的異常(例如:燒錄失敗、網路斷開等)。查看上報進度:
會直接顯示在物聯網平台控制台。更多資訊,請參見查看升級情況。
正常和異常下載時上報進度:
如果下載正常,則以整數的形式,向物聯網平台上報下載情況。Link SDK會自動計算出percent參數值,並在回調中,將其返回至物聯網平台。
如果下載異常,或下載之後韌體的燒錄異常,則將異常發送至物聯網平台。裝置和物聯網平台的協議錯誤碼,請參見aiot_ota_protocol_errcode_t 。
- 上報進度的方式:
- 如果OTA升級使用單個檔案的OTA升級包,上報的升級包進度即為總進度。
- 如果OTA升級使用多個檔案的OTA升級包,為了避免每個升級包上報各自的進度造成混亂,請勿上報單個檔案的下載進度。建議您上報進度時,以總檔案大小為分母,各檔案已下載總位元組數為分子,計算百分比,然後再上報下載進度。您可根據業務需要自行開發上報升級包進度的情境。例如:
- 開始下載時上報0%,然後僅當裝置下載的總進度達到10%的整數倍時再上報一次進度,直至100%。
- 為了簡化上報進度,您也可以在開始下載時上報0%,完成下載後上報100%,僅分兩次上報下載進度。
void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata) { …… …… /* * TODO: 下載一段韌體成功, 這個時候, 使用者應該將 * 起始地址為 packet->data.buffer, 長度為 packet->data.len 的記憶體, 儲存到本機存放區位置上 * * 如果燒寫失敗, 還應該調用 aiot_download_report_progress(handle, -4) 將失敗上報給物聯網平台 * 備忘:協議中, 與雲平台商定的錯誤碼在 aiot_ota_protocol_errcode_t 類型中, 例如 * -1: 表示升級失敗 * -2: 表示下載失敗 * -3: 表示校正失敗 * -4: 表示燒寫失敗 * */ /* percent 入參的值為 100 時, 說明SDK已經下載韌體內容全部完成 */ if (percent == 100) { g_finished_task_num++; /* * TODO: 此時, 應該完成所有的韌體燒錄, 儲存當前工作, 重啟裝置, 切換到新的韌體上啟動。 並且, 新的韌體必須通過以下代碼,將升級後的新版本號碼(例如從1.0.0升到1.1.0, 則new_version的值為1.1.0)上報至物聯網平台。 aiot_ota_report_version(ota_handle, new_version); 物聯網平台收到新的版本號碼後, 才判定升級成功, 否則認定升級失敗。 如果下載成功後升級失敗, 還應該調用aiot_download_report_progress(handle, -1)將失敗類型上報。 */ } /* 簡化輸出, 只有距離上次的下載進度增加5%以上時, 才會列印進度, 並向伺服器上報進度 */ 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) { /* 考慮到多個線程並發下載, 僅僅在所有檔案下載完成後才上報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");
步驟六:上報升級後版本號碼
- 如果OTA升級使用單個檔案的OTA升級包,該升級包僅含有一個韌體,升級包攜帶了該韌體版本號碼,您可以將該版本號碼上報到物聯網平台,從而完成版本號碼的匹配。
- 如果OTA升級使用多個檔案的OTA升級包,多個檔案可對應一個韌體,也可對應多個韌體。如果每個檔案對應不同韌體的版本號碼,例如
a1
、b1
和c1
,那麼您上報的版本號碼必須為該升級包整體的版本號碼,例如a1b1c1
。
上報版本號碼的範例程式碼,請參見步驟三:上報裝置目前的版本號。
裝置完成OTA升級後,需上報最新版本號碼,否則物聯網平台視該OTA升級任務為失敗。
如果裝置升級後需重啟,則裝置重啟後需上報最新的版本號碼。
範例程式碼中不包含升級完成後重新上報版本號碼的過程,您需自行實現。
步驟七:中斷連線
MQTT接入常應用於長串連的裝置,程式通常不會運行至此。
常式的主線程任務為配置參數並成功建立串連。串連建立後,主線程可進入休眠。
調用aiot_mqtt_disconnect,向物聯網平台發送中斷連線的報文,然後斷開網路連接。
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;
}
步驟八:退出OTA程式
調用aiot_ota_deinit,銷毀OTA
aiot_ota_deinit(&ota_handle);