| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- #include "webinterface.h"
- #include <WiFi.h>
- #include <WiFiClient.h>
- #include <WebServer.h>
- #include <WiFiAP.h>
- #include <ESPmDNS.h>
- #include <Update.h>
- #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();
- }
|