2일을 개고생해서 겨우 SD-Card FTP Server를 만들었습니다.
SPIFFS(ESP-8266 외부 Nor Flash memory)로 동작하는 예제는 이미 있는데,
이것을 SD-card로 동작이 되도록 고쳤습니다.
이전 글에서 정말 쉽게 되는 것인 줄 알았는데, 생각처럼 쉬운 것이 아니었습니다.
쓸데없이 수정한 부분도 있는데, 이건 제가 영어를 잘 몰라서, 해야하는 작업인 줄 알고, 코드를 수정해 버린 부분이었습니다.
File이라는 File pointer 가 SPIFFS 와 SD-Card를 동시에 그냥 사용하면 서로 겹쳐서 에러가 나게 되는데 이것을 방지하는
방법이 있었습니다.
저는 SPIFFS와 SD 기능 2개를 동시에 쓰지 않는데도 그냥 따라해서 고생을 사서 했습니다.
이 2가지를 같이 쓰는 방법은 다음 링크를 참조해 주시기 바랍니다.
file.cpp , SD.h , SD.cpp 를 수정하도록 되어 있습니다.
필요하신 분은 보고 따라하시고, 이렇게 수정해 버리면 File 로 변수를 선언하는 대신, Sd::File 로 변수를 선언해야 합니다.
이것이 필요 없으면 위의 파일들을 수정하지 않고, FS.H 를 include 하지 않고 나중에 File 로 변수를 선언하면 됩니다.
https://github.com/esp8266/Arduino/issues/1723 의 링크를 타고 Cosmicboris 란 사람의 글을 참고하시면 됩니다.
boris가 다시 다음의 내용을 참고하라고 링크를 알려 주는데, 좀 더 자세한 수정 방법이 나와있습니다.
이제 SPIFFS FTP Server 를 제가 이전에 쓴 글을 보고 라이브러리에 포함시킨 후, 예제를 엽니다.
아두이노 IDE에서 보이는 코드를 스케치 파일이라 하는데, 여기에서 고칠 부분은 간단하다.
1 2 3 4 5 6 7 8 9 10 | if (SD.begin(4)) { Serial.println("SD opened!"); ftpSrv.begin("esp8266","esp8266"); //username, password for ftp. set ports in ESP8266FtpServer.h (default 21, 50009 for PASV) } /* if (SPIFFS.begin()) { Serial.println("SPIFFS opened!"); ftpSrv.begin("esp8266","esp8266"); //username, password for ftp. set ports in ESP8266FtpServer.h (default 21, 50009 for PASV) } */ | cs |
위와같이 SPIFFS.begin() 을 SD.begin(4)로 고치면 된다.
그런데, 주의할 점은 SD.begin(4) 처럼 파라메터로 4를 넣어야 한다. 안 넣으면 동작되지 않는다.
그 다음에, 수정할 부분은 FTP Server 라이브러리에 복사해 놓은 파일들 중에서,
ESP8266FtpServer.cpp 와 ESP8266FtpServer.h 를 수정해야 한다.
ESP8266FtpServer.cpp 에서 수정된 부분은 다음과 같이 다른 색으로 표시해 놨다.
이 수정된 부분은 SPIFFS 용 함수를 SD Card용 함수로 다 바꿔야 하는 부분이다.
그리고 SD-Card 함수에는 rename 함수가 없어서, 수정한 FTP Server 프로그램은 Rename 명령이 안먹는다.
먼저 ESP8266FtpServer.cpp 에서 SPIFFS 용 헤더 파일을 뺀다.
1 2 3 4 5 6 | #include "ESP8266FtpServer.h" #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> //#include <FS.h> | cs |
ESP8266FtpServer.cpp 에서 다른 함수들은 그대로 두고,
boolean FtpServer::processCommand() 함수 부분만 수정하면 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 | boolean FtpServer::processCommand() { /////////////////////////////////////// // // // ACCESS CONTROL COMMANDS // // // /////////////////////////////////////// // // CDUP - Change to Parent Directory // if( ! strcmp( command, "CDUP" )) { client.println("250 Ok. Current directory is " + String(cwdName)); } // // CWD - Change Working Directory // else if( ! strcmp( command, "CWD" )) { char path[ FTP_CWD_SIZE ]; if( strcmp( parameters, "." ) == 0 ) // 'CWD .' is the same as PWD command client.println( "257 \"" + String(cwdName) + "\" is your current directory"); else { client.println( "250 Ok. Current directory is " + String(cwdName) ); } } // // PWD - Print Directory // else if( ! strcmp( command, "PWD" )) client.println( "257 \"" + String(cwdName) + "\" is your current directory"); // // QUIT // else if( ! strcmp( command, "QUIT" )) { disconnectClient(); return false; } /////////////////////////////////////// // // // TRANSFER PARAMETER COMMANDS // // // /////////////////////////////////////// // // MODE - Transfer Mode // else if( ! strcmp( command, "MODE" )) { if( ! strcmp( parameters, "S" )) client.println( "200 S Ok"); // else if( ! strcmp( parameters, "B" )) // client.println( "200 B Ok\r\n"; else client.println( "504 Only S(tream) is suported"); } // // PASV - Passive Connection management // else if( ! strcmp( command, "PASV" )) { if (data.connected()) data.stop(); //dataServer.begin(); //dataIp = Ethernet.localIP(); dataIp = WiFi.localIP(); dataPort = FTP_DATA_PORT_PASV; //data.connect( dataIp, dataPort ); //data = dataServer.available(); #ifdef FTP_DEBUG Serial.println("Connection management set to passive"); Serial.println( "Data port set to " + String(dataPort)); #endif client.println( "227 Entering Passive Mode ("+ String(dataIp[0]) + "," + String(dataIp[1])+","+ String(dataIp[2])+","+ String(dataIp[3])+","+String( dataPort >> 8 ) +","+String ( dataPort & 255 )+")."); dataPassiveConn = true; } // // PORT - Data Port // else if( ! strcmp( command, "PORT" )) { if (data) data.stop(); // get IP of data client dataIp[ 0 ] = atoi( parameters ); char * p = strchr( parameters, ',' ); for( uint8_t i = 1; i < 4; i ++ ) { dataIp[ i ] = atoi( ++ p ); p = strchr( p, ',' ); } // get port of data client dataPort = 256 * atoi( ++ p ); p = strchr( p, ',' ); dataPort += atoi( ++ p ); if( p == NULL ) client.println( "501 Can't interpret parameters"); else { client.println("200 PORT command successful"); dataPassiveConn = false; } } // // STRU - File Structure // else if( ! strcmp( command, "STRU" )) { if( ! strcmp( parameters, "F" )) client.println( "200 F Ok"); // else if( ! strcmp( parameters, "R" )) // client.println( "200 B Ok\r\n"; else client.println( "504 Only F(ile) is suported"); } // // TYPE - Data Type // else if( ! strcmp( command, "TYPE" )) { if( ! strcmp( parameters, "A" )) client.println( "200 TYPE is now ASII"); else if( ! strcmp( parameters, "I" )) client.println( "200 TYPE is now 8-bit binary"); else client.println( "504 Unknow TYPE"); } /////////////////////////////////////// // // // FTP SERVICE COMMANDS // // // /////////////////////////////////////// // // ABOR - Abort // else if( ! strcmp( command, "ABOR" )) { abortTransfer(); client.println( "226 Data connection closed"); } // // DELE - Delete a File // else if( ! strcmp( command, "DELE" )) { char path[ FTP_CWD_SIZE ]; if( strlen( parameters ) == 0 ) client.println( "501 No file name"); else if( makePath( path )) { //try.. if( ! SPIFFS.exists( path )) if( ! SD.exists( path )) client.println( "550 File " + String(parameters) + " not found"); else { //try.. if( SPIFFS.remove( path )) if( SD.remove( path )) client.println( "250 Deleted " + String(parameters) ); else client.println( "450 Can't delete " + String(parameters)); } } } // // LIST - List // else if( ! strcmp( command, "LIST" )) { if( ! dataConnect()) client.println( "425 No data connection"); else { client.println( "150 Accepted data connection"); uint16_t nm = 0; //try.. Dir dir=SPIFFS.openDir(cwdName); //try2.. Dir dir=SD.openDir(cwdName); //dir_t dir=SD.openDir(cwdName); sd::File dir=SD.open(cwdName); sd::File entry; //try.. if( !SPIFFS.exists(cwdName)) // try.. if( !SD.exists(cwdName)) if(!dir.isDirectory()) client.println( "550 Can't open directory " + String(cwdName) ); else { // try.. while( dir.next()) while(1) { entry = dir.openNextFile(); if (!entry) break; String fn,fs; //try.. fn = dir.fileName(); fn = entry.name(); //fn.remove(0, 1); //try.. fs = String(dir.fileSize()); fs = String(entry.size()); data.println( "Type=file;Size=" + fs + ";"+"modify=20000101160656;" +" " + fn); nm ++; } client.println( "226 " + String(nm) + " matches total"); } data.stop(); } } // // MLSD - Listing for Machine Processing (see RFC 3659) // else if( ! strcmp( command, "MLSD" )) { if( ! dataConnect()) client.println( "425 No data connection MLSD"); else { client.println( "150 Accepted data connection"); uint16_t nm = 0; //try.. Dir dir= SPIFFS.openDir(cwdName); //try.. Dir dir= SD.openDir(cwdName); sd::File dir=SD.open(cwdName); sd::File entry; char dtStr[ 15 ]; // if(!SPIFFS.exists(cwdName)) // client.println( "550 Can't open directory " +String(parameters)+ ); // else { //try.. while( dir.next()) while(1) { entry = dir.openNextFile(); if (!entry) break; String fn,fs; //try.. fn = dir.fileName(); fn = entry.name(); //fn.remove(0, 1); //try.. fs = String(dir.fileSize()); fs = String(entry.size()); data.println( "Type=file;Size=" + fs + ";"+"modify=20000101160656;" +" " + fn); //Serial.print("+r,s" + fs); //Serial.println( ",\t" + fn ); nm ++; } client.println( "226-options: -a -l"); client.println( "226 " + String(nm) + " matches total"); } data.stop(); } } // // NLST - Name List // else if( ! strcmp( command, "NLST" )) { if( ! dataConnect()) client.println( "425 No data connection"); else { client.println( "150 Accepted data connection"); uint16_t nm = 0; //try.. Dir dir=SPIFFS.openDir(cwdName); //Dir dir=SD.openDir(cwdName); sd::File dir=SD.open(cwdName); //try.. if( !SPIFFS.exists( cwdName )) if( !SD.exists( cwdName )) client.println( "550 Can't open directory " + String(parameters)); else { //try.. while( dir.next()) while( dir.openNextFile()) { //try.. data.println( dir.fileName()); data.println( dir.name()); nm ++; } client.println( "226 " + String(nm) + " matches total"); } data.stop(); } } // // NOOP // else if( ! strcmp( command, "NOOP" )) { // dataPort = 0; client.println( "200 Zzz..."); } // // RETR - Retrieve // else if( ! strcmp( command, "RETR" )) { char path[ FTP_CWD_SIZE ]; if( strlen( parameters ) == 0 ) client.println( "501 No file name"); else if( makePath( path )) { //try.. file = SPIFFS.open(path, "r"); sd::File file = SD.open(path, FILE_READ); if( !file) client.println( "550 File " +String(parameters)+ " not found"); else if( !file ) client.println( "450 Can't open " +String(parameters)); else if( ! dataConnect()) client.println( "425 No data connection"); else { #ifdef FTP_DEBUG Serial.println("Sending " + String(parameters)); #endif client.println( "150-Connected to port "+ String(dataPort)); client.println( "150 " + String(file.size()) + " bytes to download"); millisBeginTrans = millis(); bytesTransfered = 0; transferStatus = 1; } } } // // STOR - Store // else if( ! strcmp( command, "STOR" )) { char path[ FTP_CWD_SIZE ]; if( strlen( parameters ) == 0 ) client.println( "501 No file name"); else if( makePath( path )) { //file = SPIFFS.open(path, "w"); //try.. file = SD.open(path, "w"); sd::File file = SD.open(path, FILE_WRITE); if( !file) client.println( "451 Can't open/create " +String(parameters) ); else if( ! dataConnect()) { client.println( "425 No data connection"); file.close(); } else { #ifdef FTP_DEBUG Serial.println( "Receiving " +String(parameters)); #endif client.println( "150 Connected to port " + String(dataPort)); millisBeginTrans = millis(); bytesTransfered = 0; transferStatus = 2; } } } // // MKD - Make Directory // else if( ! strcmp( command, "MKD" )) { client.println( "550 Can't create \"" + String(parameters)); //not support on espyet } // // RMD - Remove a Directory // else if( ! strcmp( command, "RMD" )) { client.println( "501 Can't delete \"" +String(parameters)); } // // RNFR - Rename From // else if( ! strcmp( command, "RNFR" )) { buf[ 0 ] = 0; if( strlen( parameters ) == 0 ) client.println( "501 No file name"); else if( makePath( buf )) { //if( ! SPIFFS.exists( buf )) if( ! SD.exists( buf )) client.println( "550 File " +String(parameters)+ " not found"); else { #ifdef FTP_DEBUG Serial.println("Renaming " + String(buf)); #endif client.println( "350 RNFR accepted - file exists, ready for destination"); rnfrCmd = true; } } } // // RNTO - Rename To // else if( ! strcmp( command, "RNTO" )) { char path[ FTP_CWD_SIZE ]; char dir[ FTP_FIL_SIZE ]; if( strlen( buf ) == 0 || ! rnfrCmd ) client.println( "503 Need RNFR before RNTO"); else if( strlen( parameters ) == 0 ) client.println( "501 No file name"); else if( makePath( path )) { //if( SPIFFS.exists( path )) if( SD.exists( path )) client.println( "553 " +String(parameters)+ " already exists"); else { #ifdef FTP_DEBUG Serial.println("Renaming " + String(buf) + " to " + String(path)); #endif //try.. if( SPIFFS.rename( buf, path )) /*try.. if( SD.rename( buf, path )) client.println( "250 File successfully renamed or moved"); else client.println( "451 Rename/move failure");*/ client.println( "451 Rename/move failure"); } } rnfrCmd = false; } /////////////////////////////////////// // // // EXTENSIONS COMMANDS (RFC 3659) // // // /////////////////////////////////////// // // FEAT - New Features // else if( ! strcmp( command, "FEAT" )) { client.println( "211-Extensions suported:"); client.println( " MLSD"); client.println( "211 End."); } // // MDTM - File Modification Time (see RFC 3659) // else if (!strcmp(command, "MDTM")) { client.println("550 Unable to retrieve time"); } // // SIZE - Size of the file // else if( ! strcmp( command, "SIZE" )) { char path[ FTP_CWD_SIZE ]; if( strlen( parameters ) == 0 ) client.println( "501 No file name"); else if( makePath( path )) { //file = SPIFFS.open(path, "r"); sd::File file = SD.open(path,FILE_READ); if(!file) client.println( "450 Can't open " +String(parameters) ); else { client.println( "213 " + String(file.size())); file.close(); } } } // // SITE - System command // else if( ! strcmp( command, "SITE" )) { client.println( "500 Unknow SITE command " +String(parameters) ); } // // Unrecognized commands ... // else client.println( "500 Unknow command"); return true; | cs |
ESP8266FtpServer.h 파일에서 다음과 같이 수정. (FS.h 를 빼 버리고 SD.h 와 SPI.h 를 넣는다)
1 2 3 4 | #include <SPI.h> #include <SD.h> //#include <FS.h> #include <WiFiClient.h> | cs |
이렇게 수정하고 혹시나 라이브러리가 중복된다는 메세지가 나온다면 겹치는 라이브러리를 지워버리거나 다른 장소로 이동하면 없어진다. 아두이노는 초보라서 라이브러리가 중복되는 것을 무식하게 잡았다. ㅜㅜ
그럼 테스트 과정이다.
먼저 공유기에 접속해야 한다. 아두이노 스케치 파일에 SSID,Password 를 적는 란이 있는데, 자신의 공유기의 SSID 와 Password를 적어 주면,
1 2 3 4 5 6 7 | #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <ESP8266FtpServer.h> const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASS"; | cs |
ESP8266 이 공유기로부터 IP를 할당받아서 FTP 서버로 붙는다.
위의 SD opened! 밑 부분은 제가 시리얼로 실험하느라 출력해 본 내용인데, 위에 올린 코드에서는 지웠을 겁니다.(아니네요.. 그냥 남아 있네요.)
이제 PC용 FTP Client 접속 프로그램인 Filezilla 를 인터넷에서 다운받아서 다음과 같이 설정합니다.
일반 탭의 설정 사함은 다음과 같다.
고급탭의 설정은 다음과 같다.
이렇게 설정하고 연결을 누르면 ESP8266 FTP Server와 WiFi 로 접속이 되어 파일을 읽고 쓸 수 있다.
쓰는 것(ESP8266 -> PC 로 파일 전송)은 잘 되는데, 읽는 것(PC-> ESP8266 로 파일 전송)은 내용이 없이 이름만 생기네요. 원인을 찾아봐야 겠습니다. 이런, 보여 줄려고 해보니 이상한 점이 좀 많네요. 동작 안 될 때도 있고 .. 조금 더 손봐야 하겠습니다.
문제점 잡았습니다. 휴.. 아두이노도 잘 모르고 C++도 잘 모르는데, 2가지를 한꺼번에 하고 있으니 힘드네요. ㅜㅜ
ESP8266FtpServer.cpp 파일에서,
1 2 | //try.. file = SPIFFS.open(path, "r"); sd::File file = SD.open(path, FILE_READ); | cs |
로 잘못 바꿨던 부분을 다음처럼 수정하니까, ESP8266->PC 방향으로 FTP에 의해 데이터가 제대로 전송되는군요.
1 2 | //try.. file = SPIFFS.open(path, "r"); file = SD.open(path, FILE_READ); | cs |
그리고 또 하나 수정할 점입니다.
ESP8266FtpServer.h 파일에서 file 변수 선언을 다음처럼 수정했습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | using namespace sd; using sd::File; class FtpServer { public: void begin(String uname, String pword); void handleFTP(); private: void iniVariables(); void clientConnected(); void disconnectClient(); boolean userIdentity(); boolean userPassword(); boolean processCommand(); boolean dataConnect(); boolean doRetrieve(); boolean doStore(); void closeTransfer(); void abortTransfer(); boolean makePath( char * fullname ); boolean makePath( char * fullName, char * param ); uint8_t getDateTime( uint16_t * pyear, uint8_t * pmonth, uint8_t * pday, uint8_t * phour, uint8_t * pminute, uint8_t * second ); char * makeDateTimeStr( char * tstr, uint16_t date, uint16_t time ); int8_t readChar(); IPAddress dataIp; // IP address of client for data WiFiClient client; WiFiClient data; //SdFile file; File file; boolean dataPassiveConn; uint16_t dataPort; char buf[ FTP_BUF_SIZE ]; // data buffer for transfers char cmdLine[ FTP_CMD_SIZE ]; // where to store incoming char from client char cwdName[ FTP_CWD_SIZE ]; // name of current directory char command[ 5 ]; // command sent by client boolean rnfrCmd; // previous command was RNFR char * parameters; // point to begin of parameters sent by client uint16_t iCL; // pointer to cmdLine next incoming char int8_t cmdStatus, // status of ftp command connexion transferStatus; // status of ftp data transfer uint32_t millisTimeOut, // disconnect after 5 min of inactivity millisDelay, millisEndConnection, // millisBeginTrans, // store time of beginning of a transaction bytesTransfered; // String _FTP_USER; String _FTP_PASS; }; | cs |
바꾼게 많아서 수정한 파일들을 첨부해 놓겠습니다.
아두이노는 서툴러서 zip으로 프로젝트만 뽑아서 압축하는 방법도 아직 잘 몰라서 또 무식하게 따로따로 파일을 각각 첨부하네요.
^^
'ESP8266' 카테고리의 다른 글
[ESP8266] NODE MCU_DEVKIT_V1.0 회로도. (0) | 2017.02.13 |
---|---|
[ESP8266] NODE MCU DEV KIT V3, SD_card 테스트 (0) | 2017.02.13 |
[ESP8266] SD-Card(SPI-mode) 핀/스피드 설정 (0) | 2017.02.13 |
[ESP8266] SmartConfig 소개(Arduino IDE 버전). (0) | 2017.02.13 |
[ESP8266] WiFi FTP Server with SD-Card[5] (속도 개선) (0) | 2017.02.13 |