由于项目需要ESP32连接app进行OTA,为了支持AP模式下与STA模式下的本地局域网OTA功能(不需要OTA服务器)。
咨询乐鑫技术支持,ESP-IDF下没有该模式的官方例程。网上也一直没有找到相关例程,翻出来手册看了看倒也不难。基于esp-idf\examples\system\ota\native_ota_example与esp-idf\examples\http_server\file_serving两个例程,整理出来了这个demo分享并记录一下。

demo包含:

  1. wifi连接初始化(包括AP模式和STA模式)
  2. OTA服务器(端口89):包含固件上传页面URI、POST文件接收URI、当前固件信息查询URI
  3. 固件上传html:为原生js实现,post文件上传,上传进度及速度显示,错误显示
  4. 固件诊断程序:通过将GPIO2拉高判断固件是否运行成功,若失败则回滚固件
  5. BuildVer.sh:编译并根据编译时间生成版本号文件小工具

工程下载

ESP32 HttpServer模式下 本地OTA 例程(基于ESP-IDF类似Arduino下OTAWebUpdater例程)
https://download.csdn.net/download/l851285812/18808145

分区表

分区表相关配置自行度娘,为节省flash空间该demo未使用factory工厂分区,使用sta_0分区作为默认分区

Name Type SubType Offset Size Flags
nvs data nvs 0x9000 16k
otadata data ota 0xd000 8k
ota_0 app ota_0 1000k
ota_1 app ota_1 1000k

部分代码如下

1. OTA服务器初始化

首先初始化三个URI并加载到89端口上启动服务器,另以防意外将最大连接客户端数量设置为1(config.max_open_sockets = 1)

void HttpOTA_server_init()
{httpd_config_t config = HTTPD_DEFAULT_CONFIG();config.max_open_sockets = 1;config.stack_size = 8192;config.server_port = 89;config.ctrl_port = 32779;ESP_LOGI(TAG, "Starting OTA server on port: '%d'", config.server_port);if (httpd_start(&HttpOTA_httpd, &config) == ESP_OK){httpd_uri_t HttpOTA_uri = {         //OTA页面.uri = "/HttpOTAWeb",.method = HTTP_GET,.handler = HttpOTA_handler,.user_ctx = NULL};httpd_register_uri_handler(HttpOTA_httpd, &HttpOTA_uri);httpd_uri_t Now_uri = {         //当前固件信息.uri = "/Now",.method = HTTP_GET,.handler = Now_handler,.user_ctx = NULL};httpd_register_uri_handler(HttpOTA_httpd, &Now_uri);/* URI处理程序,用于将文件上传到服务器*/httpd_uri_t file_upload = {.uri       = "/upload",.method    = HTTP_POST,.handler   = upload_post_handler,.user_ctx  = NULL    };httpd_register_uri_handler(HttpOTA_httpd, &file_upload);}
}
2. 当前固件信息(以json字符串发送)
//当前固件信息
static esp_err_t Now_handler(httpd_req_t *req)
{httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");        //跨域传输协议static char json_response[1024];esp_app_desc_t running_app_info;const esp_partition_t *running = esp_ota_get_running_partition();esp_ota_get_partition_description(running, &running_app_info);char * p = json_response;*p++ = '{';p+=sprintf(p, "\"OTAsubtype\":%d,", running->subtype - ESP_PARTITION_SUBTYPE_APP_OTA_MIN);     //OTA分区p+=sprintf(p, "\"address\":%d,", running->address);               //地址p+=sprintf(p, "\"version\":\"%s\",", running_app_info.version);   //版本号p+=sprintf(p, "\"date\":\"%s\",", running_app_info.date);         //日期p+=sprintf(p, "\"time\":\"%s\"", running_app_info.time);          //时间*p++ = '}';*p++ = 0;httpd_resp_set_type(req, "application/json");       // 设置http响应类型return httpd_resp_send(req, json_response, strlen(json_response));      //发送一个完整的HTTP响应。内容在json_response中
}
3. 固件上传页面

使用Esp32HttpWeb_OTA\main\html\www\compress_pages.sh工具压缩为.gz网页烧写。
注意: html中的中文会被压缩成乱码。
注意: 需要在CMakeLfists.txt编译配置文件下注明该网页:

#嵌入二进制文件,该文件不会被格式化为c源文件,当前为压缩网页文件
set(COMPONENT_EMBED_FILES"html/www/HttpOTA.html.gz")

固件上传页面URI:

//OTA 页面
static esp_err_t HttpOTA_handler(httpd_req_t *req)
{httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");        //跨域传输协议extern const unsigned char HttpOTA_html_gz_start[] asm("_binary_HttpOTA_html_gz_start");extern const unsigned char HttpOTA_html_gz_end[] asm("_binary_HttpOTA_html_gz_end");size_t HttpOTA_html_gz_len = HttpOTA_html_gz_end - HttpOTA_html_gz_start;httpd_resp_set_type(req, "text/html");httpd_resp_set_hdr(req, "Content-Encoding", "gzip");return httpd_resp_send(req, (const char *)HttpOTA_html_gz_start, HttpOTA_html_gz_len);
}
4. 固件接收URI

循环每次接收1k数据到缓冲区。当接收到完整固件头部后( 接收包大于 sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) )
则判断固件是否合法(该固件仅判断了版本号是否为包含"ESP_"),可自行添加判断是否与正在运行固件版本号相同等,esp-idf\examples\system\ota\native_ota_example例程中包含相关代码。

/* 单个文件的最大大小*/
#define MAX_FILE_SIZE   (1000*1024)// 1000 KB
#define MAX_FILE_SIZE_STR "1000KB"
/* 暂存缓冲区大小*/
#define SCRATCH_BUFSIZE  1024
/*将文件上传到服务器的处理程序*/
uint8_t Upload_Timeout_num;
static esp_err_t upload_post_handler(httpd_req_t *req)
{httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");        //跨域传输协议esp_err_t err;esp_ota_handle_t update_handle = 0 ;const esp_partition_t *update_partition = NULL;char SendStr[100];Upload_Timeout_num = 0;/* 文件不能大于限制*/if (req->content_len > MAX_FILE_SIZE) {ESP_LOGE(TAG, "文件过大 : %d bytes", req->content_len);/* 回应400错误请求 */httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,"File size must be less than"MAX_FILE_SIZE_STR "!");/* 返回失败以关闭基础连接,否则传入的文件内容将使套接字繁忙 */return ESP_FAIL;}/*请求的内容长度给出了要上传的文件的大小*/int remaining = req->content_len;int received,L_remaining = remaining;bool image_header_was_checked = false;  //固件头检查标识char *OTA_buf = malloc(sizeof(char) * SCRATCH_BUFSIZE);while (remaining > 0) {// char str[6];// sprintf(str,"%.2f",(float)(L_remaining-remaining)/L_remaining*100);// ESP_LOGI(TAG, "剩余尺寸 : %d---%s%%", remaining,str);/* 将文件部分接收到缓冲区中 */if ((received = httpd_req_recv(req, OTA_buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) {if (received == HTTPD_SOCK_ERR_TIMEOUT) {Upload_Timeout_num++;ESP_LOGE(TAG, "接收文件超时 %d", Upload_Timeout_num);/* 如果发生超时,请重试 */if (Upload_Timeout_num >= 3){Upload_Timeout_num = 0;ESP_LOGE(TAG, "超时过多!");httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "File receiving timeout!");return ESP_FAIL;}continue;}/* 如果出现无法恢复的错误,请关闭并删除未完成的文件*/free(OTA_buf);ESP_LOGE(TAG, "文件接收失败!");/* 响应500内部服务器错误 */httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to receive file!");if(update_handle) esp_ota_end(update_handle);   //若已begin OTA则停止OTAreturn ESP_FAIL;}/*固件头校验*///接收到固件头if (image_header_was_checked == false) {esp_app_desc_t new_app_info;    //存储新固件头if (received > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {esp_app_desc_t running_app_info;const esp_partition_t *running = esp_ota_get_running_partition();if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK){ESP_LOGI(TAG, "当前运行固件版本: %s", running_app_info.version);ESP_LOGI(TAG, "当前运行固件编译时间: %s,%s", running_app_info.date, running_app_info.time);}// 通过下载检查新固件版本memcpy(&new_app_info, &OTA_buf[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));if (strstr(new_app_info.version, "ESP_") == NULL)  //版本错误{ESP_LOGE(TAG, "新固件头错误");httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Firmware header error!");return ESP_FAIL;}ESP_LOGI(TAG, "新固件版本: %s", new_app_info.version);ESP_LOGI(TAG, "新固件编译时间: %s, %s", new_app_info.date, new_app_info.time);

固件头校验完成后,配置新固件烧写目标分区并调用esp_ota_begin( )开始OTA,注意该步骤将擦除目标OTA分区。
注意: ESP32 OTA必须由esp_ota_begin( )开始并由esp_ota_end( )结束。

                //返回下一个应使用新固件写入的OTA应用程序分区
#if 1//esp_ota_get_next_update_partition 自动选择下一个可用ota分区update_partition = esp_ota_get_next_update_partition(NULL);ESP_LOGI(TAG, "写入分区子类型 %#X 偏移 %#x", update_partition->subtype, update_partition->address);if (update_partition == NULL){httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA Partition error!");return ESP_FAIL;}
#else//手动选择ota分区if (running->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0){update_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL);}else{update_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);}ESP_LOGI(TAG, "写入分区子类型 %#X 偏移 %#x", update_partition->subtype, update_partition->address);if (update_partition == NULL){httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA Partition error!");return ESP_FAIL;}
#endifsprintf(SendStr, "To: OTA%d Ver: %s Time: %s, %s", update_partition->subtype - ESP_PARTITION_SUBTYPE_APP_OTA_MIN, new_app_info.version, new_app_info.date, new_app_info.time);//开始OTA OTA_SIZE_UNKNOWN将擦除整个分区err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);if (err != ESP_OK) {char str[25];sprintf(str, "esp_ota_begin failed (%s)", esp_err_to_name(err));ESP_LOGE(TAG, "%s", str);httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, str);return ESP_FAIL;}ESP_LOGI(TAG, "esp_ota_begin succeeded");image_header_was_checked = true;    //固件头验证完成 可自行添加版本比对}else{httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "received package is not fit len!");return ESP_FAIL;}}

循环将每次接收到的数据写入目标OTA分区 esp_ota_write( );

        /*将固件分块写入OTA分区*/err = esp_ota_write(update_handle, (const void *)OTA_buf, received);if (err != ESP_OK) {char str[25];sprintf(str, "esp_ota_write failed (%s)", esp_err_to_name(err));ESP_LOGE(TAG, "%s", str);httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, str);return ESP_FAIL;}/*跟踪剩余要上传的文件的剩余大小*/remaining -= received;}free(OTA_buf);ESP_LOGI(TAG, "文件接收完成: %dByte",L_remaining);

整个固件接收完成后调用esp_ota_end( )结束OTA并自动校验固件完整性。

    err = esp_ota_end(update_handle);if (err != ESP_OK) {if (err == ESP_ERR_OTA_VALIDATE_FAILED) {ESP_LOGE(TAG, "Image validation failed, image is corrupted");}char str[25];sprintf(str, "esp_ota_end failed (%s)", esp_err_to_name(err));ESP_LOGE(TAG, "%s", str);httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, str);return ESP_FAIL;}ESP_LOGI(TAG, "固件校验成功!");

最后调用esp_ota_set_boot_partition( )将otadata配置为新OTA分区(下次上电启动分区)
并软件复位 esp_restart();
注意: httpd_resp_sendstr后若无延时,客户端网页将接受不到成功回复。

    err = esp_ota_set_boot_partition(update_partition);if (err != ESP_OK) {char str[50];sprintf(str, "esp_ota_set_boot_partition failed (%s)", esp_err_to_name(err));ESP_LOGE(TAG, "%s", str);httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, str);return ESP_FAIL;}// httpd_resp_sendstr(req, "OTA successfully");httpd_resp_sendstr(req,SendStr);vTaskDelay(500 / portTICK_PERIOD_MS);   //延时等待消息发送ESP_LOGI(TAG, "准备重启系统!");esp_restart();return ESP_OK;
}

固件上传网页

ESP32 HttpServer模式下 本地OTA 例程(基于ESP-IDF类似Arduino下OTAWebUpdater例程)相关推荐

  1. element做树形下拉_一个基于 elementUi的vue树形下拉框组件

    # wl-vue-select,wl-tree-selectcss # 简介vue 用于vue框架的树形下拉框及带全选的普通下拉框.   node Tree drop-down box for vue ...

  2. 本地lamp虚拟服务器,LAMP环境下虚拟主机配置(基于域名)

    在之前LAMP环境下虚拟主机配置(基于IP) 继续做这个基于域名的虚拟主机配置,只需要修改之前的配置内容即可. 一.首先修改一下apache的配置文件 首先修改一下配置文件中的#NameVirtual ...

  3. 图文手把手教程--ESP32 OTA空中升级(VSCODE+IDF)

    本文内容 1)使用hello_world例程,编译生成hello_world.bin文件,并且开启HTTP本地服务器. 2)使用simple_ota_example例程,通过HTTP服务器访问hell ...

  4. ESP32 深度睡眠模式功耗测试

    ESP32 深度睡眠模式功耗测试 ESP32 拥有 18 个 RTC IO 和 10 个 TouchPad, 每一个 RTC IO 和 TouchPad 经过配置都可以将芯片从 deep_sleep ...

  5. Windows下本地安装git客户端

    转载自: < Windows下本地git服务器端安装图文教程 > 关于git 分布式:Git版本控制系统是一个分布式的系统,是用来保存工程源代码历史状态的命令行工具.     分支即时性: ...

  6. 了解ESP32睡眠模式及其功耗

    陈拓翻译 2022/05/30-2022/05/30 原文 https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ 毫 ...

  7. electron打包用http-server创建的本地服务器

    一直觉得前端想搭建本地服务器使用http-server是最方便地,不用进行环境配置啥的,在学习electron的过程中,尝试打包静态文件,但是由于涉及到跨域的问题,所以需要搭建一个本地服务器.但是虽然 ...

  8. Hadoop 的三种运行模式_本地模式_伪分布式模式

    演示的版本是:2.7.2 官方文档 Hadoop运行模式 Hadoop运行模式包括:本地模式.伪分布式模式以及完全分布式模式. Hadoop官方网站:http://hadoop.apache.org/ ...

  9. 小样本学习下的Transformer:基于谱聚类层和标签代理学习

    ©作者 | 知乎用户Alicia 研究方向 | 小样本学习 论文标题: Attribute Surrogates Learning and Spectral Tokens Pooling in Tra ...

最新文章

  1. org.hibernate.hql.ast.QuerySyntaxException: ? is not mapped
  2. java 正则表达式提取价格
  3. NSIS 的 Modern UI 教程(二)
  4. 除醛重要性美博士环保为您解答!!
  5. 泰山200机架服务器包含哪些型号_数据中心机房建设中的关键问题都有哪些?
  6. 学习人工智能不走捷径,走大道的方式
  7. 2018最新版硬盘装系统,不要U盘也能装
  8. 网关为0.0.0.0_距离ETH 2.0仅7天,目标价为?美元
  9. 图像压缩算法python_Python基于opencv的图像压缩算法实例分析
  10. WIN10任务栏隐藏图标的合并及展开问题
  11. 系统与漏洞的风云人物
  12. nodejs 安装模块失败 解决方法
  13. CXF 处理yyyy-MM-dd HH:mm:ss日期失败
  14. 论文写作 1: 学术论文的基本概念
  15. 【知识图谱】构建《射雕三部曲》图谱(CSV文件导入)
  16. bzoj2754 scoi2012 喵星球的点名
  17. 阿里云服务器远程连接
  18. 基于c语言矩阵数组透视变换,一种图像透视变换方法与流程
  19. 苹果天气应用专利获批,苹果Find My技术改变防丢技术走向
  20. 在使用Windows 10时,正常开机后Duilib加载资源文件失败

热门文章

  1. 统计建模与R软件-附R原程序
  2. 计算机用老毛桃u盘备份系统,如何使用老毛桃winpe系统进行Ghost备份
  3. UE4地形简单材质球制作,及地形变黑处理办法
  4. 技嘉显卡测试用什么软件,独家揭秘评测微星和技嘉显卡区别是?哪个好?口碑反馈揭秘...
  5. 人工智能泡妞第一步 · 了解学科
  6. 湖南省智慧教育装备展示体验中心关于暂停研学、暑假社会实践等活动通知
  7. 中秋放假最新提醒,这些地方恢复跨省旅游,你选好去哪儿旅游了吗?
  8. Ubuntu之Sailfish OS开发环境搭建
  9. 发现个下载Sailfish OS源码的地方
  10. iQOO Z6和iQOOZ6x的区别 选哪个好