1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ESP8266WebServerのSPIFFSファイルビューアに簡易FTPクライアントを追加する

Posted at

最近ESP32でFTPクライアントソフトが必要になり、GitHubでダウンロードして
動作確認できました。 しかしそのソフトはESP8266へは移植作業が必要でした。
ソース内容を確認するとデータ操作にて、非互換命令を使っている箇所があり
無駄な改造になりそうだったので、大改造ついでにESP8266のSPIFFSファイルビューワに
簡易FTP機能を追加することにしました。

ESP8266環境で必要なリソース

 SPIFFSを使います。

ESP32で使うには?

ESP8266WebServerの移植が必要になります。(確認済)
私の過去の日記に移植内容を書いてあります。 ※数行治す程度です。

 ESP8266とESP32はSPIFFSのopenの引数設定が違うので修正します。
"SD_MMC.h"も同程度の修正で動作します。

機能

・WindowsのGoogle ChromeよりIPアドレス/files.htmlへアクセスすることで
 =>ファイル一覧が確認できる。
 =>任意のファイルを削除できる。
 =>任意のファイルの内容をテキスト表示できる。
 =>任意のファイルをダウンロードできる。
 =>Windows上の任意のファイルをアップロードできる。
 =>任意のFTPサーバ/フォルダに任意のファイルをアップロードできる。

・WindowsのGoogle ChromeよりIPアドレス/ftp.htmlへアクセスすることで
 =>FTPサーバにある任意フォルダのファイル一覧が確認できる。
 =>任意のファイルをESP8266のSPIFFSにダウンロードできる。

メインソースはこんな風にします。

esp8266FILER.ino

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

// WiFi接続先
const char* ssid = "guest";                 // SSID
const char* password = "guest";             // PASSWORD
// FTP接続先などの設定
const char *ftpserver = "192.168.2.100";    // 接続先
const char *ftpuser = "ftpuser";            // ユーザ名
const char *ftppassword = "ftpuser";        // パスワード
const char *ftppath = "/folder/folder/";    // アクセスパス
uint16_t FTPtimeout = 1000;
char FTPoutBuf[128];
char FTPoutCount;

ESP8266WebServer server(80);
// HTMLヘッダ
const char *ESPHTMLHeader ="<html lang=\"ja\">\n<head>\n\
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=Shift_JIS\">\n\
<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n</head>\n";
char *ESPfilename = "filename";

WiFiClient client;
//------------------------------------------------------------------------------------------------------
bool FTPDisconnect( ) {
byte thisByte = 0;
  client.println( "QUIT" );
  while (!client.available()) delay(1);
  while (client.available()) {
    thisByte = client.read();
    Serial.write(thisByte);
  }
  client.stop();
  Serial.println( "FTP disconnected" );
}
//------------------------------------------------------------------------------------------------------
bool FTPReceiveCheck() {
  byte returnCode;
  byte thisByte;
  while (!client.available()) delay(1);
  returnCode = client.peek();
  FTPoutCount = 0;
  while (client.available()) {
    FTPoutBuf[FTPoutCount] = client.read();
    if (FTPoutCount < (sizeof(FTPoutBuf)-1) ) FTPoutCount++;
  }
  FTPoutBuf[FTPoutCount] = 0;
  Serial.print( FTPoutBuf );
  if (returnCode >= '4') {
    FTPDisconnect();
    return false;
  }
  return true;
}
//------------------------------------------------------------------------------------------------------
bool FTPConnect() {
  if ( !client.connect(ftpserver, 21) ) {  // 21 = FTP server
    Serial.println( "FTP connection failed" );
    return false;
  }
  if (!FTPReceiveCheck()) return false;
  Serial.println( "FTP connected" );
  Serial.println( "Send USER" );
  client.print( "USER " );    client.println( ftpuser );
  if (!FTPReceiveCheck()) return false;
  Serial.println( "Send PASSWORD" );
  client.print( "PASS " );    client.println( ftppassword );
  if (!FTPReceiveCheck()) return false;
  Serial.println( "Send SYST" );
  client.println( "SYST" );
  if (!FTPReceiveCheck()) return false;
  Serial.println( "Send Type I");
  client.println( "Type I" );
  if (!FTPReceiveCheck()) return false;
  Serial.println( "Send PASV");
  client.println( "PASV" );
  if (!FTPReceiveCheck()) return false;
  return true;
}
//------------------------------------------------------------------------------------------------------
int FTPGetHiPort( ) {
char *tStr = strtok(FTPoutBuf, "(,");
int array_pasv[6];
  for ( int i = 0 ; i < 6 ; i++ ) {
    tStr = strtok(NULL, "(,");
    array_pasv[i] = atoi(tStr);
    if (tStr == NULL)  Serial.println("Bad PASV Answer");
  }
  unsigned int hiPort = array_pasv[4] << 8;
  unsigned int loPort = array_pasv[5] & 255;
  hiPort = hiPort | loPort;
  Serial.print("Data port: ");  Serial.println(hiPort);
  return hiPort;
}
//------------------------------------------------------------------------------------------------------
bool FTPList( String dir ) {
  if ( !FTPConnect() ){
    client.stop();
    Serial.println( "FTP connection failed" );
    return false;
  }
  int hiPort = FTPGetHiPort();
  WiFiClient dclient;
  if ( !dclient.connect(ftpserver, hiPort) ) {
    Serial.println( "Data connection failed" );
    client.stop();
    return false;
  }
  Serial.println( "Data connected" );
  client.print( "NLST " );  client.println( dir );
  if (!FTPReceiveCheck()) return false;
  unsigned long _m = millis();
  while( !dclient.available() && millis() < _m + FTPtimeout) delay(1);
  String output = ESPHTMLHeader;
  FSInfo fs_info;
  SPIFFS.info(fs_info);
  output += "FTP FILER\n\
<form name='ftp' action=/ftp.html method='post'>\
<p><select name=\"mode\">\n\
<option value=\"none\" selected>-------</option>\
<option value=\"ftpdownload\">download file</option>\
</select><br>\n";
  while( dclient.available() ) {
    String filename = String(dclient.readStringUntil('\n')).substring(1);
    output += "<input type=\"radio\" name=\"remove\" value=\"";
    output += filename;
    output += "\">";
    output += filename;
    output += "\n<br>";
    Serial.println( filename );
  }
  output += "<input type=submit value=OK target=tif>\n</form>\n</html>\n";
  server.send(200, "text/html", output);
  dclient.stop();
  Serial.println( "Data disconnected" );
  if (!FTPReceiveCheck()) return false;
  return FTPDisconnect();
}
//------------------------------------------------------------------------------------------------------
// 選択したファイルをSPIFFSにダウンロードする
bool FTPdownload( ){
  String path = server.arg(1).substring( 0, server.arg(1).indexOf( '\xd', 1 ) );  // なぜか末尾に改行らしきゴミが付いているので消す
  String ESPpath;
  int pos = path.lastIndexOf( '/', path.length() );
  if( pos>0 ) ESPpath = path.substring( pos-1 );
  else        ESPpath = "/" + path;
  path = "/" + server.arg(1);
  Serial.print( "FTP download file : " );   Serial.println( path );
  Serial.print( "ESP filename : " );        Serial.println( ESPpath );
  SPIFFS.remove( ESPpath );
  File fh = SPIFFS.open( ESPpath, "w" );
  if (!fh) {
    Serial.println( "SPIFFS create file failed" );
    return false;
  }
  if ( !FTPConnect() ){
    client.stop();
    Serial.println( "FTP connection failed" );
    return false;
  }
  int hiPort = FTPGetHiPort();
  WiFiClient dclient;
  if ( !dclient.connect(ftpserver, hiPort) ) {
    Serial.println( "Data connection failed" );
    client.stop();
    fh.close();
    SPIFFS.remove( ESPpath );
    return false;
  }
  Serial.println( "Data connected" );
  client.print( "RETR " );  client.println( path );
  if ( !FTPReceiveCheck()) {
    dclient.stop();
    fh.close();
    SPIFFS.remove(path);
    Serial.println( "RETR error" );
    return false;
  }
  Serial.println( "RETR file" );
  #define bufSizeFTP 1460
  uint8_t clientBuf[bufSizeFTP];
  size_t clientCount = 0;  
  while (dclient.connected()) {
    while (dclient.available()) {
      clientBuf[clientCount] = dclient.read();
      clientCount++;
      if (clientCount > (bufSizeFTP - 1)) {
        fh.write((const uint8_t *) &clientBuf[0], bufSizeFTP);
        clientCount = 0;
        Serial.print( "." );
      }
    }
  }
  if (clientCount > 0)
    fh.write((const uint8_t *) &clientBuf[0], clientCount);
  Serial.println( "end" );
  fh.close();
  dclient.stop();
  Serial.println( "Data disconnected" );
  return FTPDisconnect();
}
//=====================================================================================
// SPIFFSで選択したファイルをFTPサーバーにアップロードする
bool ESPFTPupload( ){
  String path = server.arg(1).substring( 0, server.arg(1).indexOf( '\xd', 1 ) );  // なぜか末尾に改行らしきゴミが付いているので消す;
  String FTPpath = ftppath + server.arg(1);
  path = "/" + path;
  Serial.print( "ESPfile upload : " );  Serial.println( path );
  Serial.print( "FTP file path : " );    Serial.println( FTPpath );
  if (!SPIFFS.exists( path.c_str() )) {
    Serial.println( "SPIFFS file not found" );
    return false;
  }
  if ( !FTPConnect() ){
    client.stop();
    Serial.println( "FTP connection failed" );
    return false;
  }
  int hiPort = FTPGetHiPort();
  WiFiClient dclient;
  if ( !dclient.connect(ftpserver, hiPort) ) {
    Serial.println( "Data connection failed" );
    client.stop();
    return false;
  }
  Serial.println( "Data connected" );
  Serial.println( "Send STOR filename" );
  client.print( "STOR " );  client.println( FTPpath );
  if (!FTPReceiveCheck()) {
    Serial.println( "STOR command failed" );
    dclient.stop();
    return false;
  }
  File fh = SPIFFS.open( path.c_str(), "r" );
  if (!fh) {
    Serial.println( "SPIFFS file open fail" );
    return false;
  }
  if (!fh.seek((uint32_t)0, SeekSet)) {
    Serial.println( "SPIFFS file Rewind fail" );
    fh.close();
    return false;
  }
  Serial.println( "Writing file" );
  // for faster upload increase buffer size to 1460
  //#define bufSizeFTP 64
  #define bufSizeFTP 1460
  uint8_t clientBuf[bufSizeFTP];
  size_t clientCount = 0;  
  while (fh.available()) {
    clientBuf[clientCount] = fh.read();
    clientCount++;
    if (clientCount > (bufSizeFTP - 1)) {
      dclient.write((const uint8_t *) &clientBuf[0], bufSizeFTP);
      clientCount = 0;
      Serial.print( "." );
    }
  }
  if (clientCount > 0) dclient.write((const uint8_t *) &clientBuf[0], clientCount);
  fh.close();
  dclient.stop();
  Serial.println( "Data disconnected" );
  return FTPDisconnect();
}
//=====================================================================================
// 選択ファイルをテキストで表示する
void ESPlist(){
  Serial.print( "display text : " );  Serial.println( server.arg(1) );
  String dataType = "text/plain";
  String path = "/" + server.arg(1);
  File dataFile = SPIFFS.open( path, "r");
  if (server.streamFile(dataFile, dataType) != dataFile.size())
    Serial.println( "Sent less data than expected!" );
  dataFile.close();
}
//=====================================================================================
// 選択ファイルを起動する(htmlなら普通に表示
// ※※※拡張子に従って実行する処理を書きます※※※
void ESPexec(){
  Serial.print( "execute file : " );  Serial.println( server.arg(1) );
  String path = "/" + server.arg(1);
  //loadFromDataFile( path );     
}
//=====================================================================================
// 削除ファイルを実際に削除する
void ESPremove(){
  String path = "/" + server.arg(1);
  SPIFFS.remove(path);
  Serial.print( "removed file : " );  Serial.println( server.arg(1) );
}
//=====================================================================================
// アップロード用htmlを表示する
void ESPuploadhtml( ){
  Serial.println( "execute : upload.html" );
  String output = ESPHTMLHeader;
  output += "\
<form method=\"POST\" action=\"/upload\" enctype=\"multipart/form-data\">\n\
<p>Select upload file</p>\n<input type=\"file\" name=\"file\"/><br>\n\
<input type=\"submit\" value=\"SUBMIT\">\n</form>\n</html>\n";
  server.send(200, "text/html", output);
}
//=====================================================================================
// ファイル一覧及び削除ファイルの選択
void ESPfiles( ){
  if( 0<server.args() ){
    if( server.arg(0)=="remove" )         ESPremove();
    if( server.arg(0)=="list" ){          ESPlist();          return; }
    if( server.arg(0)=="exec" ){          ESPexec();          return; }
    if( server.arg(0)=="/upload.html" ){  ESPuploadhtml();    return; }
    if( server.arg(0)=="ftpupload" ){
      if( ESPFTPupload( ) ){              FTPList( ftppath ); return; }
    }
  }
  String output = ESPHTMLHeader;
  FSInfo fs_info;
  SPIFFS.info(fs_info);
  output += "SPIFFS FILER<br>total:" + String(fs_info.totalBytes/1024);
  output += " Kbytes  /  " + String((fs_info.totalBytes - fs_info.usedBytes)/1024);
  output += " Kbytes free\n<br>\
<form name='files' action=/files.html method='post'>\
<p><select name=\"mode\">\n\
<option value=\"exec\" selected>exec file</option>\
<option value=\"list\">source list</option>\
<option value=\"/upload.html\">upload file</option>\
<option value=\"remove\">removed file</option>\
<option value=\"ftpupload\">FTP upload</option>\
</select><br>\n";
  Dir dir = SPIFFS.openDir( server.arg("dir") );
  while(dir.next()){
    File entry = dir.openFile("r");
    output += "<input type=\"radio\" name=\"remove\" value=\"";
    output += String(entry.name()).substring(1);
    output += "\">";
    output += String(entry.name()).substring(1);
    output += " : ";
    if( (dir.fileSize()/1024)>9 ){ output += String(dir.fileSize()/1024);  output += " Kbytes\n<br>";
    }else{                         output += String(dir.fileSize());       output += " bytes\n<br>";    }
    entry.close();
  }
  output += "<input type=submit value=OK target=tif>\n</form>\n</html>\n";
  server.send(200, "text/html", output);
}
//=====================================================================================
// upload.htmlで選択したファイルをアップロードする
File fsUploadFile;
void ESPhandleFileUpload(){
  HTTPUpload& upload = server.upload();
  if ( upload.status == UPLOAD_FILE_START){
    String filename = "/" + upload.filename;
    Serial.print( "upload [" );
    Serial.print( filename );
    Serial.print( "] start:");
    fsUploadFile = SPIFFS.open( filename, "w");
  } else if ( upload.status == UPLOAD_FILE_WRITE){
    if ( fsUploadFile)
      fsUploadFile.write(upload.buf, upload.currentSize);
    Serial.print( upload.currentSize );
    Serial.print( ":" );
  } else if( upload.status == UPLOAD_FILE_END){
    if ( fsUploadFile )
      fsUploadFile.close();
    Serial.println( "end" );
  }
}
//=====================================================================================
// ftp.htmlの処理
void ESPftp( ){
  if( 0<server.args() ){
    if( server.arg(0)=="ftpdownload" ){
      if( FTPdownload()==true ){
        ESPfiles();
        return;
      }
    }
  }
  FTPList( ftppath );
}
//=====================================================================================
void setup(void){  
  Serial.begin(115200);
  SPIFFS.begin();
  
  // Connect to WiFi network
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");  
  
  // Wait for connection
  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());

  if (!MDNS.begin("esp8266")) {
    Serial.println("Error setting up MDNS responder!");
    while(1) { 
      delay(1000);
    }
  }
  // Start TCP (HTTP) server
  server.begin();
  Serial.println("web server started");
  // Add service to MDNS-SD
  MDNS.addService("http", "tcp", 80);
  //
  server.on("/upload", HTTP_POST, [](){ ESPfiles(); }, ESPhandleFileUpload);
  server.on("/files.html",  ESPfiles);
  server.on("/upload.html", ESPuploadhtml);
  server.on("/ftp.html",    ESPftp);
}
//=====================================================================================
void loop(void){
  server.handleClient( );
  MDNS.update();
}

あとがき
 FTP側もファイラーで作ろうかと思ったのですが、用意するFTPサーバで
全権限持ったユーザーを開放させるのは「無謀」かな?と思ったので
Upload/Downloadを作ってファイル内容の確認を行って良しとしました。

私の使い方だと以下の3機能あれば十分ですかね・・・?
・接続チェックでファイル一覧が見たい
・年・月日のフォルダを作成したい
・年月日時分秒のファイルを書き込みたい

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?