最近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にダウンロードできる。
メインソースはこんな風にします。
# 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機能あれば十分ですかね・・・?
・接続チェックでファイル一覧が見たい
・年・月日のフォルダを作成したい
・年月日時分秒のファイルを書き込みたい