ESP32入门基础之空中升级(OTA)
Posted while(1)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ESP32入门基础之空中升级(OTA)相关的知识,希望对你有一定的参考价值。
文章目录
参考资料:
1 OTA简介
2 OTA操作流程
3 OTA操作实例
3.1 使用http实现简单的OTA升级
参考例程:examples\\system\\ota\\simple_ota_example
3.1.1 例程简介
3.1.2 在本地生成简单的HTTP服务器
-
选择要升级的程序(bin文件);
-
打开命令行界面,并进入到包含要升级的程序的目录文件夹下;
-
输入命令 python2 -m SimpleHTTPServer 8070(有些可能是python -m SimpleHTTPServer 8070),并执行;
-
保持命令行模式软件不要关闭;
-
在浏览器输入http://xxx.xxx.xxx.xxx:8070,验证http服务器是否成功;
命令行界面也有提示输出,执行的是GET操作;
-
在接下来的实验中也要保持命令行模式不要关闭。
3.1.3 配置simple_ota_example例程
- 不要关闭http服务器的命令行界面;
- 重新打开一个命令行界面,进入simple_ota_example例程, 使用 idf.py menuconfig 指令进入菜单配置模式;
- 配置好串口、所连接的wif、确保分区表有两个OTA分区
- 配置要升级的程序的URL
- 最后编译程序并烧录到ESP32开发板
- 到此说明程序升级完成
3.1.4 程序分析
- 芯片初始化及wifi连接
void app_main(void)
/*省略了代码,主要作用是芯片初始化、wifi连接等*/
xTaskCreate(&simple_ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
void simple_ota_example_task(void *pvParameter)
ESP_LOGI(TAG, "Starting OTA example");
esp_http_client_config_t config =
.url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL, //这里即升级程序的URL,在菜单中配置
.cert_pem = (char *)server_cert_pem_start, //https所需要的认证,但这里是http,所以并不需要用到
.event_handler = _http_event_handler,
;
esp_err_t ret = esp_https_ota(&config); //连接http,并下载升级程序
if (ret == ESP_OK)
esp_restart(); //下载程序成功,重启即执行升级程序
else
ESP_LOGE(TAG, "Firmware upgrade failed");
//一切正常的话不会执行到这里,如下升级程序下载失败则执行下列程序
while (1)
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "while ---");
感觉这个例程没有太大的实用性,只是帮助了解OTA的基本操作。
3.2 使用阿里云物联网平台实现简单的OTA升级
3.2.1 新建工程
- 选择 app-MqttToAliyun 作为基础工程,在此基础上修改;
- 更改工程名为 app-AliyunOTA;
3.2.2 工程菜单配置
- 执行命令 idf.py menuconfig
- 修改分区表,如下
- 修改Flash size,如下Flash size 默认为2MB,但第二步分区表选择官方提供的OTA分区表(Factory app,two OTA definitions),所以需要4MB以上的空间,如果实际的ESP32开发板Flash size 小于4MB,则可以自定义分区表。
- 其余参数,如wifi、端口、波特率可根据实际情况定义
3.2.3 将升级程序上传到阿里云物联网平台
参考资料:阿里云物联网平台->设备端OTA升级
-
该工程是以 app-MqttToAliyun为基础,所以已经实现了ESP32设备与阿里云之间的通信。
-
打开阿里云物联网平台,定位到 “物联网平台/监控运维/OTA 升级 ” ,点击 “添加升级包”,如下填完参数,最后点击下方“确认”,升级包随便选择一个工程的.bin文件
-
升级包上传完成
-
点击右方“批量升级”
点击左下角 下一步 -
到这里阿里云物联网平台就配置完了,只要ESP32开发板连接到阿里云平物联网平台的 ESP8266-TEST产品下的dev-esp32设备,就会收到阿里云物联网平台推送的JSON数据,(当然ESP32开发板要实现了OTA程序)
3.2.4 下载阿里云物理网平台TLS根证书配置工程
- 因为阿里云物联网平台使用的是https协议,所以需要TLS证书,下载地址点击这里
- 将阿里云服务器的TLS根证书替换到工程中的server_certs目录(如果没有则新建该目录),
- 进入 main 目录下,打开 CMakeLists.txt 文件,添加 root.crt: EMBED_TXTFILES $project_dir/server_certs/root.crt
- 打开 component.mk 文件,添加 root.crt: COMPONENT_EMBED_TXTFILES := $PROJECT_PATH/server_certs/root.crt
- 注意在程序中,也需要做相应的修改,如下
extern const uint8_t server_root_crt_start[] asm("_binary_root_crt_start"); //注意该地方需要修改
extern const uint8_t server_root_crt_end[] asm("_binary_root_crt_end");
esp_http_client_config_t config =
.url = url_buf,
.cert_pem = (char *)server_root_crt_start,
.event_handler = _http_event_handler,
;
3.2.5 工程编写函数
- 工程配置完成后,编译工程,下载程序到ESP32设备,ESP32成功连接到阿里云物联网平台后,会收到阿里云推送的OTA升级的JSON格式数据,该数据包含OTA升级包的URL、版本号等信息
该数据由如下函数接口打印:
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
esp_mqtt_client_handle_t client = event->client;
int msg_id;
// your_context_t *context = event->context;
switch (event->event_id)
case MQTT_EVENT_CONNECTED:
break;
case MQTT_EVENT_DISCONNECTED:
break;
case MQTT_EVENT_SUBSCRIBED:
break;
case MQTT_EVENT_UNSUBSCRIBED:
break;
case MQTT_EVENT_PUBLISHED:
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\\r\\n", event->topic_len, event->topic); //打印主题
printf("DATA=%.*s\\r\\n", event->data_len, event->data); //打印数据
break;
case MQTT_EVENT_ERROR:
break;
default:
break;
return ESP_OK;
- 该OTA升级的JSON数据,是按如下格式打印的,详细请参考:阿里云物联网平台设备端OTA升级
- 将该OTA升级的JSON数据使用JSON数据在线解析工具解析,如下
- 在工程中将OTA的JSON数据解析,并根据解析的URL下载升级包,最后重启ESP32
#define AliyunSubscribeTopic_ota_upgrade "/ota/device/upgrade/a1tUbQR2faQ/dev-esp32"
#define AliyunPublishTopic_ota_inform "/ota/device/inform/a1tUbQR2faQ/dev-esp32"
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
esp_mqtt_client_handle_t client = event->client;
// your_context_t *context = event->context;
switch (event->event_id)
...
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
char *topicBuf = (char*)malloc(event->topic_len+1);
//void *memcpy(void *s1, const void *s2, size_t n); 函数memcpy从s2指向的对象中复制n个字符到s1指向的对象中。
memcpy(topicBuf, event->topic, event->topic_len);
//printf("---Receive topic:\\n %s \\r\\n", topicBuf);//打印接收的消息
temp = strcmp(topicBuf,AliyunSubscribeTopic_ota_upgrade); //判断主题是否是OTA主题,如果是才调用OTA的JSON解析函数
if(0 == temp)
ESP_LOGI(TAG, "OTA DATA PARSE");
//char *strncpy(char *s2, const char *s1, size_t n);函数strncpy从s1指向的数组中最多复制n个字符(不复制空字符后面的字符)到s2指向的数组中。
strncpy(local_data_buffer, event->data, event->data_len); //将指针类型的数据复制到一个数组中
//printf("local_data_buffer:%s ", local_data_buffer);
ret = user_parse_json(local_data_buffer,aliyun_ota_config);//解析数据,并使用解析出来的URL下载OTA升级包
if (ret == ESP_OK) //解析并下载OTA升级包成功
//构造JSON格式数据,该数据用于反馈给阿里云物联网平台,作用是通知升级包接收完成
cJSON *Wroot = cJSON_CreateObject();
cJSON *Pitem = cJSON_CreateObject();
cJSON_AddItemToObject(Wroot, "id", cJSON_CreateString("123"));
cJSON_AddItemToObject(Wroot, "params", Pitem);
cJSON_AddItemToObject(Pitem, "step", cJSON_CreateString("100"));
cJSON_AddItemToObject(Pitem, "desc", cJSON_CreateString("OTA update successfully !"));
cJSON_AddItemToObject(Pitem, "module", cJSON_CreateString("MCU"));
//printf("%s\\n", cJSON_Print(Wroot)); //打印刚才构建的JSON数据,看是否正确
char ota_inform_buf[512];
int len = strlen(cJSON_Print(Wroot));
memcpy(ota_inform_buf, cJSON_Print(Wroot),len); //将JSON格式数据复制到数组中,将以数组的形式传递给发布函数
ota_inform_buf[len] = '\\0';
printf("%s\\n",ota_inform_buf);//打印数据是否正确
//发布信息到阿里云物联网平台
msg_id = esp_mqtt_client_publish(client, AliyunPublishTopic_ota_inform, ota_inform_buf, strlen(ota_inform_buf), 0, 0);
ESP_LOGI(TAG, "sent publish ota inform successful, msg_id=%d", msg_id);
// cJSON_Delete(Wroot); //执行这个函数会出错,不知道什么原因
// cJSON_Delete(Pitem);
esp_restart(); //ESP32设备重启,重启后将执行刚才下载的程序
else
ESP_LOGE(TAG, "Firmware upgrade failed");
free(topicBuf);
break;
...
default:
break;
return ESP_OK;
esp_err_t user_parse_json(char *json_data,user_aliyun_ota_config_t ota_config)
cJSON *item = NULL;
cJSON *root = NULL;
//root 开始-------------------------------------------------------------------------
root = cJSON_Parse(json_data); /*json_data 阿里云OTA原始数据*/
if (!root)
printf("Error before: [%s]\\n",cJSON_GetErrorPtr());
return ESP_FAIL;
printf("%s\\n\\n", cJSON_Print(root)); /*将完整的数据以JSON格式打印出来*/
item = cJSON_GetObjectItem(root, "code");
ota_config.code = cJSON_Print(item);
//data 开始-------------------------------------------------------------------------
cJSON *Pdata = cJSON_GetObjectItem(root, "data");
item = cJSON_GetObjectItem(Pdata, "size");
ota_config.size = item->valueint;
//extData 开始-------------------------------------------------------------------------
cJSON *PextData = cJSON_GetObjectItem(Pdata, "extData");
item = cJSON_GetObjectItem(PextData, "_package_udi");
ota_config._package_udi = cJSON_Print(item);
//extData 结束-------------------------------------------------------------------------
item = cJSON_GetObjectItem(Pdata, "sign");
ota_config.sign = cJSON_Print(item);
item = cJSON_GetObjectItem(Pdata, "version");
ota_config.version = cJSON_Print(item);
item = cJSON_GetObjectItem(Pdata, "url");
ota_config.url = cJSON_Print(item);
item = cJSON_GetObjectItem(Pdata, "signMethod");
ota_config.signMethod = cJSON_Print(item);
item = cJSON_GetObjectItem(Pdata, "md5");
ota_config.md5 = cJSON_Print(item);
//data 结束-------------------------------------------------------------------------
item = cJSON_GetObjectItem(root, "id");
ota_config.id = item->valuedouble;
item = cJSON_GetObjectItem(root, "message");
ota_config.message = cJSON_Print(item);
//root 结束-------------------------------------------------------------------------
// cJSON_Delete(root); //执行这个函数会出错,不知道什么原因
// cJSON_Delete(Pdata);
// cJSON_Delete(PextData);
// cJSON_Delete(item);
// printf("code:%s\\n", ota_config.code);
// printf("size:%d\\n", ota_config.size);
// printf("_package_udi:%s\\n", ota_config._package_udi);
// printf("sign:%s\\n", ota_config.sign);
// printf("version:%s\\n", ota_config.version);
// printf("url:%s\\n", ota_config.url);
// printf("signMethod:%s\\n", ota_config.signMethod);
// printf("md5:%s\\n", ota_config.md5);
// printf("id:%f\\n", ota_config.id);
// printf("message:%s\\n", ota_config.message);
char url_buf[OTA_URL_SIZE];
int len = strlen(ota_config.url);
memcpy(url_buf, ota_config.url, len); //将指针型数据复制到数组中,
//解析出来的URL包含了双引号"",但数组中的URL不能包含双引号""
for(int i = 0;i < len;i++ )
url_buf[i] = url_buf[i+1]; //为了去掉数组中的双引号“”,这里是前部分
url_buf[len - 2] = '\\0'; //为了去掉数组中的双引号“”,这里是后部分
printf("buf:%s\\n", url_buf); //打印检查URL是否正确
esp_http_client_config_t config =
//. url = "https://xxx" //这里需要双引号""
.url = url_buf, //这里,数组中不能包含双引号""
.cert_pem = (char *)server_root_crt_start,
.event_handler = _http_event_handler,
;
esp_err_t ret = esp_https_ota(&config); //下载升级包等一系列操作
return ret;
3.2.6 实验分析
程序编写完成,编译下载到ESP32设备中,查看串口打印数据,如下
//数据开始
ets Jul 29 2019 12:21:46
...
I (61) boot: Partition Table: //分区表
I (64) boot: ## Label Usage Type ST Offset Length
I (72) boot: 0 nvs WiFi data 01 02 00009000 00004000
I (79) boot: 1 otadata OTA data 01 00 0000d000 00002000
I (87) boot: 2 phy_init RF data 01 01 0000f000 00001000
I (94) boot: 3 factory factory app 00 00 00010000 00100000
I (102) boot: 4 ota_0 OTA app 00 10 00110000 00100000
I (109) boot: 5 ota_1 OTA app 00 11 00210000 00100000
I (117) boot: End of partition table
I (121) boot: Defaulting to factory image //刚编译并通过有线烧录,默认执行工厂镜像程序
...
I (498) boot: Loaded app from partition at offset 0x10000 //从偏移地址0x10000处加载程序
I (498) boot: Disabling RNG early entropy source...
I (498) cpu_start: Pro cpu up.
I (502) cpu_start: Application information: //该程序信息
I (507) cpu_start: Project name: app-MqttToAliyun
I (513) cpu_start: App version: 5204503a-dirty
I (518) cpu_start: Compile time: Jul 3 2021 11:33:25
...
I (0) cpu_start: App cpu up.
...
I (1698) wifi station: connected to ap SSID:canbo-418-2.4G password:canbo418 //连接wifi
I (1708) MQTT_EXAMPLE: into mqtt_test_task //开始执行mqtt任务,即连接阿里云物联网平台
I (1708) MQTT_EXAMPLE: Other event id:7
I (1948) MQTT_EXAMPLE: MQTT_EVENT_CONNECTED
I (1948) MQTT_EXAMPLE: sent publish successful, msg_id=49855
I (1948) MQTT_EXAMPLE: sent subscribe successful, msg_id=15854
I (1998) MQTT_EXAMPLE: MQTT_EVENT_PUBLISHED, msg_id=49855
I (2058) MQTT_EXAMPLE: MQTT_EVENT_SUBSCRIBED, msg_id=15854
I (2068) MQTT_EXAMPLE: sent publish successful, msg_id=0
I (6228) MQTT_EXAMPLE: MQTT_EVENT_DATA
I (6238) MQTT_EXAMPLE: OTA DATA PARSE//收到阿里云物联网平台推送的OTA的JSON格式数据
"code": "1000",
"data":
"size": 822528,
"extData":
"_package_udi": "这是阿里云OTA测试程序"
,
"sign": "72fd4a8b0e67fc0657b1ea707570e27a",
"version": "1.00",
"url": "https://iotx-ota.oss-cn-shanghai.aliyuncs.com/ota/6bc61a794bad89c4c1edf4ecf46a1666/ckqnivitr0003268hsmg81bac.bin?Expires=1625537056&OSSAccessKeyId=LTAI4G1TuWwSirnbAzUHfL3e&Signature=39CeoZ34CY1KucXl69yq1ajHqDw%3D",
"signMethod": "Md5",
"md5": "72fd4a8b0e67fc0657b1ea707570e27a"
,
"id": 1625450656015,
"message": "success"
buf:https://iotx以上是关于ESP32入门基础之空中升级(OTA)的主要内容,如果未能解决你的问题,请参考以下文章
ESP32学习笔记(25)——OTA(空中升级)接口使用(简化API)