#include "webinterface.h" #include #include #include #include #include #include #include "parameters.h" #include "romfs.h" #include "check_firmware.h" #include "status.h" static WebServer server(80); // 80 是 HTTP 协议的默认端口 static bool front_16byte_flag = false; // 调试观察前16个字节使用 /* serve files from ROMFS ROMFS_Handler 是 Web 服务器的 “静态资源处理器”:拦截所有 Web 请求, 将 URI 映射到 ROMFS 中的 web/ 目录下的文件(如 / → web/index.html), 判断文件是否存在并匹配正确的 Content-Type,最终把 ROMFS 中的文件流式返回给浏览器, 是 Web 升级页面能正常加载的关键 */ class ROMFS_Handler : public RequestHandler { bool canHandle(HTTPMethod method, String uri) { if (uri == "/") { uri = "/index.html"; } uri = "web" + uri; if (ROMFS::exists(uri.c_str())) { return true; } return false; } bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) { if (requestUri == "/") { requestUri = "/index.html"; } String uri = "web" + requestUri; Serial.printf("handle: '%s'\n", requestUri.c_str()); // work out content type const char *content_type = "text/html"; const struct { const char *extension; const char *content_type; } extensions[] = { { ".js", "text/javascript" }, { ".jpg", "image/jpeg" }, { ".png", "image/png" }, { ".css", "text/css" }, }; for (const auto &e : extensions) { if (uri.endsWith(e.extension)) { content_type = e.content_type; break; } } auto *f = ROMFS::find_stream(uri.c_str()); if (f != nullptr) { server.sendHeader("Content-Encoding", "gzip"); server.streamFile(*f, content_type); delete f; return true; } return false; } } ROMFS_Handler; /* serve files from ROMFS */ class AJAX_Handler : public RequestHandler { bool canHandle(HTTPMethod method, String uri) { return uri == "/ajax/status.json"; } bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) { if (requestUri != "/ajax/status.json") { return false; } server.send(200, "application/json", status_json()); return true; } } AJAX_Handler; /* init web server 该函数是 Web 升级的 “总开关”:启动 WiFi AP 并初始化 Web 服务器, 注册 AJAX/ROMFS 处理器支撑页面显示,绑定 /update 接口处理固件上传, 上传过程中缓存固件头部、写入 OTA 分区,最后通过你之前关注的 check_OTA_next() 校验固件合法性, 合法则升级重启,非法则返回失败,是 Web 升级从 “页面访问” 到 “固件生效” 的全流程入口 */ void WebInterface::init(void) { Serial.printf("WAP start %s %s\n", g.wifi_ssid, g.wifi_password); IPAddress myIP = WiFi.softAPIP(); // 获取 AP 模式默认 IP(192.168.4.1) server.addHandler( &AJAX_Handler ); // 处理 AJAX 异步请求(如获取模块状态) server.addHandler( &ROMFS_Handler ); // 处理静态页面(HTML/CSS/JS) /*handling uploading firmware file */ server.on("/update", HTTP_POST, []() { if (Update.hasError()) { server.sendHeader("Connection", "close"); server.send(500, "text/plain","FAIL"); Serial.printf("Update Failed: Update function has errors\n"); delay(5000); } else { server.sendHeader("Connection", "close"); server.send(200, "text/plain","OK"); Serial.printf("Update Success: \nRebooting...\n"); delay(1000); ESP.restart(); } }, [this]() { // 回调 2:上传过程中处理固件数据(核心中的核心) HTTPUpload& upload = server.upload(); // 获取待写入的备用 OTA 分区(OTA0/OTA1 中非当前运行的分区) static const esp_partition_t* partition_new_firmware = esp_ota_get_next_update_partition(NULL); //get OTA partion to which we will write new firmware file; if (upload.status == UPLOAD_FILE_START) { // 阶段1:开始上传(UPLOAD_FILE_START) Serial.printf("Update: %s\n", upload.filename.c_str()); lead_len = 0; // 初始化固件头部数据长度 // 启动固件更新,UPDATE_SIZE_UNKNOWN 表示固件大小未知 擦除备份区 if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { // 阶段2:上传中写入数据(UPLOAD_FILE_WRITE) /* flashing firmware to ESP*/ /* 就是专门用 16 字节的 “小盒子” 只装固件最开头的 16 个字节(固件的 “身份证”), 同时不管这个小盒子装没装满,收到的每一段固件数据都会完整写入备用 OTA 分区(备份区); 等小盒子装满 16 字节后,就只专心往备份区写固件,不再往小盒子存数据了 */ if (lead_len < sizeof(lead_bytes)) { /* 第一步:缓存固件头部数据(lead_bytes) */ uint32_t n = sizeof(lead_bytes)-lead_len; if (n > upload.currentSize) { n = upload.currentSize; } memcpy(&lead_bytes[lead_len], upload.buf, n); // 拷贝到 lead_bytes 缓存 lead_len += n; if(lead_len >= 16 && !front_16byte_flag ) { front_16byte_flag = true; Serial.printf("front 16 byte is %d %d %d %d ", lead_bytes[0], lead_bytes[1],lead_bytes[2],lead_bytes[3]); // 打印当前头16个字节 Serial.printf("%d %d %d %d ", lead_bytes[4], lead_bytes[5],lead_bytes[6],lead_bytes[7]); // 打印当前头16个字节 Serial.printf("%d %d %d %d ", lead_bytes[8], lead_bytes[9],lead_bytes[10],lead_bytes[11]); // 打印当前头16个字节 Serial.printf("%d %d %d %d end\n", lead_bytes[12], lead_bytes[13],lead_bytes[14],lead_bytes[15]); // 打印当前头16个字节 } } /* 第二步:将固件数据写入 OTA 分区 */ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); // 写入失败打印错误 } } else if (upload.status == UPLOAD_FILE_END) { // 阶段3:上传完成(UPLOAD_FILE_END) // write extra bytes to force flush of the buffer before we check signature uint32_t extra = SPI_FLASH_SEC_SIZE+1; while (extra--) { uint8_t ff = 0xff; Update.write(&ff, 1); // 写入额外的 0xFF 字节,强制刷新 Flash 缓冲区(保证数据写入完成) } // 核心校验:调用 check_OTA_next() 验证固件合法性 必须签名后才能校验成功 并找到对应的应用描述符 if (!CheckFirmware::check_OTA_next(partition_new_firmware, lead_bytes, lead_len)) { Serial.printf("Update Failed: firmware checks have errors\n"); server.sendHeader("Connection", "close"); server.send(500, "text/plain","FAIL"); delay(5000); } else if (Update.end(true)) { // 校验通过,完成更新 Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); server.sendHeader("Connection", "close"); server.send(200, "text/plain","OK"); } else { // Update.end() 执行失败 Update.printError(Serial); Serial.printf("Update Failed: Update.end function has errors\n"); server.sendHeader("Connection", "close"); server.send(500, "text/plain","FAIL"); delay(5000); } } }); Serial.printf("WAP started\n"); server.begin(); } /* 该函数是 Web 服务的 “心跳循环”:通过 initialised 标志实现 init() 只执行一次(避免重复初始化), 然后在主循环中反复调用 server.handleClient(),让 ESP32 持续监听并处理 80 端口的 Web 请求, 确保用户访问升级页面、上传固件时能实时响应,是 Web 升级功能 “持续可用” 的基础 */ void WebInterface::update() { if (!initialised) { init(); initialised = true; } server.handleClient(); }