The ./demos/fota_multi_file_demo.c sample shows how a device downloads a multi-file OTA update package over HTTPS and performs an OTA update.
Background information
- For more information about the OTA update feature, see OTA update overview.
- The OTA update feature is based on an MQTT connection. For more information about the code for an MQTT connection, see MQTT connection.
- Compared with Sample 1
./demo/fota_posix_demo.c), this sample differs only in the following sections:
For more information about how to obtain the SDK, see Obtain C Link SDK.
Step 1: Initialize the OTA feature
- Add the header file.
…… …… #include "aiot_ota_api.h" …… Configure underlying dependencies and log output.
aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile); aiot_state_set_logcb(demo_state_logcb);- Call aiot_ota_init to create an OTA instance.
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 the OTA feature
Call aiot_ota_setopt to configure the following options.
- Associate the handle of the MQTT connection.Important Before you configure OTA parameters, you must configure parameters such as device credentials. For more information, see Configure connection parameters for MQTT.
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_MQTT_HANDLE, mqtt_handle);Configuration item Example Description AIOT_OTAOPT_MQTT_HANDLE mqtt_handle OTA feature requests are based on MQTT connections. This configuration item associates the MQTT connection handle.
- Configure the callback for OTA update instruction messages.
aiot_ota_setopt(ota_handle, AIOT_OTAOPT_RECV_HANDLER, demo_ota_recv_handler);Configuration item Example Description AIOT_OTAOPT_MQTT_HANDLER demo_ota_recv_handler When the device receives an OTA update instruction from IoT Platform, this callback function is called.
Step 3: Report the current device version
After the device establishes an MQTT connection, call aiot_ota_report_version to report the current version number of the device. IoT Platform determines whether an update is required based on the version number.
In the following sample code, the version number reported by the device before the OTA update is 1.0.0. In your application, you must obtain the actual version number from the device's configuration area and modify the code accordingly.
The device must report its version number at least once before an OTA update.
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 instructions
- After you add an update package and start an update task in the IoT Platform console, IoT Platform sends an update instruction to the device.For more information, see Add an update package.
- The device calls aiot_mqtt_recv to receive messages. When a message is identified as an OTA update instruction, the
demo_ota_recv_handlercallback function is called. - Write the processing logic for the callback function.
You can use the following information to write the processing logic for the callback function:- IoT Platform sends OTA update package instructions to the device over the topic
/ota/device/upgrade/${ProductKey}/${DeviceName}.For more information about ${ProductKey} and ${DeviceName}, see Obtain device credentials.
- The type of the OTA update instruction is 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; } …… …… } - The data structure type for OTA update instruction messages is aiot_ota_recv_t. Link SDK automatically parses the received update instruction messages.
- You can refer to the sample code to write the processing logic for the callback function. For more information, see Step 5: Download the update package and perform an OTA update.
- IoT Platform sends OTA update package instructions to the device over the topic
Step 5: Download the update package and perform an OTA update
When the demo_ota_recv_handler function is triggered, the downloader initiates a download request over HTTPS to download the update package and perform the OTA update.
- Initialize the downloader.
Call aiot_download_init to create adownloadNoteOTA updates support multi-file update packages. 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);
- Configure download parameters.
Call aiot_download_setopt to configure parameters for the download task.NoteOTA updates support multi-file update packages. When you download a package, you must distinguish the ID, quantity, and download progress of each file.
/* Set the download protocol to TLS. */ aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_CRED, (void *)(&cred)); /* Set the server port for the download. */ aiot_download_setopt(dl_handle, AIOT_DLOPT_NETWORK_PORT, (void *)(&port)); /* Set the download task information. This is obtained from the task_desc member of the ota_msg input parameter. It includes the download URL, firmware size, firmware signature, and more. */ aiot_download_setopt(dl_handle, AIOT_DLOPT_TASK_DESC, (void *)(ota_msg->task_desc)); /* Set the callback function that the SDK calls when the downloaded content arrives. */ aiot_download_setopt(dl_handle, AIOT_DLOPT_RECV_HANDLER, (void *)(demo_download_recv_handler)); /* Set the maximum cache length for a single download. The user is notified each time this memory buffer is full. */ aiot_download_setopt(dl_handle, AIOT_DLOPT_BODY_BUFFER_MAX_LEN, (void *)(&max_buffer_len)); /* Set the data to be shared between different calls of AIOT_DLOPT_RECV_HANDLER. For example, the sample stores the progress here. */ 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); /* If this is the first download task, report a progress of 0. */ if (0 == ota_msg->task_desc->file_id) { aiot_download_report_progress(dl_handle, 0); }
- Initiate a download request.
- Start the download thread
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 { /* The download thread is set to the detach type. It can exit on its own after the firmware content is obtained. */ pthread_detach(g_download_thread); }
- In the
demo_ota_download_threaddownload thread, call aiot_download_send_request to send an HTTPS GET request to the specified update package storage server.
void *demo_ota_download_thread(void *dl_handle) { int32_t ret = 0; printf("\r\nstarting download thread in 2 seconds ......\r\n"); sleep(2); /* Request to download from the update package storage server. */ /* * TODO: The following code uses one request to get the entire firmware content. * For devices with limited resources or poor network conditions, you can also download in segments. To do this, combine the following statements: * * aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_START, ...); * aiot_download_setopt(dl_handle, AIOT_DLOPT_RANGE_END, ...); * aiot_download_send_request(dl_handle); * * In this case, you need to place the combined statements in a loop and call send_request and recv multiple times. * */ …… …… }You can download the update package in segments using AIOT_DLOPT_RANGE_START and AIOT_DLOPT_RANGE_END.
For example, to download a 1024-byte update package in two parts, you can set the parameters as follows:- First part:
AIOT_DLOPT_RANGE_START=0,AIOT_DLOPT_RANGE_END=511 - Second part:
AIOT_DLOPT_RANGE_START=512,AIOT_DLOPT_RANGE_END=1023
- First part:
- Start the download thread
- Receive the update package.
- After the download request is sent, call aiot_download_recv in the
demo_ota_download_threaddownload thread to receive the update package data. The received data triggers thedemo_download_recv_handlercallback function. In the callback function, you must save the downloaded update package to the device's local storage or file system.
void *demo_ota_download_thread(void *dl_handle) { …… …… aiot_download_send_request(dl_handle); // while (1) { while (should_stop == 0) { /* Receive the firmware content from the server over the network. */ ret = aiot_download_recv(dl_handle); /* When the entire firmware is downloaded, the return value of aiot_download_recv() is STATE_DOWNLOAD_FINISHED. Otherwise, it is the number of bytes obtained in the current call. */ 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; } } …… …… }
- Define the
demo_download_recv_handlercallback function to include the logic for storing and updating the package after the download is complete.
Note The sample code only prints information and does not include logic for storing and burning the update package. You must write the code to save the update package based on your environment and then burn the firmware to complete the OTA update.- If the OTA update uses a single-file package, you can burn the file to a specified local storage location.
- If the OTA update uses a multi-file package, each file can correspond to a different storage address. You must identify the
file_namefield of each firmware.
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; /* Currently, only the case where packet->type is AIOT_DLRECV_HTTPBODY is supported. */ if (!packet || AIOT_DLRECV_HTTPBODY != packet->type) { return; } percent = packet->data.percent; /* userdata can store data that needs to be shared between different entries into demo_download_recv_handler(). */ /* Here, it is used to store the firmware download percentage from the last time this callback function was entered. */ if (userdata) { last_percent = (download_status->last_percent); } data_buffer_len = packet->data.len; /* If percent is negative, a packet reception exception or a digest verification error occurred. */ if (percent < 0) { printf("exception: percent = %d\r\n", percent); if (userdata) { free(userdata); } return; } …… …… }
- After the download request is sent, call aiot_download_recv in the
- Report the download progress.
In the
demo_download_recv_handlercallback function, call aiot_download_report_progress to report the download progress and any exceptions that occur during the update process, such as a burning failure or network disconnection, to IoT Platform.- View reported progress:
The progress is displayed in the IoT Platform console. For more information, see View update status.
- Reporting progress for normal and abnormal downloads:
- If the download is normal, the download progress is reported to IoT Platform as an integer percentage. Link SDK automatically calculates the value of the percent parameter and reports it to IoT Platform.
- If the download is abnormal or a firmware burning exception occurs after the download, you must report the exception to IoT Platform. For a list of protocol error codes between the device and IoT Platform, see aiot_ota_protocol_errcode_t.
- Progress reporting methods:
- If the OTA update uses a single-file package, the reported progress is the total progress.
- If the OTA update uses a multi-file package, do not report the download progress of individual files because this can cause confusion. When you report progress, we recommend that you calculate the percentage using the total file size as the denominator and the total number of bytes downloaded for all files as the numerator. Then, you can report the calculated download progress.You can develop scenarios for reporting update package progress based on your business needs. For example:
- Report 0% when the download starts. Then, report the progress only when the total download progress reaches an integer multiple of 10%, up to 100%.
- To simplify progress reporting, you can report 0% when the download starts and 100% after it is complete. This means you only report the progress twice.
void demo_download_recv_handler(void *handle, const aiot_download_recv_t *packet, void *userdata) { …… …… /* * TODO: When a segment of firmware is successfully downloaded, the user should save the memory * starting at packet->data.buffer with a length of packet->data.len to a local storage location. * * If the burning fails, you should also call aiot_download_report_progress(handle, -4) to report the failure to IoT Platform. * Note: The error codes agreed upon with the cloud platform in the protocol are in the aiot_ota_protocol_errcode_t type. For example: * -1: indicates an update failure. * -2: indicates a download failure. * -3: indicates a verification failure. * -4: indicates a burning failure. * */ /* When the value of the percent input parameter is 100, it means the SDK has finished downloading the entire firmware content. */ if (percent == 100) { g_finished_task_num++; /* * TODO: At this point, all firmware burning should be complete. Save the current work, restart the device, and start with the new firmware. The new firmware must report the new version number to IoT Platform using the following code. For example, if updating from 1.0.0 to 1.1.0, the value of new_version is 1.1.0. aiot_ota_report_version(ota_handle, new_version); IoT Platform considers the update successful only after receiving the new version number. Otherwise, the update is considered failed. If the update fails after a successful download, you should also call aiot_download_report_progress(handle, -1) to report the failure type. */ } /* Simplified output. The progress is printed and reported to the server only when the download progress has increased by 5% or more since the last report. */ 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) { /* Considering concurrent downloads by multiple threads, report 100% progress only after all files are downloaded. */ aiot_download_report_progress(handle, 100); } } if (percent == 100 && userdata) { free(userdata); } } } - View reported progress:
- Exit the downloader.
After the update package content is downloaded, call aiot_download_deinit to destroy the
downloadsession. The download thread then exits.aiot_download_deinit(&dl_handle); printf("download thread exit\n");
Step 6: Report the version number after the update
- If the OTA update uses a single-file package that contains only one firmware, report the firmware version number included in the package to IoT Platform to complete version matching.
- If the OTA update uses a multi-file package, the files can correspond to one or more firmware. If each file has a different firmware version number, such as
a1,b1, andc1, report the combined package version number, such asa1b1c1.
For sample code on reporting the version number, see Step 3: Report the current device version number.
After the device completes the OTA update, it must report the latest version number. Otherwise, IoT Platform considers the OTA update task to have failed.
If the device needs to be restarted after the update, it must report the latest version number after the restart.
- The sample code does not include the logic for reporting the version number after the update is complete. You must add this logic to your code.
Step 7: Disconnect
MQTT is typically used for devices that require a persistent connection. Therefore, the program usually does not reach this point.
In the sample program, the main thread is responsible for configuring parameters and establishing a connection. After the connection is established, the main thread can enter hibernation.
You can call aiot_mqtt_disconnect to send a disconnection message to IoT Platform and disconnect from the network.
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 OTA program
Call aiot_ota_deinit to destroy the OTA instance.
aiot_ota_deinit(&ota_handle);What to do next
-
./output/fota-multi-file_demo.For more information, see Compile and run.
- Operation log