webinterface.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #include "webinterface.h"
  2. #include <WiFi.h>
  3. #include <WiFiClient.h>
  4. #include <WebServer.h>
  5. #include <WiFiAP.h>
  6. #include <ESPmDNS.h>
  7. #include <Update.h>
  8. #include "parameters.h"
  9. #include "romfs.h"
  10. #include "check_firmware.h"
  11. #include "status.h"
  12. static WebServer server(80); // 80 是 HTTP 协议的默认端口
  13. static bool front_16byte_flag = false; // 调试观察前16个字节使用
  14. /*
  15. serve files from ROMFS
  16. ROMFS_Handler 是 Web 服务器的 “静态资源处理器”:拦截所有 Web 请求,
  17. 将 URI 映射到 ROMFS 中的 web/ 目录下的文件(如 / → web/index.html),
  18. 判断文件是否存在并匹配正确的 Content-Type,最终把 ROMFS 中的文件流式返回给浏览器,
  19. 是 Web 升级页面能正常加载的关键
  20. */
  21. class ROMFS_Handler : public RequestHandler
  22. {
  23. bool canHandle(HTTPMethod method, String uri) {
  24. if (uri == "/") {
  25. uri = "/index.html";
  26. }
  27. uri = "web" + uri;
  28. if (ROMFS::exists(uri.c_str())) {
  29. return true;
  30. }
  31. return false;
  32. }
  33. bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) {
  34. if (requestUri == "/") {
  35. requestUri = "/index.html";
  36. }
  37. String uri = "web" + requestUri;
  38. Serial.printf("handle: '%s'\n", requestUri.c_str());
  39. // work out content type
  40. const char *content_type = "text/html";
  41. const struct {
  42. const char *extension;
  43. const char *content_type;
  44. } extensions[] = {
  45. { ".js", "text/javascript" },
  46. { ".jpg", "image/jpeg" },
  47. { ".png", "image/png" },
  48. { ".css", "text/css" },
  49. };
  50. for (const auto &e : extensions) {
  51. if (uri.endsWith(e.extension)) {
  52. content_type = e.content_type;
  53. break;
  54. }
  55. }
  56. auto *f = ROMFS::find_stream(uri.c_str());
  57. if (f != nullptr) {
  58. server.sendHeader("Content-Encoding", "gzip");
  59. server.streamFile(*f, content_type);
  60. delete f;
  61. return true;
  62. }
  63. return false;
  64. }
  65. } ROMFS_Handler;
  66. /*
  67. serve files from ROMFS
  68. */
  69. class AJAX_Handler : public RequestHandler
  70. {
  71. bool canHandle(HTTPMethod method, String uri) {
  72. return uri == "/ajax/status.json";
  73. }
  74. bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) {
  75. if (requestUri != "/ajax/status.json") {
  76. return false;
  77. }
  78. server.send(200, "application/json", status_json());
  79. return true;
  80. }
  81. } AJAX_Handler;
  82. /*
  83. init web server
  84. 该函数是 Web 升级的 “总开关”:启动 WiFi AP 并初始化 Web 服务器,
  85. 注册 AJAX/ROMFS 处理器支撑页面显示,绑定 /update 接口处理固件上传,
  86. 上传过程中缓存固件头部、写入 OTA 分区,最后通过你之前关注的 check_OTA_next() 校验固件合法性,
  87. 合法则升级重启,非法则返回失败,是 Web 升级从 “页面访问” 到 “固件生效” 的全流程入口
  88. */
  89. void WebInterface::init(void)
  90. {
  91. Serial.printf("WAP start %s %s\n", g.wifi_ssid, g.wifi_password);
  92. IPAddress myIP = WiFi.softAPIP(); // 获取 AP 模式默认 IP(192.168.4.1)
  93. server.addHandler( &AJAX_Handler ); // 处理 AJAX 异步请求(如获取模块状态)
  94. server.addHandler( &ROMFS_Handler ); // 处理静态页面(HTML/CSS/JS)
  95. /*handling uploading firmware file */
  96. server.on("/update", HTTP_POST, []() {
  97. if (Update.hasError()) {
  98. server.sendHeader("Connection", "close");
  99. server.send(500, "text/plain","FAIL");
  100. Serial.printf("Update Failed: Update function has errors\n");
  101. delay(5000);
  102. } else {
  103. server.sendHeader("Connection", "close");
  104. server.send(200, "text/plain","OK");
  105. Serial.printf("Update Success: \nRebooting...\n");
  106. delay(1000);
  107. ESP.restart();
  108. }
  109. }, [this]() { // 回调 2:上传过程中处理固件数据(核心中的核心)
  110. HTTPUpload& upload = server.upload();
  111. // 获取待写入的备用 OTA 分区(OTA0/OTA1 中非当前运行的分区)
  112. 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;
  113. if (upload.status == UPLOAD_FILE_START) { // 阶段1:开始上传(UPLOAD_FILE_START)
  114. Serial.printf("Update: %s\n", upload.filename.c_str());
  115. lead_len = 0; // 初始化固件头部数据长度
  116. // 启动固件更新,UPDATE_SIZE_UNKNOWN 表示固件大小未知 擦除备份区
  117. if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
  118. Update.printError(Serial);
  119. }
  120. } else if (upload.status == UPLOAD_FILE_WRITE) { // 阶段2:上传中写入数据(UPLOAD_FILE_WRITE)
  121. /* flashing firmware to ESP*/
  122. /*
  123. 就是专门用 16 字节的 “小盒子” 只装固件最开头的 16 个字节(固件的 “身份证”),
  124. 同时不管这个小盒子装没装满,收到的每一段固件数据都会完整写入备用 OTA 分区(备份区);
  125. 等小盒子装满 16 字节后,就只专心往备份区写固件,不再往小盒子存数据了
  126. */
  127. if (lead_len < sizeof(lead_bytes)) { /* 第一步:缓存固件头部数据(lead_bytes) */
  128. uint32_t n = sizeof(lead_bytes)-lead_len;
  129. if (n > upload.currentSize) {
  130. n = upload.currentSize;
  131. }
  132. memcpy(&lead_bytes[lead_len], upload.buf, n); // 拷贝到 lead_bytes 缓存
  133. lead_len += n;
  134. if(lead_len >= 16 && !front_16byte_flag )
  135. {
  136. front_16byte_flag = true;
  137. Serial.printf("front 16 byte is %d %d %d %d ", lead_bytes[0], lead_bytes[1],lead_bytes[2],lead_bytes[3]); // 打印当前头16个字节
  138. Serial.printf("%d %d %d %d ", lead_bytes[4], lead_bytes[5],lead_bytes[6],lead_bytes[7]); // 打印当前头16个字节
  139. Serial.printf("%d %d %d %d ", lead_bytes[8], lead_bytes[9],lead_bytes[10],lead_bytes[11]); // 打印当前头16个字节
  140. Serial.printf("%d %d %d %d end\n", lead_bytes[12], lead_bytes[13],lead_bytes[14],lead_bytes[15]); // 打印当前头16个字节
  141. }
  142. }
  143. /* 第二步:将固件数据写入 OTA 分区 */
  144. if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
  145. Update.printError(Serial); // 写入失败打印错误
  146. }
  147. } else if (upload.status == UPLOAD_FILE_END) { // 阶段3:上传完成(UPLOAD_FILE_END)
  148. // write extra bytes to force flush of the buffer before we check signature
  149. uint32_t extra = SPI_FLASH_SEC_SIZE+1;
  150. while (extra--) {
  151. uint8_t ff = 0xff;
  152. Update.write(&ff, 1); // 写入额外的 0xFF 字节,强制刷新 Flash 缓冲区(保证数据写入完成)
  153. }
  154. // 核心校验:调用 check_OTA_next() 验证固件合法性 必须签名后才能校验成功 并找到对应的应用描述符
  155. if (!CheckFirmware::check_OTA_next(partition_new_firmware, lead_bytes, lead_len)) {
  156. Serial.printf("Update Failed: firmware checks have errors\n");
  157. server.sendHeader("Connection", "close");
  158. server.send(500, "text/plain","FAIL");
  159. delay(5000);
  160. } else if (Update.end(true)) { // 校验通过,完成更新
  161. Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
  162. server.sendHeader("Connection", "close");
  163. server.send(200, "text/plain","OK");
  164. } else { // Update.end() 执行失败
  165. Update.printError(Serial);
  166. Serial.printf("Update Failed: Update.end function has errors\n");
  167. server.sendHeader("Connection", "close");
  168. server.send(500, "text/plain","FAIL");
  169. delay(5000);
  170. }
  171. }
  172. });
  173. Serial.printf("WAP started\n");
  174. server.begin();
  175. }
  176. /*
  177. 该函数是 Web 服务的 “心跳循环”:通过 initialised 标志实现 init() 只执行一次(避免重复初始化),
  178. 然后在主循环中反复调用 server.handleClient(),让 ESP32 持续监听并处理 80 端口的 Web 请求,
  179. 确保用户访问升级页面、上传固件时能实时响应,是 Web 升级功能 “持续可用” 的基础
  180. */
  181. void WebInterface::update()
  182. {
  183. if (!initialised) {
  184. init();
  185. initialised = true;
  186. }
  187. server.handleClient();
  188. }