This article describes how to call the API operations of Link SDK for C to download an over-the-air (OTA) update package that contains multiple files over HTTPS and perform an update on a device. In this example, the ./demo/fota_multi_file_demo.c sample code file is used.

Background information

Note Only Link SDK for C that is downloaded after September 09, 2021 (excluding this date) supports downloading an OTA update package that contains multiple files.

For more information about how to obtain the SDK, see Obtain the SDK.

Step 1: Initialize a client

  1. Add header files.
    ...
    ……
    #include "aiot_ota_api.h"
    ……
  2. Add the underlying dependency and configure the log output feature.
        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        aiot_state_set_logcb(demo_state_logcb);
  3. Call the aiot_ota_init operation to create an OTA client instance and initialize the default parameters.
        ota_handle = aiot_ota_init();
        if (NULL == ota_handle) {
            printf("aiot_ota_init failed\r\n");
            aiot_mqtt_deinit(&mqtt_handle);
            return -2;
        }

Step 2: Configure required features

Call the aiot_ota_setopt operation to configure the following items.

  1. Associate with an MQTT connection handle.
    Notice Before you set OTA update-specific parameters, make sure that device authentication information is specified. For more information, see Example.
    • Sample code
          aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);
    • Parameters:
      Parameter Example Description
      AIOT_OTAOPT_MQTT_HANDLE mqtt_handle You must establish an MQTT connection. This parameter is used to associate with the MQTT connection handle.
  2. Configure a callback to process OTA update commands.
    • Sample code
          aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, demo_ota_recv_handler);
    • Parameters:
      Parameter Example Description
      AIOT_OTAOPT_MQTT_HANDLER demo_ota_recv_handler This callback function is called when the device receives an OTA update command from IoT Platform.

Step 3: Submit the version number of the device

After the device establishes an MQTT connection with IoT Platform, call the aiot_ota_report_version operation to submit the version number of the device. IoT Platform determines whether an update is required based on the version number.

In this example, the submitted version number is 1.0.0. In actual business scenarios, you must obtain the version number from the device settings and specify the logic to submit the version number.

Notice

Before you perform an OTA update, you must submit the version number at least once.

    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);
    }

Step 4: Receive update commands

  1. After you add an update package to IoT Platform and initiate an update task, IoT Platform sends an update command to the device.
    For more information, see Add an update package.
  2. The device calls the aiot_mqtt_recv operation to receive the message. After the device recognizes the message as an OTA update command, the demo_ota_recv_handler callback is called.
  3. Specify the processing logic of the callback.
    When you specify the processing logic of the callback, take note of the following items:
    • IoT Platform uses the /ota/device/upgrade/${ProductKey}/${DeviceName} topic to send the OTA update command to the device.

      For more information about ${ProductKey} and ${DeviceName}, see Obtain device authentication information.

    • AIOT_OTARECV_FOTA indicates the message type.
      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 indicates the data format. Link SDK automatically parses the received message.
    • You can specify the processing logic of the callback based on the sample code. For more information, see Step 5: Download the update package and perform an OTA update.

Step 5: Download the update package and perform an OTA update

Notice After the device receives the update command pushed by IoT Platform, the device does not automatically download the update package. You must call the API operation of Link SDK to download the package.

After the demo_ota_recv_handler callback is called, the downloader initiates an HTTPS request to receive the OTA update package.

  1. Initialize the downloader.
    Call the aiot_download_init operation to create a download client instance and initialize the default parameters.
    Note

    The OTA update package can contain multiple files. For more information, see Add an update package.

    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. Set the parameters.
    Call the aiot_download_setopt operation to set the parameters that are related to the download task.
    Note

    If the OTA update package contains multiple files, take note of the ID, size, and progress of each file when you download the package.

                /* Set the TLS protocol for downloading. */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *)(&cred));
                /* Set the port number of the server to be accessed. */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *)(&port));
                /* Specify the information of the download task, which can be obtained from the task_desc member in the ota_msg input parameter. The information includes the download URL, firmware size, and firmware signature. */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *)(ota_msg->task_desc));
                /* Set the callback that the SDK calls when the downloaded content is received. */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *)(demo_download_recv_handler));
                /* Set the maximum buffer length for a single download. Users are notified if the limit is reached. */
                aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *)(&max_buffer_len));
                /* Specify the information that is shared among different calls of AIOT_DLOPT_RECV_HANDLER. In this example, the progress information is stored. */
                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);
    
                /* Submit the progress 0 for the first download task. */
                if (0 == ota_msg->task_desc->file_id) {
                    aiot_download_report_progress(dl_handle, 0);
                }
                            
  3. Initiate a download request.
    1. Start the demo_ota_download_thread 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 {
                      /* Set the type of the downloading thread to detach. After the firmware is obtained, the downloading thread automatically exits. */
                      pthread_detach(g_download_thread);
                  }
    2. After the demo_ota_download_thread thread is enabled, call the aiot_download_send_request operation to initiate an HTTPS GET request to download the update package from the storage server.
      • Sample code
        void *demo_ota_download_thread(void *dl_handle)
        {
            int32_t     ret = 0;
        
            printf("\r\nstarting download thread in 2 seconds ......\r\n");
            sleep(2);
        
            /* Initiate a request to download the update package from the storage server.
            /*
             * TODO: The following syntax uses one request to obtain all the firmware content.
             *       If the device has limited resources or the network connection is in poor condition, you can implement a segmented download.
             *
             *       aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_START, ...);
             *       aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_END, ...);
             *       aiot_download_send_request(dl_handle);
             *
             *      If you implement a segmented download, specify the preceding three statements in a loop. In this case, multiple requests are sent and multiple responses are received.
             *
             */
        
             ……
             ……
        
        }
      • Parameters:

        To implement a segmented download, set the AIOT_DLOPT_RANGE_START and AIOT_DLOPT_RANGE_END parameters.

        For example, if you want to download a 1,024-byte update package by using two segments, specify the following values for the parameters:
        • First segment: AIOT_DLOPT_RANGE_START=0, AIOT_DLOPT_RANGE_END=511
        • Second segment: AIOT_DLOPT_RANGE_START=512, AIOT_DLOPT_RANGE_END=1023
  4. Receive the update package.
    1. After the download request is sent, call the aiot_download_recv operation to receive the update package by using the demo_ota_download_thread download thread. After the device receives the package, the demo_download_recv_handler callback is called. You must save the downloaded update package to the local storage or file system of the device.
      void *demo_ota_download_thread(void *dl_handle)
      {
           ……
           ……
      
         aiot_download_send_request(dl_handle);
          // while (1) {
          while (should_stop == 0) {
              /* Receive the firmware that is sent from the server. */
              ret = aiot_download_recv(dl_handle);
      
              /* After the firmware is downloaded, the return value of aiot_download_recv() is STATE_DOWNLOAD_FINISHED. Otherwise, the value is the number of bytes that are obtained. */
              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. Define the demo_download_recv_handler callback to store the downloaded update package and perform an update.
      Note In this example, the response is printed. The logic to store and burn the update package is not specified. In actual business scenarios, you must store the update package and install the package to complete the OTA update.
      • If the OTA update package contains a single file, you can burn the file to a specified local storage location.
      • If the OTA update package contains multiple files, the files can be burned to different storage locations. You must identify the file_name field of each firmware file.
      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;
      
          /* You can set packet->type only to AIOT_DLRECV_HTTPBODY. */
          if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) {
              return;
          }
          percent = packet->data.percent;
      
          /* userdata can store the data that needs to be shared between different calls of demo_download_recv_handler(). */
          /* In this example, the percentage of the firmware download progress is stored. */
          if (userdata) {
              last_percent = (download_status->last_percent);
          }
      
          data_buffer_len = packet->data.len;
      
          /* A negative value of percent indicates that an exception occurs during data receiving or the digest authentication fails. */
          if (percent < 0) {
              printf("exception: percent = %d\r\n", percent);
              if (userdata) {
                  free(userdata);
              }
              return;
          }
           ……
           ……
      
      }
  5. Submit the download progress.

    After the demo_download_recv_handler callback is called, call the aiot_download_report_progress operation to submit the download progress and update errors to IoT Platform. The update errors include burning failure and network disconnection.

    • View the submitted progress:

      The progress is displayed in the IoT Platform console. For more information, see View update status.

    • Submit the progress in normal or abnormal condition:
      • If the download is successful, an integer that indicates the progress is submitted to IoT Platform. Link SDK automatically calculates the value of the percent parameter and submits the value to IoT Platform by using a callback.
      • If an error occurs during the download or the downloaded firmware fails to be burnt, the error is submitted to IoT Platform. For more information about error codes, see aiot_ota_protocol_errcode_t.
    • Submit the progress by using the following methods:
      • If the update package contains a single file, submit the progress of the file.
      • If the update package contains multiple files, do not submit the download progress of each file to prevent confusion. We recommend that you calculate a percentage by using the total file size as the denominator and the number of the downloaded bytes for all files as the numerator. Then, submit the download progress.
        You can also specify the logic to submit the progress based on your business needs. Examples:
        • Submit the progress 0% when the download starts. Then, submit the progress each time the progress increased by 10% until the download is completed.
        • Submit the progress 0% when the download starts and 100% when the download is completed.
    void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata)
    {
         ……
         ……
    
        /*
         * TODO: After a segment of firmware is downloaded, perform the following operations:
         *       After you download the update package, save the memory whose initial position is packet->data.buffer and length is packet->data.len to a local storage location.
         *
         *      If the burning fails, you must call the aiot_download_report_progress(handle, -4) operation to submit the error message to IoT Platform.
         *       The error codes that are defined in the protocol are included in the aiot_ota_protocol_errcode_t variable. Examples:
         *           -1: indicates that the update fails.
         *           -2: indicates that the download fails.
         *           -3: indicates that the verification fails.
         *           -4: indicates that the burning fails.
         *
         */
    
        /* If the value of the percent parameter is 100, all the firmware content is downloaded. */
        if (percent == 100) {
            g_finished_task_num++;
            /*
             * TODO: Burn the firmware, save the configurations, restart the device, and then switch to the new firmware to boot the device. 
                     Use the following code to submit the version number of the new firmware to IoT Platform. For example, if the version is updated from 1.0.0 to 1.1.0, the value of new_version is 1.1.0 must be submitted to IoT Platform. 
                     aiot_ota_report_version(ota_handle, new_version);
                     IoT Platform determines that the update is successful after it receives the version number of the new firmware. Otherwise, IoT Platform determines that the update fails. 
                     If the update fails after the update package is downloaded, call the aiot_download_report_progress(handle, -1) operation to submit the error type. 
             */
        }
    
        /* Simplify the output. Each time the download progress increases by at least 5%, the progress is printed and submitted to IoT Platform. */
        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) {
                    /* If multiple threads are used to download the update package, submit the progress 100% after all files are downloaded. */
                    aiot_download_report_progress(handle, 100);
                }
            }
    
            if (percent == 100 && userdata) {
                free(userdata);
            }
        }
    }
  6. Exit the downloader.

    After the update package is downloaded, call the aiot_download_deinit operation to destroy the download session. Then, the download thread is closed.

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

Step 6: Submit the version number after the update

  • If the OTA update package contains a single file, the package carries the firmware version number. You can submit the version number to IoT Platform.
  • If the update package contains multiple files, the files can correspond to one or multiple firmware version numbers. If the files correspond to different firmware version numbers, such as a1, b1, and c1, you must submit the combined version number of the update package, such as a1b1c1.

For more information about the sample code to submit the version number, see Step 3: Submit the version number of the device.

Note
  • After the device is updated, you must submit the latest version number. Otherwise, IoT Platform determines that the OTA update task fails.

  • The device may need to be restarted after the update. In this case, submit the latest version number after the device is restarted.

  • In this example, the logic to submit the latest version number after the update is not specified. You must specify the logic based on your business requirements.

Step 7: End the connection

Note

MQTT connections are applied to devices that remain persistently connected. You can manually disconnect devices from IoT Platform.

In this example, the main thread is used to set parameters and establish a connection. After the connection is established, you can put the main thread to sleep.

Call the aiot_mqtt_disconnect operation to disconnect the device from 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;
    }

Step 8: Exit the program

Call the aiot_ota_deinit operation to destroy the OTA client instance and release resources.

    aiot_ota_deinit(&ota_handle);

What to do next

  • After you configure the sample code file, compile the file to generate an executable file. In this example, the ./output/fota-multi-file_demo executable file is generated.

    For more information, see Compilation and running.

  • For more information about running results, see View logs.