在ESP8266的SPIFFS檔案系統存放網頁檔案(一)

本文旨在補充《超圖解物聯網IoT實作入門》第12章「在程式記憶體區儲存靜態網頁」一節(12-31頁),書本的範例把HTML網頁透過C++的Raw String語法存入快閃記憶體。本單元將採用ESP8266的FS.h檔案系統,將網頁和相關資源(如:圖像和JavaScript程式檔)存入快閃記憶體的SPIFFS區域。

連接ESP8266網站伺服器

ESP8266的快閃記憶體結構

下圖是ESP8266的快閃記憶體的分割概略圖,取自12-24頁的註解以及13-13頁「透過OTA更新ESP8266的韌體」,檔案系統區域的大小取決於ESP8266模組的快閃記憶體大小。

ESP8266的快閃記憶體結構

SPIFFS宛如內建於ESP8266的SD記憶卡,可存放程式所需的資料、設定檔或者網站檔案。要存入SPIFFS區域的資料,都得事先擺在程式原始碼的資料夾裡的“data”資料夾(請自行新增“data”資料夾)。例如,本單元程式叫做espStaticWeb,其資料夾結構如下(請按此連結下載本單元的原始檔):

data資料夾裡的網頁內容

使用ESP8266FS工具上傳資料檔案到SPIFFS區域

負責把資料上傳到SPIFFS的工具程式叫做ESP8266FS,需要另外安裝,步驟如下:

  1. 下載ESP8266FS工具
  2. 將它解壓縮到Arduino IDE安裝路徑底下的tools資料夾

ESP8266FS工具的安裝路徑

重新啟動Arduino IDE之後,開啟espStaticWeb.ino檔。你可以執行主功能表「草稿碼→顯示草稿碼資料夾」指令,確認目前的程式原始碼路徑裡面包含data資料夾。

執行主功能表「工具→ESP8266 Sketch Data Upload」(ESP8266草稿碼資料上傳)指令:

ESP8266 Sketch Data Upload指令

IDE將開始上傳data資料夾裡的內容。

開始上傳資料到SPIFFS

當IDE訊息窗格顯示“SPIFFS Image Uploaded”,代表SPIFFS映像檔上傳完畢。

撰寫引用檔案系統的ESP8266網站伺服器程式

本單元的程式改寫自第12章「使用ESP8266WebServer程式庫建立HTTP伺服器」的WiFi_server.ino檔,並加入第13章「設置區域網路域名」的mDNS.ino檔裡面的設定專屬域名和服務回應訊息的程式碼。

凡是有使用檔案系統的程式,都要在程式開頭引用“FS.h”檔。請先在檔案開頭加入引用FS.h的敘述:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <FS.h>

把原本處理網站根路徑請求的rootRouter函式,改成以「唯讀」方式開啟SPIFFFS根路徑裡的index.htm檔,並且交給server物件傳給用戶端。SPIFFS與檔案的相關操作指令說明,請參閱下文「ESP8266檔案系統的相關操作指令」一節。

rootRouter函式

完整的程式碼如下,setup函式裡面要執行SPIFFS.begin()啟用SPIFFS檔案系統

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <FS.h>

const char ssid[] = "你的WiFi網路SSID";
const char pass[] = "你的WiFi密碼";
const char* host = "jarvis";

ESP8266WebServer server(80);

void rootRouter() {
  File file = SPIFFS.open("/index.htm", "r");
  server.streamFile(file, "text/html");
  file.close();
}

void setup( ){
  Serial.begin(115200);

  SPIFFS.begin();  // 啟用SPIFFS檔案系統

  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  IPAddress ip = WiFi.localIP();
  
  if (!MDNS.begin(host, ip)) {
    Serial.println("Error setting up MDNS responder!");
    while(1) { 
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.print("Connected! IP address: ");
  Serial.println(WiFi.localIP());

  MDNS.begin(host);

  server.on("/", rootRouter);
  server.on("/index.htm", rootRouter);

  server.onNotFound([](){
    server.send(404, "text/plain", "FileNotFound");
  });

  server.begin();
  Serial.println("HTTP server started");

  MDNS.setInstanceName("Cubie's ESP8266");
  MDNS.addService("http", "tcp", 80);
}
 
void loop( ){
  server.handleClient();
}

編譯並上傳程式碼到ESP8266之後,開啟瀏覽器連線到jarvis.local或者ESP8266的IP位址,即可看見如下的網頁:

呈現在瀏覽器的jarvis.local網頁畫面

咦…網頁圖片並沒有顯現。這是因為上面的程式只處理來自根路徑(“/”和“/index.htm”)的請求,沒有處理影像路徑“/img/ESP8266.png”的請求(參閱第五章「使用Socket.io建立即時連線」一節的圖說,5-27頁),所以網站無法傳遞影像檔給瀏覽器。

雖然我們可以加入像底下處理影像路徑的請求:

server.on("/img/esp8266.png", imgRouter);

以及對應的處理函式:

void imgRouter() {
  File file = SPIFFS.open("/img/esp8266.png", "r");
  server.streamFile(file, "image/png");
  file.close();
}

但很顯然地,這種寫法很不妥當,因為程式無法處理存取非特定資源路徑的請求。相關處理方式請參閱下一篇文章介紹。

ESP8266檔案系統的相關操作指令

底下的指令說明摘譯自ESP8266專案網站的這一篇說明文件

檔案系統物件(SPIFFS)

SPIFFS.begin()
掛載SPIFFS檔案系統的方法。它必須在其他任何FS API被使用之前呼叫。若檔案系統掛載成功,傳回true,否則傳回false。

SPIFFS.format()
格式化檔案系統。可以在執行begin()之前或之後呼叫。若格式化成功則傳回true。

SPIFFS.open(路徑, 模式)
開啟檔案。路徑必需要斜線開頭的絕對路徑(如:/dir/filename.txt)。模式參數是個用字串指定的存取模式,其值為”r”, “w”, “a”, “r+”, “w+”和”a+”之中的一個,這些模式字串的意義和C語言的fopen函式相同(註:請參閱第七章「利用SD記憶卡紀錄溫濕度變化」一節,7-39頁)。

傳回File(代表「檔案」)物件。若要檢查檔案是否開啟成功,請使用布林運算子:

File f = SPIFFS.open("/f.txt", "w");   // 以「寫入」模式開啟f.txt檔
if (!f) {
    Serial.println("file open failed");  // 檔案開啟失敗
}

SPIFFS.exists(path)
如果指定的路徑存在,則傳回true,否則傳回false。

SPIFFS.openDir(path)
開啟絕對路徑資料夾,傳回一個Dir物件。

SPIFFS.remove(path)
刪除絕對路徑的檔案,若刪除成功則傳回true。

SPIFFS.rename(原始路徑檔名, 新路徑檔名)
重新命名檔案,路徑必須是絕對路徑,若重新命名成功則傳回true。

Dir(Directory,目錄)物件

Dir物件的作用是遍覽目錄(資料夾)裡的所有檔案,它提供三個方法:next(下一個檔案)、fileName(讀取檔名)和openFile(開啟檔案),它們的用法如底下的範例:

Dir dir = SPIFFS.openDir("/data");  // 開啟“/data”目錄
while (dir.next()) {   // 只要還有檔案…
    Serial.print(dir.fileName());  // 顯示檔名
    File f = dir.openFile("r");    // 以「唯讀」模式開啟檔案
    Serial.println(f.size());      // 顯示檔案大小
}

在遍覽目錄的過程中,只要還有檔案,dir.next()就傳回true,這個方法必須在fileName()和openFile()函式之前呼叫。openFile()方法接收一個「開啟檔案的模式」參數,這個參數的意義和SPIFFS.open()函式相同。

File(檔案)物件

SPIFFS.open和dir.openFile函式都會傳回File物件。這個物件支援Stream的所有函式,因此你可以使用readBytes, findUntil, parseInt, println以及其他所有Stream方法。底下是File物件特有的函式:

file.seek(位移字元量, 模式)
這個函式的行為就像C語言的fseek()函式。根據的「模式」參數值,它將在目前的檔案中移動:

  • 若「模式」值是SeekSet,則從檔案開頭移動指定的字元量。
  • 若「模式」值是SeekCur,則從目前的位置位移指定的字元量。
  • 若「模式」值是SeekEnd,則從檔案結尾移動指定的字元量。

若位移成功,則傳回true。

file.position()
傳回目前在檔案中的位置,單位是byte。

file.size()
傳回檔案大小,單位是byte。

String name = file.name();

傳回檔案名稱,其格式相當於const char*,若要儲存檔名,請將它轉成String。

file.close()
關閉檔案。執行此函式之後,就不能在該檔案物件上執行其他操作。

延伸閱讀

Posts created 468

31 thoughts on “在ESP8266的SPIFFS檔案系統存放網頁檔案(一)

  1. 趙老師您好:
    我在完成13-7頁的動手做時,發現用網頁去操控LED開關和LED照度時,會有延遲,也就是我網頁操作一個動作後,隔了1~2秒,接在esp8266的LED燈才有動作,所以我在找原因出在哪,可能原因如下

    1. 在程式記憶體區引用CDN的jQuery和程式碼,是透過網路下載所以變慢
    2. ESP8266和網頁之間沒有建立socket,所以沒辦法做到即時

    針對1的部分,我才找看看有沒有辦法將jQuery或jQuery mobile的css檔案和js檔案直接燒進esp8266,如果網頁有用到圖檔,也把圖檔燒進esp8266,剛好用老師寫的這篇說明來實做看看

    針對2的部分,我在https://swf.com.tw/?p=901有詢問過老師,把ESP8266當作網頁伺服器(server),瀏覽器為用戶端(client),兩者之間建立socket,不知老師是否能建立一個 “建立Arduino的Socket即時通訊程式(三)”來說明這部分

    另外在13-30頁下方 的 dtostrf(charTemp, 5, 2, 12.34) 是否應該改成 dtostrf(12.34, 5, 2, charTemp)
    另外想請問在13-23頁中文字的圖檔(temperature.png)是用甚麼軟體建立的,我用小畫家 -> 常用 -> 調整大小 -> 選擇像素 ->
    水平改成32,垂直改成16 -> 選擇文字把文字放在區塊上,再調整區塊背景顏色(黑),文字前景顏色(白)及透明,發現不太好操作。

    1. hi alex:

      ESP8266網站伺服器程式有時候反應比較慢,是因為HTTP訊息比較多的原因,如果是電腦或樹莓派這種主記憶體空間比較大的微控器,就不會出現類似的問題。透過CDN下載或者從本機伺服器讀取的jQuery程式庫,只要下載完成,就會一直待在用戶端裝置的記憶體,所以只有在首次開啟網頁的時候,會比較慢。

      我會陸續更新相關內容,本週會先更新一篇「在ESP8266的SPIFFS檔案系統存放網頁檔案(二)」。

      13-23頁的中文字點陣圖,我是從Flash輸出的。你可以嘗試免費的Paint.NET繪圖軟體,有中文操作介面。

      thanks,
      jeffrey

  2. 趙老師你好怎麼利用arduino結合ESP8266-01去連上某個網站
    不用IP能連上網站?

    1. 小丁:

      你指的不用IP,是說採用網域名稱(像swf.com.tw)嗎?請使用 AT+CIPSTART 指令連結看看,詳細的指令參數請參閱這一篇留言,安信可公司提供的PDF文件。

      thanks,
      jeffrey

  3. 老師您好請教一下
    也就是說ESP系列比AVR單晶片的EEPROM多了一項可讀寫的記憶體空間
    使用SPIFFS的方式存取
    這樣講對嗎?

  4. 老师您好,我想问一下这句代码server.streamFile(file, “text/html”);中为什么要用streamFile?
    streamFile是什么意思?

  5. 老師你好~ 請問esp8266有辦法利用arduino 影像模組像是ov7670來傳送圖片到雲端或網站嗎?

  6. 老師您好!我來補充一下,Windows 10底下如果在瀏覽器沒辦法開啟http://jarvis.local/的話需要安裝Bonjour。
    參考:https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266mDNS
    官方下載網站:https://developer.apple.com/bonjour/
    需要apple賬號安裝該Bonjour SDK for Windows

  7. 請問此篇的CODE可直接給ARDUINO MEGA2560用嗎?
    還是只能給NODEMCU系列的板子
    傳上mega都只有編譯錯誤

  8. 老師您好,想問一下若是想要用如您課本中Ch12一樣在程式記憶區儲存靜態網頁(需加入圖片或超連結影片之類的網頁)可以使用Mega2560配合W5100達成嗎?或是說要達成此網頁需用您書中提到的node.js達成呢?謝謝老師!

  9. 老師您好,接續剛剛的問題,若是用W5100想要做網頁前端的設計以及想要控制繼電器來開關電器,除了用client.println指令外,還有其他方式嗎?麻煩老師提示了,謝謝老師的指導,多麻煩您了!

    1. client.println指令和控制繼電器兩者並沒有關聯…

      如果你打算製作一個圖文並茂、美侖美奐的網頁,並且放在微控器裡面,建議你使用樹莓派之類的高階控制板,而且價格不見得比Mega2560+W5100高。畢竟Arduino板的強項還是在「硬體控制」,能夠存入簡單的網頁並扮演網站伺服器,已經很了不起了。

      我不了解你為何要選用Mega2560,就透過網頁控制繼電器來說,ESP8266就夠好用了。

      建議你先從基本的硬體輸出∕入控制開始練習,然後再練習網路方面的實作,屆時你自然就會了解它們之間的整合方式。

      have fun!
      jeffrey

  10. 請問老師

    開啟12章的範例 WiFi_GET 可以順利執行
    但是WiFi_HTML這個範例卻無法成功執行(連線成功後過一段時間序列埠冒出一堆亂碼如下)
    不知道是發生了什麼事,是這個範例問題嗎?

    Exception (3):
    epc1=0x40208b3c epc2=0x00000000 epc3=0x00000000 excvaddr=0x40239c86 depc=0x00000000

    ctx: cont
    sp: 3ffffc90 end: 3fffffd0 offset: 01a0

    >>>stack>>>
    3ffffe30: 3ffe8b0b 4020627f 0000001c 401004e8
    3ffffe40: 00000000 40204ba9 3ffe8b0b 402049a8
    3ffffe50: 00000010 3fff0428 3ffee9c8 40204568
    3ffffe60: 00000120 3ffffec0 3ffffec0 4020627f
    3ffffe70: 40239c86 00000119 3ffffec0 402062cb
    3ffffe80: 00000010 00000010 3ffffec0 402062fd
    3ffffe90: 00000001 00000001 3ffffec0 4020634a
    3ffffea0: 40239c86 3fffff00 3fffff00 4020627f
    3ffffeb0: 3ffeec2c 00000238 3ffefb0c 402024e2
    3ffffec0: 3fff05a4 0000011f 00000119 401006dc
    3ffffed0: 00000001 40203abc 3ffefb0c 40207032
    3ffffee0: 00000000 3fffdad0 3ffefb0c 4020455e
    3ffffef0: 3ffefb0c 3ffeea0c 3fffff20 4020459a
    3fffff00: 00000000 00000000 00000000 4020642c
    3fffff10: 3ffefb0c 3ffeea0c 3ffee9c8 40204621
    3fffff20: 3fff040c 0000000f 00000001 00000000
    3fffff30: 00000000 4bc6a7f0 0000d08b 3ffeebb4
    3fffff40: 3ffeea0c 00000001 3ffe8504 3ffee9f0
    3fffff50: 00000001 00000000 40203abc 0000000f
    3fffff60: 00000000 3fff01e4 3ffee9c8 3ffeebb4
    3fffff70: 00000001 3ffee9f0 3ffee9c8 40204870
    3fffff80: 402073a0 00000000 00001388 40202504
    3fffff90: 00000000 3fff01e4 00000000 feefeffe
    3fffffa0: 3fffdad0 00000000 3ffeebac 402026b8
    3fffffb0: 3fffdad0 00000000 3ffeebac 40206d70
    3fffffc0: feefeffe feefeffe 3ffe8504 40100739
    <<<stack<<<

    ets Jan 8 2013,rst cause:2, boot mode:(1,7)

    ets Jan 8 2013,rst cause:4, boot mode:(1,7)

    wdt reset

    1. 上面那一串是執行13章的 “”WiFi_HTML 範例”” 後,在序列埠看到的亂碼

      這個亂碼只有從Mac系統上用Arduino IDE燒錄程式碼,並且從網頁連線到ESP8266後才會出現
      如果程式上傳完成沒有用網頁連線到ESP8266,序列埠不會出現這段亂碼

      但是我用Windows上傳程式碼就可以順利連線到ESP8266的網頁,這實在神奇了
      雖然這個問題暫時解決的,但不知道原因次什麼還又有點毛毛的
      不過我也不知道要從哪個環節開始檢討,所以沒辦法自己找出問題的原因

      _________________________________
      我用 maxOS(10.14) 和 Windows 10(17134.345)
      開啟Arduino IDE(1.8.7) 連線到網路硬碟上的範例程式(開啟相同檔案)
      _________________________________

    2. 感謝告知!我現在沒有用Mac,也許這個月底會在Mac上測試看看。

      thanks,
      jeffrey

  11. 請問老師:

    如果想要讓多片開發版互相用WiFi方式溝通的話,用什麼方式會比較好呢?

    目前書上的解決方案有
    1.用網頁對開發板下命令對板子上的元件做事
    2.開發版透過網頁向使用者送出提示訊息

    有沒有什麼方式是可以讓A板接收訊號,然後直接叫B板做事的方法呢?

    1. 你可以把A板當作前端,B板當作後端,B板最好採用樹莓派之類的高階控制板,才能負擔多個前端同時連線。

      thanks,
      jeffrey

  12. 請問關於”超圖解ESP32深度實”中的第六章,使用到SPIFFS工具
    安裝了Arduino 2.0 IDE卻無法使用,網路上搜尋了一下,發現應該是Arduino IDE改版導致
    請問有其他方式嗎?

    1. 沒錯,Sketch Data Upload外掛是用Java語言開發的,2.0版IDE不支援,所以我仍保留使用1.8.19版。

    2. 可以請教一下,如果同時安裝1.8跟2.0的IDE,系統會不會產生衝突?
      因為發現很多工具在2.0都還不支援

    3. 不會,應用程式本體分別存於不同路徑,程式庫和編譯環境的工具鏈(例如:ESP32)是共用的。第一次啟動IDE 2.0時,它提示”AVR平台已安裝,無法安裝”錯誤,因為我的1.8.19版IDE的AVR工具鏈是舊版,需要在2.0的開發板管理員手動選擇最新版、安裝。

  13. 感謝!!我安裝後,使用visual code當編輯器成功:)
    不過編譯過程跑出了一個警告訊號,我觀察了一下自己python版本是3.6.8
    gen_esp32part.py:507: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode – interpreting them as being unequal

    當然這個警告訊息不會導致編譯跟上傳的問題,您有遇過嗎?有辦法解決嗎?

    1. ESP8266的esptool.py所在路徑:

      %HOMEPATH%\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\版本\tools\esptool

      請嘗試先在「開發板管理員」刪除ESP8266開發環境,或者手動刪除這個路徑裡的esp8266資料夾:

      %HOMEPATH%\AppData\Local\Arduino15\packages

      然後在開發板管理員重新安裝”ESP8266″開發環境試試。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top