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