全部產品
Search
文件中心

:範例程式碼說明

更新時間:Jun 30, 2024

./demo/fota_posix_demo.c協助裝置端使用HTTPS協議,下載僅含單個升級檔案的OTA升級包,實現裝置的OTA升級。

背景資訊

  • OTA升級功能的更多資訊,請參見OTA升級概述
  • OTA升級功能基於MQTT接入,開發過程中涉及MQTT接入的代碼說明,請參見MQTT接入

步驟一:初始化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_HANDLEmqtt_handleOTA功能的請求基於MQTT串連,通過該配置項,關聯MQTT串連控制代碼。

  2. 配置OTA升級指令訊息的回調。
    •     aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, demo_ota_recv_handler);
    • 配置項樣本說明
      AIOT_OTAOPT_MQTT_HANDLERdemo_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;
                  void *last_percent = NULL;
      
                  if (NULL == ota_msg->task_desc) {
                      break;
                  }
           ……
           ……
      
      }
    • OTA升級指令訊息的Alink資料格式說明,請參見物聯網平台推送OTA升級包資訊
    • OTA升級指令訊息的資料結構類型為aiot_ota_recv_t,Link SDK自動解析收到的升級指令訊息。
    • 您可以參考範例程式碼,編寫回呼函數的處理邏輯,請參見步驟五:下載升級包,進行OTA升級

步驟五:下載升級包,進行OTA升級

重要 裝置端收到物聯網平台推送的升級包訊息後,不會自動下載升級包,您需要調用Link SDK提供的API啟動下載。

觸發函數demo_ota_recv_handler後,基於HTTPS協議,下載器發起下載請求,然後接收升級包,實現OTA升級。

  1. 初始化下載器。
    調用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);
  2. 配置下載參數。
    調用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);
  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(last_percent);
                  } 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("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_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)
      {
           ……
           ……
      
           while (1) {
              /* 從網路收取伺服器回應的升級包內容 */
              ret = aiot_download_recv(dl_handle);
      
              /* 升級包全部下載完時, aiot_download_recv() 的傳回值會等於 STATE_DOWNLOAD_FINISHED, 否則是當次擷取的位元組數。 */
              if (STATE_DOWNLOAD_FINISHED == ret) {
                  printf("download completed\n");
                  break;
              }
          }
           ……
           ……
      }
    2. 定義回呼函數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;
          }
           ……
           ……
      
      }
  5. 上報下載進度

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

    升級包內容下載完成後,調用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);

後續步驟