OTA(Over-the-Air Technology)即空中下载技术,物联网平台支持通过OTA方式进行设备固件升级。
背景信息
基于MQTT协议下固件升级流程如下。
OTA例程讲解
过OTA的API可以实现设备端固件下载。但是如何存储/使用下载到的固件,需要用户实现。
存储固件是指将下载到的固件存储到FLASH等介质上。
使用固件,包括加载新下载的固件,需要用户根据业务的具体需求(例如需要用户单击升级按钮)来实现。
OTA整体流程请见OTA服务。
下面用两个例子分别说明如何用基础版接口和高级版接口来实现OTA功能。
用基础版接口实现的OTA例程
现对照 src/ota/examples/ota_example_mqtt.c 例程分步骤讲解如何使用基础版的接口实现OTA的功能。
OTA业务建立前的准备:导入设备证书,初始化连接信息。
int main(int argc, char *argv[]) { ... /**< get device info*/ HAL_SetProductKey(PRODUCT_KEY); HAL_SetDeviceName(DEVICE_NAME); HAL_SetDeviceSecret(DEVICE_SECRET); /**< end*/ _ota_mqtt_client() }
在_ota_mqtt_client函数完成建连和OTA的主要配置逻辑。
/* Device AUTH */ if (0 != IOT_SetupConnInfo(g_product_key, g_device_name, g_device_secret, (void **)&pconn_info)) { EXAMPLE_TRACE("AUTH request failed!"); rc = -1; goto do_exit; } /* Initialize MQTT parameter */ memset(&mqtt_params, 0x0, sizeof(mqtt_params)); mqtt_params.port = pconn_info->port; mqtt_params.host = pconn_info->host_name; mqtt_params.client_id = pconn_info->client_id; mqtt_params.username = pconn_info->username; mqtt_params.password = pconn_info->password; mqtt_params.pub_key = pconn_info->pub_key; mqtt_params.request_timeout_ms = 2000; mqtt_params.clean_session = 0; mqtt_params.keepalive_interval_ms = 60000; mqtt_params.read_buf_size = OTA_MQTT_MSGLEN; mqtt_params.write_buf_size = OTA_MQTT_MSGLEN; mqtt_params.handle_event.h_fp = event_handle; mqtt_params.handle_event.pcontext = NULL; /* Construct a MQTT client with specify parameter */ pclient = IOT_MQTT_Construct(&mqtt_params); if (NULL == pclient) { EXAMPLE_TRACE("MQTT construct failed"); rc = -1; goto do_exit; }
在
_ota_mqtt_client
函数进行OTA有关的初始化工作(主要是订阅跟这个设备有关的固件升级信息)。h_ota = IOT_OTA_Init(PRODUCT_KEY, DEVICE_NAME, pclient); if (NULL == h_ota) { rc = -1; EXAMPLE_TRACE("initialize OTA failed"); goto do_exit; }
建立一个循环,一直去尝试接收OTA升级的消息。
int ota_over = 0; do { uint32_t firmware_valid; EXAMPLE_TRACE("wait ota upgrade command...."); /* 接收MQTT消息 */ IOT_MQTT_Yield(pclient, 200); /* 判断接收到的消息中是否有固件升级的消息 */ if (IOT_OTA_IsFetching(h_ota)) { /* 下载OTA内容, 上报下载进度,见章节 "5. 下载OTA内容, 上报下载进度" */ /* 校验固件的md5, 见章节 "6. 校验md5的值" */ } } while (!ota_over);
需要到服务端推送一个固件升级事件下去,
IOT_OTA_IsFetching
返回才能结果为1,才能走入固件升级的逻辑。推送固件升级事件的具体步骤如下。到IoT控制台的 OTA服务页面,单击新增固件。
单击创建固件,验证固件。
单击这个新增固件的批量升级按钮,从中选择设备所属产品为examples/ota/ota_mqtt-example.c中设备证书对应的产品。
待升级版本号点开下拉框选当前版本号,升级范围选定向升级,再从设备范围中选当前的设备证书对应的设备,单击确定即可。
下载OTA内容,上报下载进度。
do { /* 下载OTA固件 */ len = IOT_OTA_FetchYield(h_ota, buf_ota, OTA_BUF_LEN, 1); if (len > 0) { if (1 != fwrite(buf_ota, len, 1, fp)) { EXAMPLE_TRACE("write data to file failed"); rc = -1; break; } } else { /* 上报已下载进度 */ IOT_OTA_ReportProgress(h_ota, IOT_OTAP_FETCH_FAILED, NULL); EXAMPLE_TRACE("ota fetch fail"); } /* get OTA information */ /* 获取已下载到的数据量, 文件总大小, md5信息, 版本号等信息 */ IOT_OTA_Ioctl(h_ota, IOT_OTAG_FETCHED_SIZE, &size_downloaded, 4); IOT_OTA_Ioctl(h_ota, IOT_OTAG_FILE_SIZE, &size_file, 4); IOT_OTA_Ioctl(h_ota, IOT_OTAG_MD5SUM, md5sum, 33); IOT_OTA_Ioctl(h_ota, IOT_OTAG_VERSION, version, 128); last_percent = percent; percent = (size_downloaded * 100) / size_file; if (percent - last_percent > 0) { /* 上报已下载进度 */ IOT_OTA_ReportProgress(h_ota, percent, NULL); IOT_OTA_ReportProgress(h_ota, percent, "hello"); } IOT_MQTT_Yield(pclient, 100); /* 判断下载是否结束 */ } while (!IOT_OTA_IsFetchFinish(h_ota));
校验md5的值。
IOT_OTA_Ioctl(h_ota, IOT_OTAG_CHECK_FIRMWARE, &firmware_valid, 4); if (0 == firmware_valid) { EXAMPLE_TRACE("The firmware is invalid"); } else { EXAMPLE_TRACE("The firmware is valid"); } ota_over = 1;
用户通过
IOT_OTA_Deini
t释放所有资源。if (NULL != h_ota) { IOT_OTA_Deinit(h_ota); } if (NULL != pclient) { IOT_MQTT_Destroy(&pclient); } if (NULL != msg_buf) { HAL_Free(msg_buf); } if (NULL != msg_readbuf) { HAL_Free(msg_readbuf); } if (NULL != fp) { fclose(fp); } return rc;
固件的存储。
在
_ota_mqtt_client
函数中通过下述方式打开,写入和关闭一个文件。fp = fopen("ota.bin", "wb+") ... if (1 != fwrite(buf_ota, len, 1, fp)) { EXAMPLE_TRACE("write data to file failed"); rc = -1; break; } ... if (NULL != fp) { fclose(fp); }
用高级版接口实现的OTA例程
现对照src/dev_model/examples/linkkit_example_solo.c分步骤讲解如何使用高级版的接口实现OTA的功能。
初始化主设备,注册FOTA的回调函数,建立与云端的连接。
int res = 0; int domain_type = 0, dynamic_register = 0, post_reply_need = 0; iotx_linkkit_dev_meta_info_t master_meta_info; memset(&g_user_example_ctx, 0, sizeof(user_example_ctx_t)); memset(&master_meta_info, 0, sizeof(iotx_linkkit_dev_meta_info_t)); memcpy(master_meta_info.product_key, PRODUCT_KEY, strlen(PRODUCT_KEY)); memcpy(master_meta_info.product_secret, PRODUCT_SECRET, strlen(PRODUCT_SECRET)); memcpy(master_meta_info.device_name, DEVICE_NAME, strlen(DEVICE_NAME)); memcpy(master_meta_info.device_secret, DEVICE_SECRET, strlen(DEVICE_SECRET)); /* Register Callback */ ... ... IOT_RegisterCallback(ITE_FOTA, user_fota_event_handler); domain_type = IOTX_CLOUD_REGION_SHANGHAI; IOT_Ioctl(IOTX_IOCTL_SET_DOMAIN, (void *)&domain_type); /* Choose Login Method */ dynamic_register = 0; IOT_Ioctl(IOTX_IOCTL_SET_DYNAMIC_REGISTER, (void *)&dynamic_register); /* post reply doesn't need */ post_reply_need = 1;IOT_Ioctl(IOTX_IOCTL_RECV_EVENT_REPLY, (void *)&post_reply_need); /* Create Master Device Resources */ g_user_example_ctx.master_devid = IOT_Linkkit_Open(IOTX_LINKKIT_DEV_TYPE_MASTER, &master_meta_info); if (g_user_example_ctx.master_devid < 0) { EXAMPLE_TRACE("IOT_Linkkit_Open Failed\n"); return -1;} /* Start Connect Aliyun Server */ res = IOT_Linkkit_Connect(g_user_example_ctx.master_devid); if (res < 0) { EXAMPLE_TRACE("IOT_Linkkit_Connect Failed\n"); return -1; }
实现上述代码中的回调函数
user_fota_event_handler
。该回调函数在如下两种情况下会被触发:
直接收到云端下发的新固件通知时。
由设备端主动发起新固件查询,云端返回新固件通知时。
在收到新固件通知后,可调用
IOT_Linkkit_Query
进行固件下载。int user_fota_event_handler(int type, const char *version){ char buffer[128] = {0}; int buffer_length = 128; /* 0 - new firmware exist, query the new firmware */ if (type == 0) { EXAMPLE_TRACE("New Firmware Version: %s", version); IOT_Linkkit_Query(EXAMPLE_MASTER_DEVID, ITM_MSG_QUERY_FOTA_DATA, (unsigned char *)buffer, buffer_length); } return 0; }
固件的存储。
用户需要实现如下3个HAL接口来实现固件的存储。
/* SDK在开始下载固件之前进行调用 */ void HAL_Firmware_Persistence_Start(void); /* SDK在接收到固件数据时进行调用 */ int HAL_Firmware_Persistence_Write(char *buffer, uint32_t length); /* SDK在固件下载结束时进行调用 */ int HAL_Firmware_Persistence_Stop(void);
用户主动发起新固件查询。
IOT_Linkkit_Query(user_example_ctx->master_devid, ITM_MSG_REQUEST_FOTA_IMAGE, (unsigned char *)("app-1.0.0-20180101.1001"), 30);
OTA支持多模块
OTA除了可以升级设备固件外,还可以下载并升级设备的软件模块,客户需要在物联网平台的控制台上创建模块,如下图所示:
设备端开发时, 需要将module设置为与控制台的模块名一致:
char* module = "mcu";
IOT_Ioctl(IOTX_IOCTL_SET_MODULE, (void *)module);
其他流程如正常的ota测试流程,在云端控制台部署任务后, 设备端会有如下日志, 具体见其中的module字段:
[dbg] otamqtt_UpgrageCb(111): topic=/ota/device/upgrade/a1******PjW/foDzDj*******3PDJ9d
[dbg] otamqtt_UpgrageCb(112): len=431, topic_msg={"code":"1000","data":{"size":143360,"module":"mcu","sign":"867f1536fb********a2205436252","version":"111","url":"https://iotx-******ily.oss-cn-shanghai.aliyuncs.com/ota/338ac9db05545dcab9*********52/ck75mi***********5xbgz61vl.tar?Expires=1582951757&OSSAccessKeyId=aS4***********j6Gy&Signature=urw%2F9WAlizQui*************0A%3D","signMethod":"Md5","md5":"867f1536fb*********436252"},"id":1582865357795,"message":"success"}
[dbg] otamqtt_UpgrageCb(129): receive device upgrade
[inf] ofc_Init(47): protocol: https
received state: -0x092C(msg queue size: 0, max size: 50)
received state: -0x092C(msg enqueue w/ message type: 43)
received state: -0x092C(msg dequeue)
received state: -0x0938(alink event type: 43)
received state: -0x0938(new fota information received, 111)
user_fota_module_event_handler.219: New Firmware Version: 111, module: mcu