全部产品
云市场

设备OTA开发

更新时间:2019-03-15 17:20:08

功能说明

OTA(Over-the-Air Technology)即空中下载技术. 物联网平台支持通过OTA方式进行设备固件升级

基于MQTT协议下固件升级流程如下

OTA例程讲解

OTA整体流程请见OTA服务

  • 通过OTA的API可以实现设备端固件下载, 但是如何存储/使用下载到的固件, 需要用户实现
  • 存储固件是指将下载到的固件存储到FLASH等介质上
  • 使用固件, 包括加载新下载的固件, 需要用户根据业务的具体需求(比如需要用户点击升级按钮)来实现

下面用两个例子分别说明如何用基础版接口和高级版接口来实现OTA功能

用基础版接口实现的OTA例程

现对照 src/ota/examples/ota_example_mqtt.c 例程分步骤讲解如何使用基础版的接口实现OTA的功能

1. OTA业务建立前的准备: 导入设备三元组, 初始化连接信息

  1. int main(int argc, char *argv[]) {
  2. ...
  3. /**< get device info*/
  4. HAL_SetProductKey(PRODUCT_KEY);
  5. HAL_SetDeviceName(DEVICE_NAME);
  6. HAL_SetDeviceSecret(DEVICE_SECRET);
  7. /**< end*/
  8. _ota_mqtt_client()
  9. }

2. 在_ota_mqtt_client函数完成建连和OTA的主要配置逻辑

  1. /* Device AUTH */
  2. if (0 != IOT_SetupConnInfo(g_product_key, g_device_name, g_device_secret, (void **)&pconn_info)) {
  3. EXAMPLE_TRACE("AUTH request failed!");
  4. rc = -1;
  5. goto do_exit;
  6. }
  7. /* Initialize MQTT parameter */
  8. memset(&mqtt_params, 0x0, sizeof(mqtt_params));
  9. mqtt_params.port = pconn_info->port;
  10. mqtt_params.host = pconn_info->host_name;
  11. mqtt_params.client_id = pconn_info->client_id;
  12. mqtt_params.username = pconn_info->username;
  13. mqtt_params.password = pconn_info->password;
  14. mqtt_params.pub_key = pconn_info->pub_key;
  15. mqtt_params.request_timeout_ms = 2000;
  16. mqtt_params.clean_session = 0;
  17. mqtt_params.keepalive_interval_ms = 60000;
  18. mqtt_params.read_buf_size = OTA_MQTT_MSGLEN;
  19. mqtt_params.write_buf_size = OTA_MQTT_MSGLEN;
  20. mqtt_params.handle_event.h_fp = event_handle;
  21. mqtt_params.handle_event.pcontext = NULL;
  22. /* Construct a MQTT client with specify parameter */
  23. pclient = IOT_MQTT_Construct(&mqtt_params);
  24. if (NULL == pclient) {
  25. EXAMPLE_TRACE("MQTT construct failed");
  26. rc = -1;
  27. goto do_exit;
  28. }

3. 在_ota_mqtt_client函数进行OTA有关的初始化工作(主要是订阅跟这个设备有关的固件升级信息)

  1. h_ota = IOT_OTA_Init(PRODUCT_KEY, DEVICE_NAME, pclient);
  2. if (NULL == h_ota) {
  3. rc = -1;
  4. EXAMPLE_TRACE("initialize OTA failed");
  5. goto do_exit;
  6. }

4. 建立一个循环, 一直去尝试接收OTA升级的消息

  1. int ota_over = 0;
  2. do {
  3. uint32_t firmware_valid;
  4. EXAMPLE_TRACE("wait ota upgrade command....");
  5. /* 接收MQTT消息 */
  6. IOT_MQTT_Yield(pclient, 200);
  7. /* 判断接收到的消息中是否有固件升级的消息 */
  8. if (IOT_OTA_IsFetching(h_ota)) {
  9. /* 下载OTA内容, 上报下载进度,见章节 "5. 下载OTA内容, 上报下载进度" */
  10. /* 校验固件的md5, 见章节 "6. 校验md5的值" */
  11. }
  12. } while (!ota_over);

需要到服务端推送一个固件升级事件下去, IOT_OTA_IsFetching返回才能结果为1, 才能走入固件升级的逻辑. 推送固件升级事件的具体步骤如下:

  • 到IoT控制台的 OTA服务 页面, 点击”新增固件”,
  • 点击”创建固件”, “验证固件”,
  • 点击这个新增固件的”批量升级”按钮, 从中选择设备所属产品为 examples/ota/ota_mqtt-example.c 中三元组对应的产品,
  • 待升级版本号点开下拉框选当前版本号, “升级范围”选”定向升级”, 再从”设备范围”中选当前的三元组对应的设备, 点击确定即可

5. 下载OTA内容, 上报下载进度

  1. do {
  2. /* 下载OTA固件 */
  3. len = IOT_OTA_FetchYield(h_ota, buf_ota, OTA_BUF_LEN, 1);
  4. if (len > 0) {
  5. if (1 != fwrite(buf_ota, len, 1, fp)) {
  6. EXAMPLE_TRACE("write data to file failed");
  7. rc = -1;
  8. break;
  9. }
  10. } else {
  11. /* 上报已下载进度 */
  12. IOT_OTA_ReportProgress(h_ota, IOT_OTAP_FETCH_FAILED, NULL);
  13. EXAMPLE_TRACE("ota fetch fail");
  14. }
  15. /* get OTA information */
  16. /* 获取已下载到的数据量, 文件总大小, md5信息, 版本号等信息 */
  17. IOT_OTA_Ioctl(h_ota, IOT_OTAG_FETCHED_SIZE, &size_downloaded, 4);
  18. IOT_OTA_Ioctl(h_ota, IOT_OTAG_FILE_SIZE, &size_file, 4);
  19. IOT_OTA_Ioctl(h_ota, IOT_OTAG_MD5SUM, md5sum, 33);
  20. IOT_OTA_Ioctl(h_ota, IOT_OTAG_VERSION, version, 128);
  21. last_percent = percent;
  22. percent = (size_downloaded * 100) / size_file;
  23. if (percent - last_percent > 0) {
  24. /* 上报已下载进度 */
  25. IOT_OTA_ReportProgress(h_ota, percent, NULL);
  26. IOT_OTA_ReportProgress(h_ota, percent, "hello");
  27. }
  28. IOT_MQTT_Yield(pclient, 100);
  29. /* 判断下载是否结束 */
  30. } while (!IOT_OTA_IsFetchFinish(h_ota));

6. 校验md5的值

  1. IOT_OTA_Ioctl(h_ota, IOT_OTAG_CHECK_FIRMWARE, &firmware_valid, 4);
  2. if (0 == firmware_valid) {
  3. EXAMPLE_TRACE("The firmware is invalid");
  4. } else {
  5. EXAMPLE_TRACE("The firmware is valid");
  6. }
  7. ota_over = 1;

7. 用户通过IOT_OTA_Deinit释放所有资源

  1. if (NULL != h_ota) {
  2. IOT_OTA_Deinit(h_ota);
  3. }
  4. if (NULL != pclient) {
  5. IOT_MQTT_Destroy(&pclient);
  6. }
  7. if (NULL != msg_buf) {
  8. HAL_Free(msg_buf);
  9. }
  10. if (NULL != msg_readbuf) {
  11. HAL_Free(msg_readbuf);
  12. }
  13. if (NULL != fp) {
  14. fclose(fp);
  15. }
  16. return rc;

8. 固件的存储

在_ota_mqtt_client函数中通过下述方式打开, 写入和关闭一个文件

  1. fp = fopen("ota.bin", "wb+")
  2. ...
  3. if (1 != fwrite(buf_ota, len, 1, fp)) {
  4. EXAMPLE_TRACE("write data to file failed");
  5. rc = -1;
  6. break;
  7. }
  8. ...
  9. if (NULL != fp) {
  10. fclose(fp);
  11. }

用高级版接口实现的OTA例程

现对照src/dev_model/examples/linkkit_example_solo.c分步骤讲解如何使用高级版的接口实现OTA的功能

1. 初始化主设备,注册FOTA的回调函数,建立与云端的连接

  1. int res = 0;
  2. int domain_type = 0, dynamic_register = 0, post_reply_need = 0;
  3. iotx_linkkit_dev_meta_info_t master_meta_info;
  4. memset(&g_user_example_ctx, 0, sizeof(user_example_ctx_t));
  5. memset(&master_meta_info, 0, sizeof(iotx_linkkit_dev_meta_info_t));
  6. memcpy(master_meta_info.product_key, PRODUCT_KEY, strlen(PRODUCT_KEY));
  7. memcpy(master_meta_info.product_secret, PRODUCT_SECRET, strlen(PRODUCT_SECRET));
  8. memcpy(master_meta_info.device_name, DEVICE_NAME, strlen(DEVICE_NAME));
  9. memcpy(master_meta_info.device_secret, DEVICE_SECRET, strlen(DEVICE_SECRET));
  10. /* Register Callback */
  11. ...
  12. ...
  13. IOT_RegisterCallback(ITE_FOTA, user_fota_event_handler);
  14. domain_type = IOTX_CLOUD_REGION_SHANGHAI;
  15. IOT_Ioctl(IOTX_IOCTL_SET_DOMAIN, (void *)&domain_type);
  16. /* Choose Login Method */
  17. dynamic_register = 0;
  18. IOT_Ioctl(IOTX_IOCTL_SET_DYNAMIC_REGISTER, (void *)&dynamic_register);
  19. /* post reply doesn't need */
  20. post_reply_need = 1;
  21. IOT_Ioctl(IOTX_IOCTL_RECV_EVENT_REPLY, (void *)&post_reply_need);
  22. /* Create Master Device Resources */
  23. g_user_example_ctx.master_devid = IOT_Linkkit_Open(IOTX_LINKKIT_DEV_TYPE_MASTER, &master_meta_info);
  24. if (g_user_example_ctx.master_devid < 0) {
  25. EXAMPLE_TRACE("IOT_Linkkit_Open Failed\n");
  26. return -1;
  27. }
  28. /* Start Connect Aliyun Server */
  29. res = IOT_Linkkit_Connect(g_user_example_ctx.master_devid);
  30. if (res < 0) {
  31. EXAMPLE_TRACE("IOT_Linkkit_Connect Failed\n");
  32. return -1;
  33. }

2. 实现上述代码中的回调函数user_fota_event_handler

该回调函数在如下两种情况下会被触发:

  • 直接收到云端下发的新固件通知时
  • 由设备端主动发起新固件查询,云端返回新固件通知时

在收到新固件通知后,可调用IOT_Linkkit_Query进行固件下载

  1. int user_fota_event_handler(int type, const char *version)
  2. {
  3. char buffer[128] = {0};
  4. int buffer_length = 128;
  5. /* 0 - new firmware exist, query the new firmware */
  6. if (type == 0) {
  7. EXAMPLE_TRACE("New Firmware Version: %s", version);
  8. IOT_Linkkit_Query(EXAMPLE_MASTER_DEVID, ITM_MSG_QUERY_FOTA_DATA, (unsigned char *)buffer, buffer_length);
  9. }
  10. return 0;
  11. }

4. 固件的存储

用户需要实现如下3个HAL接口来实现固件的存储

  1. /* SDK在开始下载固件之前进行调用 */
  2. void HAL_Firmware_Persistence_Start(void);
  3. /* SDK在接收到固件数据时进行调用 */
  4. int HAL_Firmware_Persistence_Write(char *buffer, uint32_t length);
  5. /* SDK在固件下载结束时进行调用 */
  6. int HAL_Firmware_Persistence_Stop(void);

3. 用户主动发起新固件查询:

  1. IOT_Linkkit_Query(user_example_ctx->master_devid, ITM_MSG_REQUEST_FOTA_IMAGE,
  2. (unsigned char *)("app-1.0.0-20180101.1001"), 30);

OTA功能API

用基础版接口实现OTA功能涉及的API

函数名 说明
IOT_OTA_Init OTA实例的构造函数, 创建一个OTA会话的句柄并返回
IOT_OTA_Deinit OTA实例的摧毁函数, 销毁所有相关的数据结构
IOT_OTA_Ioctl OTA实例的输入输出函数, 根据不同的命令字可以设置OTA会话的属性, 或者获取OTA会话的状态
IOT_OTA_GetLastError OTA会话阶段, 若某个 IOT_OTA_XXX() 函数返回错误, 调用此接口可获得最近一次的详细错误码
IOT_OTA_ReportVersion OTA会话阶段, 向服务端汇报当前的固件版本号
IOT_OTA_FetchYield OTA下载阶段, 在指定的timeout时间内, 从固件服务器下载一段固件内容, 保存在入参buffer中
IOT_OTA_IsFetchFinish OTA下载阶段, 判断迭代调用 IOT_OTA_FetchYield 是否已经下载完所有的固件内容
IOT_OTA_IsFetching OTA下载阶段, 判断固件下载是否仍在进行中, 尚未完成全部固件内容的下载
IOT_OTA_ReportProgress 可选API, OTA下载阶段, 调用此函数向服务端汇报已经下载了全部固件内容的百分之多少

用高级版接口实现OTA功能涉及的API

函数名 说明
IOT_Linkkit_Open 创建本地资源, 在进行网络报文交互之前, 必须先调用此接口, 得到一个会话的句柄
IOT_Linkkit_Connect 对主设备/网关来说, 将会建立设备与云端的通信. 对于子设备来说, 将向云端注册该子设备(若需要), 并添加主子设备拓扑关系
IOT_Linkkit_Yield 若SDK占有独立线程, 该函数只将接收到的网络报文分发到用户的回调函数中, 否则表示将CPU交给SDK让其接收网络报文并将消息分发到用户的回调函数中
IOT_Linkkit_Close 若入参中的会话句柄为主设备/网关, 则关闭网络连接并释放SDK为该会话所占用的所有资源
IOT_Linkkit_Query 向云端发送存在云端业务数据下发的查询报文, 包括OTA状态查询/OTA固件下载/子设备拓扑查询/NTP时间查询等各种报文
函数名 说明
IOT_RegisterCallback 对SDK注册事件回调函数, 如云端连接成功/失败, 有属性设置/服务请求到达, 子设备管理报文答复等

需要实现的HAL

用基础版接口实现OTA功能需要实现的API

用高级版接口实现OTA功能需要实现的API

函数名 说明
HAL_Firmware_Persistence_Start 固件持久化功能开始
HAL_Firmware_Persistence_Write 固件持久化写入固件
HAL_Firmware_Persistence_Stop 固件持久化功能结束