基于ESP32 IDF的WebServer实现以及OTA固件升级实现记录(三)

news2025/1/30 2:45:49

          经过前面两篇的前序铺垫,对webserver以及restful api架构有了大体了解后本篇描述下最终的ota实现的代码以及调试中遇到的诡异bug。

         eps32的实际ota实现过程其实esp32官方都已经基本实现好了,我们要做到无非就是把要升级的固件搬运到对应ota flash分区里面,相对来说esp32 ota已经很完善了。esp32的ota相关接口可参见官网的相关ota文档。本文主要讲解基于http post方式的本地webserver的ota实现。

        1、网页端web实现,主要完成固件选择以及上传post功能,该部分可以参考开源web,如esp32-vue3demo/vue-project/src/views/Upgrade.vue at main · lbuque/esp32-vue3demo · GitHub

<template>
  <form method='POST' action='/api/v1/update' enctype='multipart/form-data'>
    <input type='file' name='update'>
    <input type='submit' value='Update'>
  </form>
</template>

<script lang="ts" setup >

</script>

<style scoped lang="scss"></style>

如上代码的效果如下,主要是有一个文件选择框可以选择要升级的固件,点击update后即会向web后台url:/api/v1/updata进行post请求传输要升级的固件给运行webserver的后台即esp32,该部分即完成了待升级固件的网络传输。

        2、esp32 webserver的post文件请求接收以及实际的ota flash写入。

           ota过程需要先找到要写入的ota分区,然后接收文件后按esp32的ota api写入ota分区即可,文件传输完成后即可启动esp32进行固件升级。升级代码如下:


    /* URI handler for light brightness control */
    httpd_uri_t light_brightness_post_uri = {
        .uri = "/api/v1/update",
        .method = HTTP_POST,
        .handler = light_brightness_post_handler,
        .user_ctx = rest_context
    };
    httpd_register_uri_handler(server, &light_brightness_post_uri);

注册一个用于接收post文件的rest api,回调名懒得改,实际项目中按编码要求进行修改。

static esp_err_t light_brightness_post_handler(httpd_req_t *req)
{
    
    esp_err_t err;
    /* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */
    esp_ota_handle_t update_handle = 0 ;
    const esp_partition_t *update_partition = NULL;

    char filepath[FILE_PATH_MAX];
    ESP_LOGI(REST_TAG, "light_brightness_post_handler");
    /* Skip leading "/upload" from URI to get filename */
    /* Note sizeof() counts NULL termination hence the -1 */
    const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
                                             req->uri + sizeof("/upload") - 1, sizeof(filepath));
   
    ESP_LOGI(REST_TAG, "Receiving file : %s...", filename);

    /* Retrieve the pointer to scratch buffer for temporary storage */
    char *buf = ((struct file_server_data *)req->user_ctx)->scratch;
    int received;

    /* Content length of the request gives
     * the size of the file being uploaded */
    int remaining = req->content_len;

    
    while (remaining > 0) {

        if(remaining == req->content_len)
        {
            update_partition = esp_ota_get_next_update_partition(NULL);
            assert(update_partition != NULL);
            ESP_LOGI(REST_TAG, "Writing to partition subtype %d at offset 0x%"PRIx32,update_partition->subtype, update_partition->address);
            err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
            if (err != ESP_OK) {
                ESP_LOGE(REST_TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
                esp_ota_abort(update_handle);
            }
            ESP_LOGI(REST_TAG, "esp_ota_begin succeeded");
        }

        ESP_LOGI(REST_TAG, "Remaining size : %d", remaining);
        /* Receive the file part by part into a buffer */
        if ((received = httpd_req_recv(req, buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) {
            if (received == HTTPD_SOCK_ERR_TIMEOUT) {
                /* Retry if timeout occurred */
                continue;
            }

            /* In case of unrecoverable error,
             * close and delete the unfinished file*/
            

            ESP_LOGE(REST_TAG, "File reception failed!");
            /* Respond with 500 Internal Server Error */
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file");
            return ESP_FAIL;
        }

        err = esp_ota_write( update_handle, buf, received);
        if (err != ESP_OK) {
            esp_ota_abort(update_handle);
        }

        /* Keep track of remaining size of
         * the file left to be uploaded */
        remaining -= received;
         ESP_LOGI(REST_TAG, " remaining = %d, received = %d", remaining, received);
    }

    /* Close file upon upload completion */
   
    ESP_LOGI(REST_TAG, "File reception complete");

    /* Redirect onto root to see the updated file list */
    httpd_resp_set_status(req, "303 See Other");
    httpd_resp_set_hdr(req, "Location", "/");
#ifdef CONFIG_EXAMPLE_HTTPD_CONN_CLOSE_HEADER
    httpd_resp_set_hdr(req, "Connection", "close");
#endif
    httpd_resp_sendstr(req, "File uploaded successfully");


    ESP_LOGI(REST_TAG, "======endota======");
    err = esp_ota_end(update_handle);
    if (err != ESP_OK) {
        if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
            ESP_LOGE(REST_TAG, "Image validation failed, image is corrupted");
        }
        ESP_LOGE(REST_TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
    }
    err = esp_ota_set_boot_partition(update_partition);
    if (err != ESP_OK) {
        ESP_LOGE(REST_TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));

    }
    ESP_LOGI(REST_TAG, "Prepare to restart system!");
    esp_restart();

    return ESP_OK;

    
}

        大体代码的含义就是接收post请求后持续读取文件知道传输完成,同时传输过程中写ota分区的flash。

        实际按如上代码进行build后即可进行ota升级。运行起来一切都ok,不过没有意外下还是出现了意外,web页面选择固件进行升级后esp32端接收到的bin文件总是格式不对,出现了如下错误,

错误提示传输的文件不是bin文件格式,因此怀疑是post传输问题,通过web的开发者工具查看post的文件大小果真和实际文件大小不一致,

此时还没法确定是esp32嵌入式端代码问题还是web端前端代码问题,后面找了curl工具,

直接用命令行接口进行post试了后可以正常升级,post过来的大小也是实际的文件大小。至此基本可以确认是前端web页面 post的时候自己传输头等有修改了content-length导致其比实际的bin文件更大导致esp32接收的bin文件错误。

经过后面修改发现只有用原始的xmlrequest才不会像html或者axios那样会导致传输的content-length会比实际的bin文件大从而导致ota固件升级失败的问题。

改善后的web端的post关键代码如下:

 upload() {
      // your upload logic here using XMLHttpRequest
      const uploadPath = "/upload/" + this.filePath;
      const fileInput = document.getElementById("newfile").files;
      
      // add your upload logic using XMLHttpRequest here
      // be sure to use "this.filePath" and "fileInput" from Vue data

      // Example
      const file = fileInput[0];
      const xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
        if (xhttp.readyState == 4) {
          if (xhttp.status == 200) {
            document.open();
            document.write(xhttp.responseText);
            document.close();
          } else if (xhttp.status == 0) {
            alert("Server closed the connection abruptly!");
            location.reload();
          } else {
            alert(xhttp.status + " Error!\n" + xhttp.responseText);
            location.reload();
          }
        }
      };
      xhttp.open("POST", "/api/v1/update", true);
      xhttp.send(file);
    }

webserver ota传输升级没问题的log截图

esp32 的webserver ota的源代码放github上,有需要的自行取用,iot-lorawan (iot-lorawan) · GitHub

关于web端为何用xtmlrequest进行post的时候content-length才能是正确的bin文件大小,而axios或者html自带的post都会导致该字段长度偏大的目前原因还不得而知。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1888626.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

应用密码学—(扩展)欧几里得、DES、RSA、SHA-1算法

1. 欧几里得算法 1.1 分析算法的实现原理 欧几里德&#xff08;Euclid&#xff09;算法&#xff0c;也既常说的“辗转相除法”&#xff0c;公式为gcd(m, n) { return gcd(n, m%n); }&#xff0c;对于任意两个正整数m、n&#xff0c;每次求的一个数字r m % n&#xff0c;然后把…

气膜馆一小时电费有多高—轻空间

气膜馆因其独特的设计和功能性&#xff0c;广泛应用于体育场馆、展览馆和临时建筑等多个领域。除了其便捷的搭建和拆卸外&#xff0c;运营成本&#xff0c;尤其是电费&#xff0c;成为了许多关注气膜馆用户的重点。轻空间将详细分析气膜馆一小时的电费构成&#xff0c;并探讨其…

跨境电商自养号全攻略:TEMU、Shein、速卖通测评技巧揭秘

TEMU、Shein、速卖通等跨境平台都推出了全托管模式&#xff0c;普通平台讲究排名&#xff0c;销量&#xff0c;流量量&#xff0c;转化率等等。那么全托管为什么需要做测评呢&#xff1f;因为全托管平台讲究的是一个动销率&#xff0c;有的新品上架或许很快就出单&#xff0c;而…

【每日一练】Python遍历循环

1. 情节描述&#xff1a;上公交车(10个座位)&#xff0c;并且有座位就可以坐下 要求&#xff1a;输入公交卡当前的余额&#xff0c;只要超过2元&#xff0c;就可以上公交车&#xff1b;如果车上有空座位&#xff0c;才可以上。 seat 10 while seat > 0:money int(input(…

2024 年如何构建 AI 软件

人工智能 (AI) 是当今 IT 行业最热门的话题&#xff0c;受到大型科技公司、大型企业和投资者的青睐。如果有人不参与 AI&#xff0c;他们就出局了。虽然“AI 泡沫”一词尚未公开使用&#xff0c;但街上的每个人都可能听说过 AI 将取代我们的工作&#xff08;可能不会&#xff0…

文献解读-长读长测序-第十四期|《作为了解棉花驯化的资源,印度棉(Gossypium herbaceum L. Wagad)基因组》

关键词&#xff1a;基因组&#xff1b;长读长测序&#xff1b;棉花基因组&#xff1b; 文献简介 标题&#xff08;英文&#xff09;&#xff1a;The Gossypium herbaceum L. Wagad genome as a resource for understanding cotton domestication标题&#xff08;中文&#xff…

【论文解读】iSDF: Real-Time Neural Signed Distance Fieldsfor Robot Perception

《iSDF: Real-Time Neural Signed Distance Fields for Robot Perception》提出了一种用于实时签名距离场&#xff08;SDF&#xff09;重建的持续学习系统。 论文&#xff1a;https://arxiv.org/abs/2204.02296https://arxiv.org/abs/2204.02296 项目&#xff1a;iSDFhttps:/…

手撕LLM,弄懂这些,你大模型就算入门了

在人工智能的浩瀚星空中&#xff0c;大型语言模型&#xff08;Large Language Model, LLM&#xff09;无疑是近年来最为耀眼的星辰之一。它们以惊人的文本生成能力、上下文理解能力以及广泛的应用潜力&#xff0c;正逐步改变着我们的生活方式和工作模式。对于想要踏入这一领域的…

Mac密室逃脱游戏推荐:Escape Simulator for mac安装包

Escape Simulator 是一款逃生模拟游戏&#xff0c;玩家在游戏中需要寻找线索、解决谜题&#xff0c;以逃离各种房间或环境。这种类型的游戏通常设计有多个关卡或场景&#xff0c;每个场景都有不同的设计和难度。 在 Escape Simulator 中&#xff0c;玩家的目标通常是找到出口或…

用MySQL+node+vue做一个学生信息管理系统(一):配置项目

先用npm init -y生成配置文件 在项目下新建src文件夹&#xff0c;app.js文件。src目录用来放静态资源文件&#xff0c;app.js是服务器文件&#xff0c;index.js是vue的入口文件 使用npm install express下载express框架 在app.js文件夹开启node服务&#xff0c;监听的端口为…

开放式耳机排行榜10强!最强开放式耳机大揭秘!

在开放式耳机的市场中&#xff0c;各大品牌竞相推出了一系列优秀的产品。这些耳机不仅具备高品质的音质表现&#xff0c;还融入了各种黑科技&#xff0c;如智能降噪、无线充电等&#xff0c;带来更加便捷、智能的体验。作为一名开放式耳机收藏家&#xff0c;目前也入手了差不多…

java入门-基础语法(运算符)

运算符是对变量、字面量进行运算的 符号 &#xff08;一&#xff09;基本的算术运算符、符号做连接符 &#xff08;1&#xff09;基本运算符&#xff1a;(加)&#xff0c; - &#xff08;减&#xff09;、 * &#xff08;乘&#xff09;、 / &#xff08;除&#xff09;、%&…

仿论坛项目--初识Spring Boot

1. 技术准备 技术架构 • Spring Boot • Spring、Spring MVC、MyBatis • Redis、Kafka、Elasticsearch • Spring Security、Spring Actuator 开发环境 • 构建工具&#xff1a;Apache Maven • 集成开发工具&#xff1a;IntelliJ IDEA • 数据库&#xff1a;MySQL、Redi…

Monorepo(单体仓库)与 MultiRepo(多仓库): Monorepo 单体仓库开发策略与实践指南

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言1. Monorepo 和 MultiRepo 简介2. 为什么选择 Monorepo&#xff1f; 二、Monorepo 和 MultiRepo 的区别1. 定义和概述2. 各自的优点和缺点3. 适用场景 三、Monorepo 的开发策略1. 版本控制2. 依赖管理3. 构建和发布…

模拟算法系列|替换所有的问号|提莫攻击|种花问题|Z字形变换|兼具大小写的英文字母|删除字符使频率相同

大家好,我是LvZi,今天带来模拟算法系列|替换所有的问号|提莫攻击|种花问题|Z字形变换|兼具大小写的英文字母|删除字符使频率相同 一.基本概念 模拟算法就是根据题意 模拟出代码的过程,模拟算法的题意往往都很简单,考验的是将思路转化为代码的能力,十分的锻炼代码能力,且能很好…

大模型学习笔记1【大模型】

文章目录 学习内容0.大模型应用的流程1.构建任务/领域的数据集2.寻找备选模型3.调整模型PromptFine-tuningPEFT RLHF 学习内容 根据自己的经验和课程的学习&#xff0c;系统的记录一下大模型落地的流程。 0.大模型应用的流程 构建任务/领域问题数据集使用对应任务的语料测试…

“党建链串起产业链“ —— 亦企港携手企业共赴天空卫士探索数据安全新篇章

在数字化浪潮的推动下&#xff0c;数据安全已成为国家发展的关键。北京经济技术开发区&#xff08;简称北京经开区&#xff09;通过创新的“党建链串起产业链”活动&#xff0c;不断探索党建工作与产业发展的双向促进模式&#xff0c;为企业提供政策支持和资源共享&#xff0c;…

【课程设计】基于python的一款简单的计算器

我们是大二本科生团队&#xff0c;主力两人耗时3天完成了这款计算器的制作。希望大家给我们多多引流&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 欢迎各位优秀的高考学子报考长安大学&#xff0c;报考长安大学电子信息工程专业。 欢迎有志于就…

手机数据恢复篇:如何从损坏的iPhone恢复数据

不知道如何在没有备份的情况下从损坏的iPhone恢复数据&#xff1f;阅读本文&#xff0c;您可以获得从损坏的iPhone中提取数据的详细步骤。 可能很多苹果用户都经历过上述场景带来的痛苦。意外事件经常发生&#xff0c;例如 iPhone 被液体损坏并从高处掉落。面对无响应的屏幕&a…

3DMAX选择相似对象插件使用方法

3DMAX选择相似对象插件使用教程 3DMAX选择相似对象插件&#xff0c;允许你选择与当前选定对象相似的对象。它将比较当前可见对象或场景中所有对象内的边界框大小、网格&#xff08;顶点、面、边数&#xff09;和材质。 【版本要求】 3dMax7及更高版本&#xff08;建议使用3dMa…