前幾天所說,接觸新語言、新工具或新平台,在正式投入生產前,我習慣先做好幾件事:確立專案通用框架並研究如何讓「修改程式 -> 編譯 -> 部署 -> 測試 -> 修改程式 -> ...」開發循環最佳化,消除無意義的重複手工及等待,讓思緒專中在程式碼本身,以享受 Coding 樂趣。

我的 ESP 開發流程已處理好惱人的 WIFI 密碼設定問題,下一個需要優化的環節是為 ESP 網頁介面的開發流程。ESPAsyncWebServer 提供很棒的框架,宣告 server.on("/url-path", HTTP_GET, [](AsyncWebServerRequest *request) ) 即可定義網站的各種行為,傳回靜態 HTML、接收瀏覽器輸入控制硬體元件... 等等。(延伸閱讀:IoT 練習 - ESP Web 介面溫溼度記錄器),先前的範例也展示過如何在 C++ 程式碼中定義 HTML 字串(記得要加 PROGMEM 或 F("...") 使用 Flash 儲存,以節省寶貴的 SDRAM 記憶體)並傳回前端,例如:

const char indexHtml[] PROGMEM = R"===(
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <style>
   ...省略...
  </dl>
</body>
</html>
)===";

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
  request->send_P(200, "text/html", indexHtml);
});

不過,這種做法有個嚴重缺點:測試起來太費事太耗時了! 每次改完得重新編譯、上傳更新、等待 ESP 開發板啟動連上 WiFi,最後開啟瀏覽器檢視。看個結果要等兩分鐘,發現有地方沒改好,調一下 HTML 又得重新輪迴一次再花兩分鐘,以我的急躁性格(人稱資訊界王藍田啊)哪忍得了,遲早急到心臟病發。

幸好我是用 VSCode + PlatformIO 開發 ESP8266/ESP32 程式,VSCode 有各式套件,功能強大,搭配 PlatformIO,很快找出完美的開發流程。

上回提到 ESP8266/ESP32 內建 4MB Flash,可透過 SPIFFS 或 LittleFS 模擬檔案系統,而 ESPAsyncWebServer 也支援從 SPIFFS 或 LittleFS 讀檔傳回網站內容

//Send index.htm with default content type
request->send(SPIFFS, "/index.htm");

//Send index.htm as text
request->send(SPIFFS, "/index.htm", "text/plain");

//Download index.htm
request->send(SPIFFS, "/index.htm", String(), true);

甚至能將整個資料夾對映成某個網址(如果有必要,還支援簡單的帳號密碼管控):

server
    .serveStatic("/", SPIFFS, "/www/")
    .setDefaultFile("default.html")
    .setAuthentication("user", "pass");

而 PlatformIO 有功能可將資料夾內容轉成 SPIFFS 跟 LittleFS 檔案系統上傳到 ESP 開發板。因此我們可依循網站概念,將網頁拆成 .html、.css、.js,網頁所需的第三方程式庫 (如 jQuery、Vue.js) 以及圓檔 .png、.jpg 、.svg 也可以依性質分資料夾擺放,方便管理運用。做法是在 PlatformIO 專案建一個 data 資料夾(如下圖),其下再建立 wwwroot 子資料夾,其下再拆成 css、imgs、js 子目錄,主網頁則為 index.html。

要將 data 上傳到 ESP 開發板,先按上圖左下角的 PlatformIO 小圖示,PlatformIO 選單有 Build Filesystem Image 跟 Upload Filesystem Image,可編譯 data 目錄內容並上傳到開發板。

提醒:上傳檔案系統 Image 時需先關閉所有序列通訊,如果有開啟 Serial Monitor,會出現以下錯誤訊息:

raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError()))
serial.serialutil.SerialException: could not open port 'COM7': PermissionError(13, '存取被拒 
。', None, 5)
*** [uploadfs] Error 1

此時請切換到 Serial Monitor 視窗按垃圾桶把它關閉,上傳完再按下方插頭圖示啟動。(註:有時視窗可能會有多個,請用下圖(3)的下拉選單確認並全部關閉)

上傳好 data 目錄,在 ESP 程式加入以下程式將網址 /html 對映到 /data/wwwroot 目錄,並設定預設文件為 index.html,另外,再加入一行 on("/", HTTP_GET...),將網站的根目錄 導向 /html 就大功告成了:

auto staticWebHandler =
    server.serveStatic("/html", WebFS, "/wwwroot/")
        .setDefaultFile("index.html");  
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
  return request->redirect("/html/");
});

如此網頁內容從 C++ 程式碼完全抽離,變成獨立檔案,ESP 網站可被當成一般網站開發,適用各式網站開發技巧與工具。而網頁介面被拆成獨立檔案,結構分明方便管理,還從此擺脫「改 HTML -> 編譯 -> 上傳 -> 測試」的無謂等待,這才是合理的開發體驗呀~

不只如此,轉成標準網站檔案結構,便適用 VSCode Live Server 套件,享受「改完按儲存,下一秒在瀏覽器看到結果」的快感,把 ESP 網頁介面當一般網站開發好,最後再一次上傳到 ESP 開發板。


(圖片來源:https://github.com/ritwickdey/vscode-live-server)

善用以上技巧,便能用 VSCode 實現完美 IoT 網頁介面開發流程,告別「改 C++ 程式測網頁」的低效率開發方式,享受各式前端開發技術與工具,對具前端經驗轉戰 IoT 開發的人是一大福音。

The bast way to build ESP web, separating web contents from C++ code and put them into filesystem, developing and testing them as normal web and finally uploading them to ESP board via PlatformIO.


Comments

Be the first to post a comment

Post a comment