ESP32 Webサーバをファイルシステムを使って作成する(SPIFFS)

これまでサーボモータやプラレールなどをwebサーバ経由で動かしてきましたが、問題はhtmlコードをスケッチに組み込んで、下の用に煩雑なコードになってしまうことでした。

SPIFFSというフラッシュメモリ内にファイルシステムを実装する仕組むを使うことで、データや画像ファイルだけでなく、webサーバのもとになるhtmlやcssファイルを保存し、プログラム実行時にそのファイルを読みに行くことができます。

無理やり組み込んでいたhtmlコードを外部ファイルとして、取り扱うことで、見づらいコードともおさらばできるって寸法です。

どうすればできるか?を今回まとめました。本記事の内容は下のサイトを参考に書いています。

ESP32 Web Server using SPIFFS (SPI Flash File System) | Random Nerd Tutorials

In this tutorial we’ll show you how to build a web server that serves HTML and CSS files stored on the ESP32 filesystem. Instead of having to write the HTML and CSS text into the Arduino sketch, we’ll create separated HTML and CSS files.

内容としては、最初のほうはまんまですが、個人的な覚書として、つづっていきます。後半戦は、以前つくったDCモータをWebサーバ経由で動かしたコードを例にして、SPIFFSを適用してみます。

ESP32でプラレールを操作してみた

Arduino IDEにファイルシステムアップローダをインストールする

ESP32のファイルシステムを利用するのはスケッチ上でコードを書いても実現できますが、不便なので、下記のようなプラグインが用意されているらしいので、これをいっときます。

Release Release for esptool_py · me-no-dev/arduino-esp32fs-plugin

You can’t perform that action at this time. You signed in with another tab or window. You signed out in another tab or window. Reload to refresh your session. Reload to refresh your session.

ダウンロードして、zipを解凍するとesp32fs.jarという名前のjavaのアーカイブファイルができました。これをArduinoのインストールディレクトリに保存します。たとえば、自分の環境では、下のようなパスです。

C:\Arduino\arduino-1.8.12\tools\ESP32FS\tool\esp32fs.jar

Arduinoを再起動して、ツールバーを確認すると、ESP32 Sketch Data Uploadという項目が追加されています。

ファイルのアップロードの仕方

データをどうやってアップするかというと、使用するスケッチが保存されてるフォルダにdataという名前のフォルダを作成して、そんなかにファイルを保存していきます。

今回は、test.txtというテキストを作成して、保存してみました。

スケッチを開きます。今回サンプルなので、空コードです。

正常にファイルがアップロードされると、下のコンソール画面の帯にSPIFFS Image Uploadedと表示されます。

アップロードしたファイルを確認する

ファイルの確認には、SPIFFSのライブラリ使って、確認できます。

最初に紹介したサイトのソースコードを使わせてもらいます。Rui Santos さんどうもありがとう。

/*********
  Rui Santos
  Complete project details at https://randomnerdtutorials.com  
*********/

#include "SPIFFS.h"
 
void setup() {
  Serial.begin(115200);
  
  if(!SPIFFS.begin(true)){
    Serial.println("An Error has occurred while mounting SPIFFS");
    return;
  }
  
  File file = SPIFFS.open("/test.txt");
  if(!file){
    Serial.println("Failed to open file for reading");
    return;
  }
  
  Serial.println("File Content:");
  while(file.available()){
    Serial.write(file.read());
  }
  file.close();
}
 
void loop() {

}

実行後、モニターウィンドウを確認すると、テキストで書いた内容が無事表示されました。ちゃんと読みにいけたようです。

アップロードしたファイルを編集してみる

上のコードを参考に、フラッシュに保存したファイルを編集してみます。ファイルの編集は、SPIFFS.openに”w”というオプションをつけます。

#include "SPIFFS.h"
 
void setup() {
  Serial.begin(115200);
  
  if(!SPIFFS.begin(true)){
    Serial.println("An Error has occurred while mounting SPIFFS");
    return;
  }
  
  File file = SPIFFS.open("/test.txt", "w");
  if(!file){
    Serial.println("Failed to open file for reading");
    return;
  }
  
  Serial.println("File writing ...");

  if (!file.print("Writing from ESP32"))
  {
    Serial.println("File write failed !");
  }
  file.close();
  Serial.println("finish !");
}
 
void loop() {

}

ESP32からテキストを書くことができました。optionの”w”は、書き込み専用でテキストファイルを開くという意味で、既存のファイルデータは削除されます。

既存のテキストを追加編集していきたいときは”a”を使います。

  File file = SPIFFS.open("/test.txt", "a");
  if(!file){
    Serial.println("Failed to open file for reading");
    return;
  }
  else
  {
    file.println("add message");      
  }

再び サントスコードを実行すると、既存のデータの後に文字列が追加されました。

ファイルシステムでhtmlをフラッシュに保存する

他のサイトだと、SPIFFSを使って、htmlをフラッシュに保存するサンプルは、なぜか非同期サーバを利用するコード(ESPAsyncWebServerのライブラリ)と合わせて紹介されていることが多いです。

自分は、混乱するので、下の同期(?)サーバのコードを可能な限りキープして、SPIFFSを使いたいと思います。

内容的にはシンプルで、これまで、説明した内容をテキストではなく、htmlを読むようにして、htmlコードを文字列に格納し、client.printlnを一回で済ましてしまおうというものです。

実際のサンプル

dataフォルダにhtmlファイルをこれまでの手順でアップロードしたら、スケッチからそのhtmlファイルを読み込むようにします。下が、SPIFFS化したコードになります。

// Import required libraries
#include "WiFi.h"
#include "SPIFFS.h"

// Network credentials
const char* ssid     = "10B1F88E5DB9-2G";
const char* password = "5530117932294";

// Web server on port 80 (http)
WiFiServer server(80);
// Variable to store the HTTP request
String header;
String file_s; //html string

// Decode HTTP GET value
String valueString = String(5);
int pos1 = 0;
int pos2 = 0;

// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0; 
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 20000;

//
// Motor制御信号用ピン
int motorpin_pos = 27; //正転 
int motorpin_neg = 14; //逆転

// プラレールマスコン
int target_duty =0;
bool inv_rotation = false;

// PWM
const int freq = 10000;   //PWM周波数 Hz
const int pwmChannel_pos = 0; //channel 0
const int pwmChannel_neg = 1; //channel 1

const int resolution = 8; //分解能8bit



void setup() {
    // sets the pins as outputs:
  pinMode(motorpin_pos, OUTPUT);
  pinMode(motorpin_neg, OUTPUT);
  
  // configure LED PWM functionalitites
  ledcSetup(pwmChannel_pos, freq, resolution);
  ledcSetup(pwmChannel_neg, freq, resolution);
  
  // attach the channel to the GPIO to be controlled
  ledcAttachPin(motorpin_pos, pwmChannel_pos);
  ledcAttachPin(motorpin_neg, pwmChannel_neg);

  //
  inv_rotation=false;
  ledcWrite(pwmChannel_pos, 0);
  digitalWrite(motorpin_neg,LOW);

  
  // Start serial
  Serial.begin(115200);

  
  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();

bool ok = SPIFFS.begin();
  if (ok) {
    Serial.println("ok");
    bool exist = SPIFFS.exists("/WifiRail.html");

    if (exist) {
      Serial.println("The file exists!");

      File f = SPIFFS.open("/WifiRail.html", "r");
      if (!f) {
        Serial.println("can't open the file...");
      }
      else {
        file_s = f.readString();
        f.close();
      }
    }
    else {
      Serial.println("No such file found.");
    }
}
}

void loop(){
  // Listen for incoming clients
  WiFiClient client = server.available();   
  
  // Client Connected
  if (client) {                             
    // Set timer references
    currentTime = millis();
    previousTime = currentTime;
    
    // Print to serial port
    Serial.println("New Client."); 
    
    // String to hold data from client
    String currentLine = ""; 
    
    // Do while client is connected
    while (client.connected() && currentTime - previousTime <= timeoutTime) { 
      currentTime = millis();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
        
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK) and a content-type
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();

            // Display the HTML web page
            client.println(file_s);


            // GET data

            int index_temp =0;
            
            if(header.indexOf("/?valduty=")>=0) {
              pos1 = header.indexOf('=');
              pos2 = header.indexOf('_');


              // String with motor position
              valueString = header.substring(pos1+1, pos2);
              
              // Move servo into position
               target_duty=valueString.toInt();
               
              // Print value to serial monitor
              Serial.print("target duty =");
              Serial.println(target_duty); 
            }

            if(header.indexOf("/?valdir=")>=0) {
              pos1 = header.indexOf('&')-1;
              
              // String with motor position
              valueString = header[pos1];

              inv_rotation=valueString.toInt();
              
              Serial.print("rotational direction =");
              Serial.println(inv_rotation); 
            }

              PWMCtr(inv_rotation,target_duty);
       
            // The HTTP response ends with another blank line
            client.println();
            
            // Break out of the while loop
            break;
          
          } else { 
            // New lline is received, clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}

  void PWMCtr (bool inv, int duty)
  {
    if(inv)
      ledcWrite(pwmChannel_neg,duty);
    else
      ledcWrite(pwmChannel_pos,duty);  
  }

長々とコピペしてしまってますが、ほかの箇所は無視で、重要なところは、

    file_s = f.readString();
    f.close();

で、読み込んだファイルをfile_sという文字列に格納して、

        // Display the HTML web page
        client.println(file_s);

で、htmlを一括で表示しているという所だけ。はじめの画像みたく、一行ずつprintlnでhtmlを記述していた状態から、だいぶ読みやすくなりました。

ちなみにSPIFFSでは、dataフォルダの下にサブフォルダを作成することもできます。

上のようにcssとjsファイルを個別に作成して、それぞれのフォルダに分けておくこともできます。例えば、jquery.min.jsをjsフォルダにいれて、htmlから読み込ませれば、ローカル環境でjQueryを使ったりもできて、便利ですね。

以上です。これを応用して、webページ上からユーザがSPIFFSのファイル操作ができるようになるといいな~

happy spiffs !

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です