在最近的两篇文章中,我们回顾了概念和流程:终端设备如何安全、安全地从服务器下载固件到本地无错地?.文章链接在这里:物联网设备OTA软件升级:升级包下载流程之旅物联网设备OTA软件升级:完全升级和增量升级本文将继续深入,用一个实际的ESP32项目来完整梳理整个过程OTA升级。主要包括以下三个部分:在AWS平台上,部署OTA升级任务需要完成哪些步骤;ESP32模组中,关于Flash分区和OTA升级控制流程及代码说明;进行OTA升级;PS:以下内容中,终端设备指的是ESP32模组。ESP32Flash分区其实在ESP32的官方文档中有描述,已经很详细了。不仅每个操作的步骤都写的很清楚,一些可能遇到的错误也会善意提醒。以下部分基本来源于官方文档。这里我们只是摘录了一些和本文相关的比较重要的内容。首先要了解的肯定是Flash的分区信息。所有固件和数据都必须存储在闪存中,闪存是系统的内存组件。没有它,再聪明的CPU也是白搭。关于分区表,ESP32中预定义了2个分区表,分别对应:是否有OTA功能,截图如下:不带OTA功能的分区表:带OTA功能的分区表:官方文档链接在这里:https://docs.espressif.com/projects/esp-idf/zh_CN/v4.3-beta3/esp32/api-guides/partition-tables.html。既然我们描述的是OTA过程,那肯定是基于这个具有OTA功能的分区表。在这张分区表中,一共定义了3个应用分区:工厂分区;ota_0分区;ota_1分区;这三个分区的类型都是app,但是具体的app类型不同。其中,位于0x10000偏移地址的是工厂应用(factory),另外两个是OTA应用(ota_0,ota_1)。名为otadata的数据分区用于保存OTA升级所需的数据。bootloader会查询这个分区(otadata)的数据,来决定应该使用哪个OTA应用分区来加载程序。如果otadata分区为空(说明设备没有进行过OTA升级),则执行factory程序,即执行factory分区中的固件程序。如果otadata分区不为空,bootloader会加载这个分区的数据,然后判断:启动哪个OTA镜像文件。AWS平台部署OTA升级任务AWS平台根据不同的业务类型分为不同的服务。这样流程更规范,操作步骤更多,当然利润也更高!从上一篇文章可以看出,当一个新的固件准备好后,需要做两件事:把固件(bin文件)和一个固件描述文件(json格式的文本文件),上传到S3云端存储服务器;在AWSCore任务管理中,创建一个新的升级任务(您将获得一个作业ID)。在这个任务中,你需要选择:(1)第1步上传的json文件;(2)哪些终端设备需要升级;json格式的固件描述文件,格式大致如下(可根据实际业务需要修改):{"product":"productname","group":"devicegrouping","firmware":[{"ota_type":"esp32","url":"http://xxx/esp32-v1.1.0.bin","md5":"xxx"}]}不知道大家有没有注意到:在固件中字段,使用数组([...])而不是对象({...})?之所以这样组织,是因为OTA升级不仅可以升级ESP32模块中的固件("ota_type":"esp32"),还可以更新一些其他的固件或者用户数据。例如:更新ESP32串口连接的MCU中的固件程序。顺便说一句,当终端通过网络连接到云平台时,它有一个唯一的ID号。一般使用ESP32模组上网卡的MAC地址作为唯一ID。当以上步骤完成后,服务器端就有了一个升级任务关系链:也就是说:一个JobID对应一个OTA升级任务。终端设备OTA升级过程中,从这个JobID开始。ESP32OTA升级触发ESP32与AWS平台通过MQTT协议进行通信。因此,当运营商创建OTA升级任务时,所有相关终端设备都必须接收到来自预定主题的OTA升级通知指令。例如一个可能的主题:$aws/things/xxx/job/notify其中xxx代表终端设备的MAC地址。只有这样,每个设备才能接收到自己的命令。升级通知命令的内容必须包含OTA升级的JobID,例如:{"timestamp":"xxxxxx","job_id":"001"}终端设备收到升级通知命令后,会提取job_id字段,然后向云平台发起请求:获取与该job_id关联的固件描述信息,即之前上传的Json格式的文件信息。AWS平台收到该请求后,会将与该job_id关联的OTA升级任务描述文件(json文件)发送给终端设备。设备拿到固件描述文件后,自然知道固件:版本、下载地址、MD5值等信息,于是进入下一个下载环节。以上流程描述基本上就是终端设备触发OTA升级的最基本流程。在实际项目中,可能会遇到一些稍微复杂的情况。示例:终端设备始终处于关闭状态。此时,云平台中已经升级了数次固件,但由于设备一直没有运行,其固件已经过时了好几个版本。有一天,这台设备开机运行。此时会收到云平台的几个升级任务。这个时候应该怎么处理呢?或许,我们需要对升级通知的说明给出更详细的内容,让设备有足够的信息来决定如何升级。ESP32固件下载及本地升级提取固件下载地址(URL)后,ESP32开始进入下载链接。官方文档非常详细地描述了固件下载过程。以下代码摘自官方文档:链接地址:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/system/ota.htmlboolimage_header_was_checked=false;while(1){intdata_read=esp_http_client_read(client,ota_write_data,BUFFSIZE);...if(data_read>0){if(image_header_was_checked==false){esp_app_desc_tnew_app_info;if(data_read>sizeof(esp_image_header_t)+sizeof(esp_image_eger_+sizeof(esp_app_desc_t)){//checkcurrentversionwithdownloadingif(esp_efuse_check_secure_version(new_app_info.secure_version)==false){ESP_LOGE(TAG,"Thisanewappcannotbedownloadedduetoasecureversionislowerthanstoredinefuse.");http_cleanup(client);task_fatal_error();}image_header_was_checked=true;esp_ota_begin(update_partition,_,OTA_handSIZE);&}}esp_ota_write(update_handle,(constvoid*)ota_write_data,data_read);}}把这个过程画成流程图,大概是这样的:我们假设这次固件升级存放在ota_0分区。固件下载完成后,esp_ota_end()函数会在otadata分区写一个标记:下次开机请加载ota_0分区的固件程序。当ESP32重启时,bootloader从otadata分区读取数据,知道这次需要启动ota_0分区的固件。这时候有一件很重要的事情要做:当ota_0分区的固件正常启动后,需要调用函数esp_ota_mark_app_valid_cancel_rollback()将ESP_OTA_IMG_VALID写入otadata区,并标记:该分区的固件是no问题!这样的话,以后每次重启都会加载ota_0分区里的固件。相反的情况:如果ota_0分区的固件在第一次启动后与新固件有问题,需要调用函数esp_ota_mark_app_invalid_rollback_and_reboot()将ESP_OTA_IMG_INVALID写入otadata区,并标记:theisaproblemwiththefirmwarein这个分区!这种情况下,重启后,bootloader会选择之前app分区中的固件,可能是factory分区,也可能是ota_1分区。OTA升级过程中断怎么办?上面描述的过程是比较理想的情况,那么遇到一些异常情况怎么办呢?例如:从接收到固件描述信息到固件下载完成。在此期间的任意时刻,如果设备因断电等原因重启,如何继续OTA升级过程?我们知道程序运行时,所有的数据都保存在内存中。重启后,内存中的数据为空白。要想OTA升级过程在任何异常情况下都能顺利进行,必须保存一些必要的信息,包括:json格式的固件描述文件;固件下载过程中已完成的每个阶段;这些信息可以调用nvs_write()函数,保存在非易失性存储器中。即使因为断电等原因导致系统重启,也可以通过nvs_read()函数读取之前已经完成的步骤,再继续后续的升级操作。通过ESP32,升级MCU固件ESP32模块只是一个用于连接网络云平台的无线设备。对于一个实际的产品,往往是另一台单片机,如STM32,起到实际功能控制的作用。单片机中的固件也可能需要OTA升级。这时候就应该使用ESP32作为中间介质,下载MCU固件并存储到本地,然后通过串口发送给单片机。在这种情况下,ESP32接收到的OTA固件描述信息可能如下所示:{"product":"产品名称","group":"设备分组","firmware":[{"ota_type":"stm32","url":"http://xxx/mcu-v1.2.3.bin","md5":"xxx"}]}从ota_type字段可以知道这次是升级MCU,那么下一个下载过程与上述过程非常相似。唯一不同的是:下载时需要将固件保存到Flash上??一个独立的数据分区,而不是ota_0或ota_1分区。至此,ESP32模组和MCU的OTA升级过程基本介绍完毕。以上内容是一些结构化的流程节点,其余是一些细节。按照官方文档的指导步骤,可以顺利完成开发!
