これまでサーボモータやプラレールなどをwebサーバ経由で動かしてきましたが、問題はhtmlコードをスケッチに組み込んで、下の用に煩雑なコードになってしまうことでした。
SPIFFSというフラッシュメモリ内にファイルシステムを実装する仕組むを使うことで、データや画像ファイルだけでなく、webサーバのもとになるhtmlやcssファイルを保存し、プログラム実行時にそのファイルを読みに行くことができます。
無理やり組み込んでいたhtmlコードを外部ファイルとして、取り扱うことで、見づらいコードともおさらばできるって寸法です。
どうすればできるか?を今回まとめました。本記事の内容は下のサイトを参考に書いています。
内容としては、最初のほうはまんまですが、個人的な覚書として、つづっていきます。後半戦は、以前つくったDCモータをWebサーバ経由で動かしたコードを例にして、SPIFFSを適用してみます。
Arduino IDEにファイルシステムアップローダをインストールする
ESP32のファイルシステムを利用するのはスケッチ上でコードを書いても実現できますが、不便なので、下記のようなプラグインが用意されているらしいので、これをいっときます。
ダウンロードして、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 !