2025年8月11日月曜日

ESP32/arduino : LittleFS の ファイルの ダウンロード

目的:

ESP32 の ファイルシステム (LittleFS) にあるファイルをダウンロードする 。

概要:

LittleFSにファイルが格納された状態でダウンロード用スケッチを書き込み、WEB経由でファイルをPC等にダウンロードする。
制限事項として、
 ファイルはカレントディレクトリのみの対応。
 ファイルの最大数は 50 固定
としている。

WEB画面サンプル

スケッチコード:

以下にスケッチのコードを示す。
// +==========================================================================+
// |  LittleFS data download                                                  |
// +==========================================================================+
#include <WiFi.h>                                //
#include <WebServer.h>                           // WebServer 使用の為
#include <ArduinoJson.h>                         // JSOM 使用 の為
#include "FS.h"                                  // LittleFS 使用に必要
#include "LittleFS.h"                            // LittleFS 使用に必要
//                                               //
// +--------------------------------------------------------------------------+
// | WiFi 設定 他                                                             |
// +--------------------------------------------------------------------------+
const char* ssid = "XXXXXXXXXXXXXXX";            // WiFi AP  SSID
const char* password = "xxxxxxxxxxxxx";          // WiFi AP  パスワード
//                                               //
IPAddress ip(192, 168, 1, 32);                   // ESP32 IPアドレス
IPAddress gateway(192,168, 1, 1);                //       ゲートウェイアドレス
IPAddress subnet(255, 255, 255, 0);              //       サブネットマスク
IPAddress DNS(192, 168, 1, 91);                  //       DNS アドレス
//                                               //
WebServer server(80);                            // WebServer オブジェクト生成
//                                               //
// +--------------------------------------------------------------------------+
// | グローバル変数                                                           |
// +--------------------------------------------------------------------------+
String filelist[50] ;                            // ファイルリスト 最大 50
int fileCount = 0 ;                              // ファイル数 カウント用
//                                               //
// +--------------------------------------------------------------------------+
// | Main HTML , JAVA Script                                                  |
// +--------------------------------------------------------------------------+
const char* htmlPage = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    .filelist{width:400px;}
    input {margin:4px;width:100px;}
    div {font-size:16pt;text-align:center;width:350px;}
  </style>
  <title>LittleFS ファイルリスト</title>
</head>
<body>
  <h3>LittleFS dataリスト</h3>
      <div>
        <form method='get'>
          <input type='button' name='btn1' value='更新' onclick='loadList()' >
          <input type='button' name='btn2' value='ダウンロード' onclick='getSelVal()'>
          <input type='button' name='btn3' value='削除' onclick='removeFile()'>
        </form>
      </div>
  <select class="filelist" id="listBox" size="5" ></select>

  <script>
    // ファイルリストを取得 ---------------------
    async function loadList() {                  // ファイルリスト取得
      const response = await fetch("/list");     // fetch で リストをjsonで 取得
      const data = await response.json();        // json の解釈
      const listBox = document.getElementById("listBox");  // listBox オブジェクト取得
      listBox.innerHTML = "";                    // ListBox を一度クリア
      data.filelist.forEach(item => {            // ListBox にファイルリストを 格納
        const option = document.createElement("option");
        option.text = item;
        listBox.add(option);
      });
    }
    // ファイルを取得 ---------------------------
    async function getSelVal() {                 //  選択したリストをダウンロードする
      // リストの選択した値からファイル名を取得する
      const selectElement = document.getElementById("listBox");  // listBox の要素を取得
      const selectedValue = selectElement.value ;                // 要素の値を取得
      if (selectedValue != "") {                                 // 要素が選択されている場合のみ実行
        const selectedVArry = selectedValue.split("\/");         // 値を "/" で分割
        const selectedfname= selectedVArry[selectedVArry.length - 1];  // 分割した最後の値(ファイル名)を取得
        // ファイル名の内容を要求し、ダウンロードする
        try {
          const response = await fetch("/data?file=" + selectedValue) ;  // GET /data?file=<ファイル名> 発行
          const bdt = await response.blob() ;                    // 応答を blob として受信して bdt に格納
          const url = window.URL.createObjectURL(bdt) ;          // blobのダウンロード先URLを生成
          const a = document.createElement('a') ;                // a タグを生成
          a.download = selectedfname ;                           // download属性にファイル名を設定
          a.href = url ;                                         // href属性に URLを設定
          a.click() ;                                            // a タグをクリックしてダウンロード実行
          window.URL.revokeObjectURL(url) ;                      // url オブジェクトの開放
        } catch (error) {                                        // ファイル取得できなかったときの処理
          alert("file read error") ;                             //
        }
      }
    } 
    // ファイルを削除 ---------------------------
    async function removeFile() {                                //
      // リストの選択した値からファイル名を取得して、削除要求を発行する
      const selectElement = document.getElementById("listBox");  // listBox の要素を取得
      const selectedValue = selectElement.value ;                // 要素の値を取得
      if (selectedValue != "") {                                 // 要素が選択されている場合のみ実行
        const response = await fetch("/del?file=" + selectedValue) ; // GET /del?file=<ファイル名> 発行
        loadList() ;                                             // ファイルリスト取得
      }                                                          //
    }
    // 最初にファイルリストを取得する                            //
    loadList() ;                                                 //
  </script>
</body>
</html>
)rawliteral";
//                                               //
// +--------------------------------------------------------------------------+
// | ハンドル処理 : WEBからの要求に対する応答処理                             |
// +--------------------------------------------------------------------------+
void handleRoot() {                              //メイン HTMLページを返す
  get_client_msg() ;                             // *- デバッグ用
  if (server.hasArg("btn2")) {                   // |    受信メッセージを表示
    Serial.println("  hit btn2 ") ;              // |
  }                                              // +---------------
  server.send(200, "text/html", htmlPage);       // HTMLを送信
}                                                //
// ファイルのリストを作成して送信                //
void handleList() {                              //ファイルリスト要求の応答
  DynamicJsonDocument doc(512);                  // JSON データ用変数
  JsonArray arr = doc.createNestedArray("filelist");// JSON配列を作成(キー:filelist)
  Serial.println("Client access /list ") ;       // デバッグ用表示
  listDir("/") ;                                 // ファイルリスト取得
  for (int i = 0; i < fileCount; i++) {          //   ファイルリストを 
    arr.add(filelist[i]);                        //   JSON配列に追加
  }                                              //
  String json;                                   // JSON 文字列変数
  serializeJson(doc, json);                      // JSONを文字列に変換
  Serial.println("json : ") ;                    // +- デバッグ用表示
  Serial.println(json) ;                         // +----------------
  server.send(200, "application/json", json);    // JSONデータを送信
}                                                //
// 選択されたファイルのデータを送信              //
void handleFileData() {                          // ファイル送信要求の応答
  String sdt = "Client access /data \n" ;        // +- デバッグ用表示
  sdt += get_client_msg() ;                      // |
  Serial.println(sdt) ;                          // +----------------
  String fname = server.arg("file") ;            // file の値(ファイル名)取得
  Serial.println("fname : " + fname ) ;          // +- デバッグ用表示
  File fp = LittleFS.open(fname,"r") ;           //  ファイルをオープンする
  if (!fp) {                                     //  オープン失敗の場合
    server.send(404, "text/plain", "File not found"); //  
    return ;                                     //
  }                                              //
  server.streamFile(fp, "application/octet-stream"); //  ファイル送信
  fp.close();                                    // ファイルをクローズ
}                                                //
// 選択されたファイルのデータを削除              //
void handleFileDel() {                           //
  String sdt = "Client access /del \n" ;         // +- デバッグ用表示
  sdt += get_client_msg() ;                      // |
  Serial.println(sdt) ;                          // +----------------
  String fname = server.arg("file") ;            // file の値(ファイル名)取得
  Serial.println("fname : " + fname ) ;          // +- デバッグ用表示
  String res = del_LittleFS(fname) ;             // ファイルを削除
  server.send(200, "text/plain",res) ;           // 結果を送信
}                                                //
// URLが不明な場合にメッセージを送信             //
void handleNotFound() {                          // 不明なURL受診時の処理
  String message = "File Not Found\n\n";         //   接続先のURLが 無い場合の
  message += get_client_msg() ;                  //   メッセージ作成
  server.send(404, "text/plain", message);       // メッセージを WEB  に送信
}                                                //
// 受信した要求の内容を文字列に格納して返す      //
String get_client_msg() {                        //  受信した要求の内容を作成
  String s_req = "request param ----\n";         //    Clientからの要求
  s_req += "URI: " + server.uri() + "\n" ;       // 
  s_req += "Method: ";                           //
  s_req += (server.method() == HTTP_GET) ? "GET\n" : "POST\n"; //
  s_req += "Arguments: " + String(server.args()) + "\n" ; //
  for (uint8_t i = 0; i < server.args(); i++) {  //
    s_req += " " + server.argName(i) + ": " ;    //
    s_req += server.arg(i) + "\n" ;              //
  }                                              //
  s_req += "------------------ \n";              //
  Serial.print(s_req);                           // シリアルに表示
  return s_req ;                                 // 呼び出し元に文字列を応答
}                                                //                                                 
// *--------------------------------------------------------------------------+
// |  処理関数 : arduino (ESP32) 内部処理用関数                               |
// +--------------------------------------------------------------------------+
//                                               //
void listDir( const char *dirname) {             // ファイルリストの取得
  Serial.printf("Listing directory: %s\r\n", dirname); //
  File root = LittleFS.open(dirname);            // 指定ディレクトをオープン
  if (!root) {                                   //
    Serial.println("- failed to open directory");//
    return;                                      //
  }                                              //
  if (!root.isDirectory()) {                     // ディレクトリか判定
    Serial.println(" - not a directory");        //   ディレクトリでなければ
    return;                                      //    戻る
  }                                              //
  //                                             //
  fileCount = 0 ;                                // ファイル数カウント 初期化
  File file = root.openNextFile();               // 最初のファイルをオープンする
  while (file) {                                 //   ファイルがある限りループ
    if (file.isDirectory()) {                    //   ディレクトリか判定
      Serial.print("  DIR : ");                  //   シリアルに情報表示
      Serial.println(file.name());               //
    //if (levels) {                              //   指定階層まで再帰的に
    //  listDir(file.path()) ;                   //   リストを取得
    //}                                          //     * 非対応
    } else {                                     //   ファイルの場合の処理
      Serial.print("  FILE: ");                  //    +--シリアルに情報表示
      Serial.print(fileCount);                   //    |
      Serial.print("  ");                        //    |
      //Serial.print(file.name());               //    |
      Serial.print(file.path());                 //    |
      Serial.print("\tSIZE: ");                  //    |
      Serial.println(file.size());               //    +--------------------
      //filelist[fileCount] = String(file.path()) + file.name() ; // リストにファイル名を追加
      filelist[fileCount] = file.path();         // リストにファイル名を追加
      fileCount ++ ;                             //    ファイル数をインクリメント
      if (fileCount <50) {                       // 
        file = root.openNextFile();              //    次のファイルをオープン
      } else {                                   //
        break ;                                  //
      }                                          //
    }                                            //
  }                                              //
}                                                //
//                                               //
String del_LittleFS(String fname) {              // LittleFS のファイル削除
  String result ;                                //
  Serial.println(" ------ Check_file ----------") ; //
  if (LittleFS.exists(fname)) {                  // fname ファイル存在確認
    Serial.println(fname + " 有り") ;            // あった場合の表示
  } else {                                       //
    Serial.println(fname + " なし") ;            // 無かった場合の表示
  }                                              //
  if (LittleFS.remove(fname)) {                  // fname ファイル削除
     Serial.println(fname + " 削除成功") ;       // 成功時の表示
     result = "success" ;                        //
  } else {                                       //
     Serial.println(fname + " 削除失敗") ;       // 失敗時の表示
     result = "failure" ;                        //
  }                                              //
  return result ;                                // 
}                                                //
// *--------------------------------------------------------------------------+
// |  初期設定                                                                |
// +--------------------------------------------------------------------------+
void setup()                                     //
{                                                //
  Serial.begin(115200);                          // シリアル出力開始
  WiFi.config(ip, gateway, subnet, DNS);         // ESP32 WiFi 設定
  delay(10);                                     //
  Serial.println();                              // デバッグ用出力
  Serial.print("Connecting to ");                // :
  Serial.println(ssid);                          // :
  WiFi.begin(ssid, password);                    // WiFi 開始
  while (WiFi.status() != WL_CONNECTED) {        // WiFi 接続待ち
      delay(500);                                //
      Serial.print(".");                         //
  }                                              //
  Serial.println("");                            // デバッグ用出力
  Serial.println("WiFi connected.");             // 接続先の IP address を
  Serial.println("IP address: ");                // シリアルに出力
  Serial.println(WiFi.localIP());                //
  //                                             //
  // ハンドル処理の登録                          //
  server.on("/", HTTP_GET, handleRoot);          // メインHTML の設定
  server.on("/list", handleList);                // ファイルリスト要求の応答
  server.on("/data", handleFileData) ;           // ファイル送信要求の応答
  server.on("/del", handleFileDel) ;             // ファイル削除要求の応答
  server.onNotFound(handleNotFound) ;            // 存在しない URL の場合
  //                                             //
  // WEBサーバー開始                             //
  server.begin();                                // WiFi サーバ 開始
  if (!LittleFS.begin(true)) {                   // LittleFS 開始
    Serial.println("LittleFS のマウントに失敗") ; //
    return ;                                     //
  }                                              //
}                                                //
//                                               //
// +--------------------------------------------------------------------------+
// | メインループ                                                             |
// +--------------------------------------------------------------------------+
void loop() {                                    //
  // クライアント処理                            //
  server.handleClient();                         //
  //                                             //
}                                                //
//                                               //

処理概要:

処理の概要は以下の通り。
  • WEB画面
    「更新」,「ダウンロード」,「削除」 のボタンを持ち、それぞれクリックされたら javascript を呼び出す。 また、リストボックスを設置する。
  • JavaScript
    各ボタンに対応する function を持つ。
    loadList : 更新時、ファイルリストを ESP32 へ要求し、結果(json) をリストボックスに表示する。
    getSelVal : ダウンロード時、選択されているファイル名のファイルを ESP32 へ要求し、結果(BSOB)をPC にダウンロードする。
    removeFile : 削除時、選択されているファイル名のファイルの削除を ESP32 へ要求し、終了後、LoadList を実行する。
  • ハンドル処理
    WEBクライアントからの各要求に対する応答処理を行う。
    handlRoot() : main HTML の送信を行う。
    handleList() : ファイルリスト要求に対する応答。
    LittleFS のファイルリストを作成し、JSON形式でWEBクライアントに送信する。

    handleFileData() : ファイル要求に対する応答。
    要求された LittleFS のファイルを server.streamFile でWEBクライアントに送信する。

    handleFileDel() : 削除要求に対する応答。
    要求された LittleFS のファイルを削除し、結果(成否 の文字列) をWEBクライアントに送信する。

    handleNotFound() : 不明なURL受診時の処理。
    クライアントからの受信内容をWEBクライアントに 404 で送信する。

  • 内部処理
    listDir() : LittleFS のファイルリストを作成する。
    del_LittleFS() : LittleFS から指定したファイルを削除する。
各ハンドル処理を setup() で登録し、loop() で server.handleClient() を呼び出す。
その他、詳細は スケッチコード内のコメントを参照。

0 件のコメント:

コメントを投稿