ESP32 嵌入式Web服务器实现与配置指南 在做物联网设备开发时,参数配置一直是个痛点。传统的串口指令配置需要用户连接电脑并安装驱动,体验较差;而屏幕配合按键的交互虽然直观,但对于不需要频繁操作的设备来说,增加了BOM成本和开发复杂度。
最优雅的方案莫过于让ESP32自己跑一个Web服务器。用户只需连上设备的Wi-Fi热点,打开浏览器就能看到配置界面。这篇文档记录了我目前项目中使用的方案:基于 esp_http_server组件实现的一个参数配置页面。
一、方案设计思路 整个交互流程非常简单直观,本质上就是典型的B/S架构(Browser/Server),只是Server跑在了一颗MCU上。
AP模式启动 :设备上电后开启Wi-Fi热点(SoftAP),供手机或电脑连接。
DNS劫持(可选)或固定IP :用户连接热点后,通过浏览器访问网关IP(如 192.168.4.1)。
GET请求 :浏览器请求根路径 /,ESP32读取NVS中保存的当前配置,动态生成带有默认值的HTML表单页面返回给浏览器。
POST请求 :用户修改表单并点击提交,数据以POST请求发送到 /submit。ESP32解析数据,写入NVS掉电保存,并自动重启生效。
这套方案完全不需要依赖外网,也不需要APP,全平台通用。
二、核心代码实现 代码主要集中在 http_server.c中,使用了ESP-IDF自带的 esp_http_server组件。这个组件非常轻量,基于FreeRTOS任务运行。
1. 启动Web服务器 首先需要配置并启动HTTP Server任务。这里我针对RAM和并发连接做了一些调整,特别是加大了栈空间(stack_size),因为生成的HTML字符串可能会比较大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void start_webserver (void ) { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.uri_match_fn = httpd_uri_match_wildcard; config.stack_size = 8192 ; config.max_open_sockets = 7 ; config.lru_purge_enable = true ; httpd_handle_t server = NULL ; if (httpd_start(&server, &config) == ESP_OK) { httpd_register_uri_handler(server, &hello); httpd_register_uri_handler(server, &submit); ESP_LOGI(TAG, "Web server started successfully" ); } }
2. 动态生成配置页面 (GET Handler) 这是最关键的一步。我们不能只是返回一个静态的HTML文件,因为我们需要把设备当前的配置 回显在网页上。
我的做法是:
先定义一个包含 %s、%d占位符的HTML模板字符串。
从各个模块(WiFi、MQTT、Modbus等)获取当前参数。
使用 snprintf将参数填入模板,生成最终的HTML。
注意:考虑到C语言处理长字符串比较麻烦,我采用了直接在该handler中拼接的方式。虽然不仅不够优雅,也占用了栈空间,但对于这种一次性的配置页面,简单有效就是最好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 esp_err_t hello_get_handler (httpd_req_t *req) { char ssid[16 ] = {0 }; snprintf (ssid, 16 , "%s" , get_wifi_ssid()); const char *resp_template = "<!DOCTYPE html><html><body>" "<h1>GOATS 网络配置</h1>" "<form action=\"/submit\" method=\"post\">" "<fieldset><legend>网络配置</legend>" "SSID: <input type=\"text\" name=\"ssid\" value=\"%s\"><br>" "Password: <input type=\"password\" name=\"password\" value=\"%s\"><br>" "</fieldset>" "<input type=\"submit\" value=\"保存并重启\">" "</form>" "</body></html>" ; int resp_len = snprintf (NULL , 0 , resp_template, ssid, password, ...); char *resp_str = malloc (resp_len + 1 ); if (resp_str) { snprintf (resp_str, resp_len + 1 , resp_template, ssid, password, ...); httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN); free (resp_str); } return ESP_OK; }
3. 处理表单提交 (POST Handler) 当用户点击“保存并重启”时,浏览器会发送一个POST请求,Body里包含类似 ssid=mywifi&password=123456...的字符串。我们需要解析这个字符串并保存到NVS。
这里我用了一个比较“暴力”但好用的方法:sscanf。在此场景下,我们对自己生成的表单结构非常清楚,所以直接按顺序解析即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 esp_err_t submit_post_handler (httpd_req_t *req) { char buf[512 ]; int ret, remaining = req->content_len; if ((ret = httpd_req_recv(req, buf, MIN(remaining, sizeof (buf)))) <= 0 ) { if (ret == HTTPD_SOCK_ERR_TIMEOUT) { httpd_resp_send_408(req); } return ESP_FAIL; } printf ("post raw data: %s\n" , buf); sscanf (buf, "ID=%ld&ssid=%15[^&]&password=%15[^&]..." , &hw_id, ssid, password ... ); save_integer_to_nvs("id" , hw_id); save_string_to_nvs("w_s" , ssid); save_string_to_nvs("w_p" , password); const char *resp_str = "<h1>保存成功! 五秒后将重启...</h1>" ; httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN); printf ("ESP32 will restart in 5 seconds...\n" ); vTaskDelay(5000 / portTICK_PERIOD_MS); esp_restart(); return ESP_OK; }
三、前端页面的CSS美化 虽然是嵌入式设备,但页面也不能太丑。我在HTML头部内嵌了一些CSS样式。为了避免C代码中字符串拼接太乱,这里最好在开发时先写好标准的HTML/CSS文件(即工程中的 index.html),调试满意后再转换成C语言字符串。
目前使用了简单的 fieldset和 legend做分组,配合Flex布局让标签和输入框对齐:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fieldset { background-color : #0e7a6ca0 ; border-radius : 20px ; margin-bottom : 20px ; } .form-group { display : flex; align-items : center; width : 400px ; } label { flex : 0 0 200px ; text-align : right; margin-right : 10px ; }
四、调试心得与注意点
栈溢出保护 :HTTP Server任务默认栈可能不够用,特别是当表单项越来越多,HTML字符串变得很大时。务必在 httpd_config_t中调大 stack_size。
URL编码 :浏览器提交的POST数据中,特殊字符(如空格、#、%等)会被URL编码(例如空格变 %20)。在简单的 sscanf解析中,如果SSID或密码包含特殊字符,可能会解析错误。完善的方案应该加入URL Decode步骤,但作为内部配置工具,可以暂时限制用户输入标准字符。
NVS 键名限制 :NVS的Key长度最长只能15个字符。可以看到代码中我用了简写如 w_s (wifi ssid)、m_ip (mqtt ip)等,既省空间又避免超长。
内存泄漏 :在 hello_get_handler中使用了 malloc分配HTML缓冲区,一定要确保 free被执行,否则几次刷新页面后系统就会崩溃。
通过这套机制,ESP32就具备了一个独立、跨平台的配置后台,极大地提升了产品的易用性。