全部產品
Search
文件中心

:範例程式碼說明

更新時間:Jun 30, 2024

./demos/fota_multi_file_demo.c協助裝置端使用HTTPS協議,下載含多個升級檔案的OTA升級包,實現裝置的OTA升級。

背景資訊

說明 僅2021年09月09日(不含當日)以後下載的C Link SDK,支援裝置下載含多個檔案的OTA升級包功能。

擷取SDK的詳細資料,請參見擷取C Link SDK

步驟一:初始化OTA功能

  1. 添加標頭檔。

    ……
    ……
    #include "aiot_ota_api.h"
    ……
  2. 配置底層依賴和日誌輸出。

        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        aiot_state_set_logcb(demo_state_logcb);
  3. 調用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,配置以下功能。

  1. 關聯MQTT串連的控制代碼。

    重要

    在配置OTA參數前,請確保已配置裝置認證資訊等相關參數。具體操作,請參見MQTT配置串連參數

    •     aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);
    • 配置項

      樣本

      說明

      AIOT_OTAOPT_MQTT_HANDLE

      mqtt_handle

      OTA功能的請求基於MQTT串連,通過該配置項,關聯MQTT串連控制代碼。

  2. 配置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);
    }

步驟四:接收升級指令

  1. 在物聯網平台添加升級包,並發起升級任務後,物聯網平台向裝置端下發升級指令。

    具體操作,請參見添加升級包

  2. 裝置端調用aiot_mqtt_recv接收訊息,當訊息被識別為OTA升級指令後,調用回呼函數demo_ota_recv_handler

  3. 編寫回呼函數的處理邏輯。
    您可以參考以下內容,編寫回呼函數的處理邏輯:
    • 物聯網平台通過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升級。

  1. 初始化下載器。
    調用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);

  2. 配置下載參數。
    調用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);
                }
                            

  3. 發起下載請求
    1. 啟動下載線程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);
                  }

    2. 通過下載線程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_STARTAIOT_DLOPT_RANGE_END,可以分段下載升級包。

        例如,一個1024位元組的升級包分兩次下載,兩次的參數可分別設定為:

        • 第一次:AIOT_DLOPT_RANGE_START=0AIOT_DLOPT_RANGE_END=511

        • 第二次:AIOT_DLOPT_RANGE_START=512AIOT_DLOPT_RANGE_END=1023


  4. 接收升級包
    1. 下載請求發送後,通過下載線程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;
              }
          }
           ……
           ……
      }

    2. 定義回呼函數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;
          }
           ……
           ……
      
      }

  5. 上報下載進度

    通過定義回呼函數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);
            }
        }
    }
  6. 退出下載器。

    升級包內容下載完成後,調用aiot_download_deinit,銷毀download會話,下載線程自行退出。

        aiot_download_deinit(&dl_handle);
        printf("download thread exit\n");

步驟六:上報升級後版本號碼

  • 如果OTA升級使用單個檔案的OTA升級包,該升級包僅含有一個韌體,升級包攜帶了該韌體版本號碼,您可以將該版本號碼上報到物聯網平台,從而完成版本號碼的匹配。
  • 如果OTA升級使用多個檔案的OTA升級包,多個檔案可對應一個韌體,也可對應多個韌體。如果每個檔案對應不同韌體的版本號碼,例如a1b1c1,那麼您上報的版本號碼必須為該升級包整體的版本號碼,例如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);

後續步驟