./demo/fota_posix_demo.c
協助裝置端使用HTTPS協議,下載僅含單個升級檔案的OTA升級包,實現裝置的OTA升級。
背景資訊
步驟一:初始化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; void *last_percent = NULL; if (NULL == ota_msg->task_desc) { break; } …… …… }
- OTA升級指令訊息的Alink資料格式說明,請參見物聯網平台推送OTA升級包資訊。
- OTA升級指令訊息的資料結構類型為aiot_ota_recv_t,Link SDK自動解析收到的升級指令訊息。
- 您可以參考範例程式碼,編寫回呼函數的處理邏輯,請參見步驟五:下載升級包,進行OTA升級。
- 物聯網平台通過Topic
步驟五:下載升級包,進行OTA升級
觸發函數demo_ota_recv_handler
後,基於HTTPS協議,下載器發起下載請求,然後接收升級包,實現OTA升級。
- 初始化下載器。調用aiot_download_init,建立
download
dl_handle = aiot_download_init(); if (NULL == dl_handle) { break; } 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,配置下載任務的相關參數。
/* 設定下載時為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(last_percent, 0, sizeof(uint32_t)); aiot_download_setopt(dl_handle, AIOT_DLOPT_USERDATA, (void *)last_percent);
- 發起下載請求。
- 啟動下載線程
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(last_percent); } 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("starting download thread in 2 seconds ......\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) { …… …… while (1) { /* 從網路收取伺服器回應的升級包內容 */ ret = aiot_download_recv(dl_handle); /* 升級包全部下載完時, aiot_download_recv() 的傳回值會等於 STATE_DOWNLOAD_FINISHED, 否則是當次擷取的位元組數。 */ if (STATE_DOWNLOAD_FINISHED == ret) { printf("download completed\n"); break; } } …… …… }
- 定義回呼函數
demo_download_recv_handler
,編寫升級包下載後的存放和升級邏輯。說明 範例程式碼僅做列印處理,未配置升級包的儲存和燒錄邏輯,您需根據環境自行儲存升級包,最後運行升級包,完成OTA升級。void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata) { uint32_t data_buffer_len = 0; uint32_t last_percent = 0; int32_t percent = 0; /* 目前只支援 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 = *((uint32_t *)(userdata)); } 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 。
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),將失敗資訊上報至物聯網平台。 */ /* 當percent入參的值為100時, 說明升級包已全部下載成功。 */ if (percent == 100) { /* * TODO: 此時, 應該完成所有的韌體燒錄, 儲存當前工作, 重啟裝置, 切換到新的韌體上啟動。 並且, 新的韌體必須要以 aiot_ota_report_version(ota_handle, new_version); 將升級後的新版本號碼(比如從1.0.0升到1.1.0, 則new_version的值為1.1.0)上報給物聯網平台。 物聯網平台收到了新的版本號碼上報後, 才會判定升級成功, 否則會認定升級失敗。 */ } /* 簡化輸出, 只有距離上次的下載進度增加5%以上時, 才會列印進度, 並把進度上報給雲端 */ if (percent - last_percent >= 5 || percent == 100) { printf("download %03d%% done, +%d bytes\n", percent, data_buffer_len); aiot_download_report_progress(handle, percent); if (userdata) { *((uint32_t *)(userdata)) = percent; } } }
- 查看上報進度:
- 退出下載器。
升級包內容下載完成後,調用aiot_download_deinit,銷毀
download
會話,下載線程自行退出。aiot_download_deinit(&dl_handle); printf("download thread exit\n");
步驟六:上報升級後版本號碼
具體操作,請參見步驟三:上報裝置目前的版本號。
裝置完成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);