はじめに
1年前のブログの記事で「ESP-WROOM-02でHTMLをファイルシステムに保存する」を書きました。今回は続編として「ESP-WROOM-02」の後継機種「ESP32(ESP-WROOM-32)」を利用してHTMLをファイルシステムに保存します。前回のブログの「サンプルプログラムの解説」ではHTMLに変数を渡す処理の説明が中心でしたが、今回はプログラム全体の処理の流れを具体的に説明します。 ご興味のある方は前回のブログも合わせて一読いただければと思います。
開発環境
■ ハードウェア
- ESP32-DevKitC
- iPhone 8 Plus (12.3.2)
■ ソフトウェア
- Arduino IDE 1.8.9
- esp32 by Espressif Systems 1.0.2
- Arduino ESP32 filesystem uploader
ファイルシステムアップローダーのインストール
※ ESP32用Arduino開発環境が整っていることを前提に手順を説明します。
1.HTMLファイルをESP32にアップロードするために「Arduino ESP32 filesystem uploader」をインストールします。 こちらのページを開きます。 
2.以下のZIPファイルをダウンロード、解凍します。「ESP32FS」というフォルダができます。 
3. PCのArduinoのプロジェクトディレクトリに「tools」という名前のフォルダを作成します。 
4.2で解凍した「ESP32FS」フォルダごと「tools」フォルダ下にコピーします。
5. Arduino IDEを再起動します。「ツール」メニューに「ESP32 Sketch Data Upload」が表示されます。以上でインストール完了です。

サンプルプログラムを書き込む
1.サンプルプログラムをこちらからダウンロード、解凍します。「spiffs_esp32-master」というフォルダができます。

2.spiffs_esp32フォルダのスケッチ「spiffs_esp32.ino」をArduino IDEから開きます。「スケッチ」メニューの「マイコンボードに書き込む」をクリックしてESP32に書き込みます。
※ Arduino IDE 1.8.9、esp32 by Espressif Systems 1.0.2 環境で動作確認済みです。これ以外のバージョンで実行した場合はコンパイルエラーが出る可能性があります。
 3.ESP32を再起動後、「ツール」メニューの「ESP32 Sketch Data Upload」をクリックします。dataフォルダの「index.html」、「site.css」、「eeprom.js」、「jquery.min.js」ファイルがESP32のフラッシュメモリにアップロードされます。
※ シリアルモニタは閉じた状態で実行して下さい。
サンプルプログラムの解説
本サンプルは前回のブログと同様で、WebブラウザからESP32のEEPROMに性別と名前を登録するプログラムです。 
 今回はプログラム全体の処理内容を以下の2つに分けて解説します。
- ESP32起動時の処理
- トップページアクセス時の処理
ESP32起動時の処理
まず、spiffs_esp32.ino の setup を実行します。setup では以下の処理を行います。
  // SPIFFS 初期設定
  SPIFFS.begin();
  // EEPROM 初期設定
  EEPROM.begin(sizeof(eeprom));
  // EEPROMに保存された性別・名前をRAMに読み込む
  getEEPROM();
  // Wi-Fi 接続
  WiFi.mode(WIFI_AP);
  WiFi.softAP(WIFI_SSID, WIFI_PWD, WIFI_CHANNEL);
  WiFi.softAPConfig(ip, ip, subnet);
  // HTTP 初期設定
  setupHttpd();
HTTP 初期設定は spiffs_httpd_esp32.ino の setupHttpd を実行します。setupHttpd では各URIアクセス時に実行する関数を定義します。
void setupHttpd(void) {
  // http://192.168.6.1 アクセス時に setupHttpdRoot() を実行
  server.on("/", setupHttpdRoot);
  // http://192.168.6.1/index.html アクセス時に setupHttpdRoot() を実行
  server.on("/index.html", setupHttpdRoot);
  // http://192.168.6.1/eeprom アクセス時にsetupHttpdEeprom() を実行
  server.on("/eeprom", setupHttpdEeprom);
  // 上記以外アクセス時に handleConfirmFile() を実行
  server.onNotFound(handleConfirmFile);
  // Webサーバ起動
  server.begin();
}
最後に spiffs_esp32.ino の loop を実行、ブラウザからのリクエスト要求を処理する準備が完了しました。
void loop() {
  // クライアントからの要求を処理
  server.handleClient();
}
トップページアクセス時の処理
スマートフォンのWi-Fi設定から SSID : 「SPIFFS_ESP32_SAMPLE」を選択、パスワード : 「123456789」を入力します。ブラウザから 「 http://192.168.6.1 」にアクセスします。 トップページアクセス時の処理は少し複雑なので以下の3つに分けて説明します。
- index.htmlを読み込みブラウザに返す
- site.css、jquery.min.js、eeprom.jsを読み込みブラウザに返す
- eeprom.jsで定義したAjaxを実行
1. index.htmlを読み込みブラウザに返す
「ESP起動時の処理」で説明した setupHttpd では server.on("/", setupHttpdRoot ); と定義しているので、プログラム側では spiffs_httpd_esp32.ino の setupHttpdRoot を実行します。
void setupHttpdRoot(void) {
  // 「設定する」ボタン押下時
  if(server.hasArg("personset")) {
    // トップページで選択した性別を取得
    if(server.hasArg("person-gender")) {
      eeprom.person.gender = atoi(server.arg("person-gender").c_str());
    }
    // トップページで入力した名前を取得
    if(server.hasArg("person-username")) {
      strncpy(eeprom.person.username, server.arg("person-username").c_str(), NAME_SIZE);
    }
    // EEPROMに保存
    setEEPROM();
  }
  // /index.html を読み込みブラウザ側に返す
  handleFileRead(INDEX_HTML_FILE);
}
「設定する」ボタンは押されていないので、handleFileRead(INDEX_HTML_FILE) のみ実行します。handleFileRead(INDEX_HTML_FILE) は SPIFFS からindex.htmlファイルを読み出しブラウザ側にファイルを返します。
void handleFileRead(String path) {
  // ファイルのMIMEタイプを取得
  String contentType = getContentType(path);
  // SPIFFSにファイルが存在するか確認
  if(SPIFFS.exists(path)){
    // ファイルを読み込む
    File file = SPIFFS.open(path, "r");
    // cache設定
    server.sendHeader("Cache-Control", "public, max-age=86400");
    // 読み込んだファイルをブラウザ側に返す
    size_t sent = server.streamFile(file, contentType);
    // ファイルクローズ
    file.close();
  } 
}
2. site.css、jquery.min.js、eeprom.jsを読み込みブラウザに返す
次に index.html ファイルを受信したブラウザ側では index.html のヘッダーに記述されている link、script タグ部分を読み込み、再度Webサーバにリクエスト送信します。
<head> <meta charset='utf-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>SPIFFSサンプル</title> <!-- http://192.168.6.1/css/site.css にアクセス--> <link rel='stylesheet' type='text/css' href='css/site.css'> <!-- http://192.168.6.1/js/jquery.min.js にアクセス--> <script src='js/jquery.min.js'></script> <!-- http://192.168.6.1/js/eeprom.js にアクセス--> <script type="text/javascript"> !-- document.write("<script src='js/eeprom.js?v=" + new Date().getTime() + "'></script>"); //--> </script> </head>
「ESP起動時の処理」で説明した setupHttpd では server.onNotFound(handleConfirmFile); と定義しているので、Webサーバがindex.htmlヘッダーの /css/site.css、/js/jquery.min.js、/js/eeprom.js のリクエストを受信後、プログラム側では spiffs_httpd_esp32.ino の handleConfirmFile を順番に実行します。
void handleConfirmFile(void) {
  
  // URIを取得
  String path = server.uri();
  
  // SPIFFSにファイルが存在しているか確認
  if (SPIFFS.exists(path)) {
    // css/site.css、js/jquery.min.js、js/eeprom.js を読み込みブラウザ側に返す
    handleFileRead(path);
  } else {
    // File Not Found を返す
    String message = F("File Not Found\n\n");
    message += F("URI: ");
    message += server.uri();
    message += F("\nMethod: ");
    message += (server.method() == HTTP_GET)?F("GET"):F("POST");
    message += F("\nArguments: ");
    message += server.args();
    message += F("\n");
    for (uint8_t i=0; i<server.args(); i++){
      message += " " + server.argName(i) + F(": ") + server.arg(i) + F("\n");
    }
    server.send(404, F("text/plain"), message);
  }
}
/css/site.css、/js/jquery.min.js、/js/eeprom.jsはSPIFFSに存在するので、 handleFileReadを呼び出し各ファイルを読み込みブラウザに返します。
3. eeprom.jsで定義したAjaxを実行
ブラウザ側では /js/eeprom.js で定義されたコードを読み込みAjaxで http://192.168.6.1/eeprom にアクセスします。
$(document).ready(function(){
  $.ajax({
    type:     'GET',
    url:      'eeprom',
    dataType: 'json',
    success:  function (data) {
                for (var category in data) {
                  for (var key in data[category]) {
                    $('#'+category+'-'+key).val(data[category][key]);
                  }
                }
              },
    error:    function (xhRequest, textStatus, errorThrown) {
                alert(textStatus + " : " + errorThrown);
              }
  });
});
「ESP起動時の処理」で説明した setupHttpd では server.on("/eeprom", setupHttpdEeprom); と定義しているので、Webサーバが /eeprom のリクエストを受信後、プログラム側では spiffs_httpd_esp32.ino の setupHttpdEeprom を実行します。
void setupHttpdEeprom(void) {
  // JSON形式の性別・名前をブラウザに返します。
  server.send(200, "application/json", getEepromJson()); 
}
getEepromJson() で性別・名前をJSON形式にします。JSON形式の値をブラウザ側が受信、index.htmlの性別・名前にその値をセットします。この処理についての詳しい説明は前回のブログをご参照下さい。以上でトップページに関する全てのデータのロード完了です。
String getEepromJson(void) {
  
  // EEPROMから値を読み出す
  getEEPROM();
  
  String gender = "";
  String username = "";
  if(eeprom.person.gender != 0 && eeprom.person.gender != 1) {
    // EEPROM初期状態ではデフォルト値を設定
    gender = String(DEFAULT_GENDER);
    username = String(DEFAULT_USERNAME);
  } else {
    // EEPROMの値を設定
    gender = String(eeprom.person.gender);
    username = String(eeprom.person.username);
  }
  // EEPROMの値をJSON形式にする
  String result = "{";
  result += "\"person\":";
  result += "{";
  result += "\"gender\":" + gender;
  result += ",";
  result += "\"username\":\"" + username + "\"";
  result += "}";
  result += "}";
  return result;
}
最後に
今回はESP32のSPIFFS機能を使用してプログラムと静的ファイルを分けて保存する方法について説明しました。サンプルプログラムでは、cssファイルや jsファイルも別ファイルで保存、index.html に動的な値をセットするためにAjaxを利用していることもあり全体の処理の流れが少し複雑になっています。前回と今回のブログの「サンプルプログラムの解説」を合わせてご確認していただければよりご理解いただけるかと思います。
 ブログに関するご質問等ございましたらお気軽にご連絡下さい。
ここで紹介する内容は、弊社で確認した一例です。
 本ページを参考に作業をされ、何らかのトラブルや損失・損害等が発生しましても一切責任を負えません。自己責任でお願いいたします。