Minimum FTP Client

Hi Folks,

I am working on a project which reads some sensors, incorporates the sensor data into some HTML which it writes into a file on an SD card, then I want the card uploaded to a web server via FTP. There are a few examples of FTP use around but I can't for the life of me figure out what the minimum is to send a file, because a lot of the examples use whether a definition is defined or not, or in some cases even user input via serial, to determine whether the sketch acts as a client or server, or sends or receives files. Can someone help me out with some bare minimum info so I can add the bells and whistles myself later and not have them confuse me as to where the base functionality is?

Do you mean this code?
http://playground.arduino.cc/Code/FTP

Do you have a Mega2560 or Uno? If you have an Uno, there is just enough memory to use one part of the FTP client code, either upload or download, but not both. You can use both at the same time if you have a Mega.

edit: If you are not familiar with FTP, two ethernet sockets and the SD must be open at the same time. That takes a bit of memory, and is not that easy to understand.

The reason it is written with the defines is if you have a Mega, you can replace the defines with if..else statements to use both upload and download with the minimum code size.

That's actually one of the examples I'm having trouble with.

I have a Uno (or more accurately a Freetronics EtherTen), and I only need to upload (not download). I'm very familiar with the FTP protocol but not good at looking at an Arduino sketch which sets out initialising an SD card then parallel code for upload and download (switched with #defines) as well as a range of error handling, and distilling out just what I need. Effectively I'm trying to figure out what the contents of a function void send_FTP(file) would look like.

That should not be difficult. Remove
the part between the "#ifdef FTPWRITE" and the "#else"
or
the part between the "#ifndef FTPWRITE" or "#else" and #endif".

All are in the doFTP() function. There are only 4. I did the hard part. :slight_smile:

hello SuferTim, Really need your help with the FTP code. I am using a arduino UNO and only want it to download the file to the SD card already made the changes you said to make. I have tried using it with FileZilla, Windows FTP and FTP rush. It does not connect in any of these clients when i ping the arduino IP it goes through. See here your code adjusted.
I really just need to get one text file to the Arduino over the network, thats all........ please help.

/*
   FTP passive client for IDE v1.0.1 and w5100/w5200
   Posted October 2012 by SurferTim
*/

#include <SD.h>
#include <SPI.h>
#include <Ethernet.h>
// comment out next line to write to SD from FTP server

// this must be unique
byte mac[] = { xxxxxxxxxxxxxxxxxxxxx };  

// change to your network settings
IPAddress ip(xxxxxxxxxxxx);   
IPAddress subnet( xxxxxxxxxxxxx );

// change to your server
IPAddress server( The IP of my pc on the network);

EthernetClient client;
EthernetClient dclient;

char outBuf[128];
char outCount;

// change fileName to your file (8.3 format!)
char fileName[13] = "mydata.txt";

void setup()
{
  Serial.begin(9600);

  pinMode(10,OUTPUT);
  digitalWrite(10,HIGH);

  if(SD.begin(4) == 0)
  {
    Serial.println(F("SD init fail"));          
  }

  Ethernet.begin(mac, ip, subnet); 
  digitalWrite(10,HIGH);
  delay(2000);
  Serial.println(F("Ready. Press f or r"));
}

void loop()
{
  byte inChar;

  inChar = Serial.read();

  if(inChar == 'f')
  {
    if(doFTP()) Serial.println(F("FTP OK"));
    else Serial.println(F("FTP FAIL"));
  }

  if(inChar == 'r')
  {
    readSD();    
  }

}

File fh;

byte doFTP()
{
  SD.remove(fileName);
  fh = SD.open(fileName,FILE_WRITE);

  if(!fh)
  {
    Serial.println(F("SD open fail"));
    return 0;    
  }

  Serial.println(F("SD opened"));

  if (client.connect(server,21)) {
    Serial.println(F("Command connected"));
  } 
  else {
    fh.close();
    Serial.println(F("Command connection failed"));
    return 0;
  }

  if(!eRcv()) return 0;

  client.println(F("USER myuser"));

  if(!eRcv()) return 0;

  client.println(F("PASS mypassword"));

  if(!eRcv()) return 0;

  client.println(F("SYST"));

  if(!eRcv()) return 0;

  client.println(F("PASV"));

  if(!eRcv()) return 0;

  char *tStr = strtok(outBuf,"(,");
  int array_pasv[6];
  for ( int i = 0; i < 6; i++) {
    tStr = strtok(NULL,"(,");
    array_pasv[i] = atoi(tStr);
    if(tStr == NULL)
    {
      Serial.println(F("Bad PASV Answer"));    

    }
  }

  unsigned int hiPort,loPort;

  hiPort = array_pasv[4] << 8;
  loPort = array_pasv[5] & 255;

  Serial.print(F("Data port: "));
  hiPort = hiPort | loPort;
  Serial.println(hiPort);

  if (dclient.connect(server,hiPort)) {
    Serial.println(F("Data connected"));
  } 
  else {
    Serial.println(F("Data connection failed"));
    client.stop();
    fh.close();
    return 0;
  }


  client.print(F("RETR "));
  client.println(fileName);

  if(!eRcv())
  {
    dclient.stop();
    return 0;
  }


  while(dclient.connected())
  {
    while(dclient.available())
    {
      char c = dclient.read();
      fh.write(c);      
      Serial.write(c); 
    }
  }

  dclient.stop();
  Serial.println(F("Data disconnected"));

  if(!eRcv()) return 0;

  client.println(F("QUIT"));

  if(!eRcv()) return 0;

  client.stop();
  Serial.println(F("Command disconnected"));

  fh.close();
  Serial.println(F("SD closed"));
  return 1;
}

byte eRcv()
{
  byte respCode;
  byte thisByte;

  while(!client.available()) delay(1);

  respCode = client.peek();

  outCount = 0;

  while(client.available())
  {  
    thisByte = client.read();    
    Serial.write(thisByte);

    if(outCount < 127)
    {
      outBuf[outCount] = thisByte;
      outCount++;      
      outBuf[outCount] = 0;
    }
  }

  if(respCode >= '4')
  {
    efail();
    return 0;  
  }

  return 1;
}


void efail()
{
  byte thisByte = 0;

  client.println(F("QUIT"));

  while(!client.available()) delay(1);

  while(client.available())
  {  
    thisByte = client.read();    
    Serial.write(thisByte);
  }

  client.stop();
  Serial.println(F("Command disconnected"));
  fh.close();
  Serial.println(F("SD closed"));
}

void readSD()
{
  fh = SD.open(fileName,FILE_READ);

  if(!fh)
  {
    Serial.println(F("SD open fail"));
    return;    
  }

  while(fh.available())
  {
    Serial.write(fh.read());
  }

  fh.close();
}

If you need only one text file from a server to the Arduino, you can use http protocol and a standard web server.

If you want to use my FTP code from the playground, I need to know what the response from the FTP download attempt.
Does it connect to the server?
Does it log in?
Does it open the data channel?

It connects and logs in and says SD opened but then it says connection failed, FTP failed. When i check the SD card the file is created but empty.

P.S. Trying to use the HTTP method now but not able to test it yet. My school network does not allow it to connect to the internet.

/*
  Web client
 
 This sketch connects to a website (http://www.google.com)
 using an Arduino Wiznet Ethernet shield. 
 
 Circuit:
 * Ethernet shield attached to pins 10, 11, 12, 13
 
 created 18 Dec 2009
 by David A. Mellis
 modified 9 Apr 2012
 by Tom Igoe, based on work by Adrian McEwen
 
 */

#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>

File myFile;

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0E, 0x07, 0xB8 };
// if you don't want to use DNS (and reduce your sketch size)
// use the numeric IP instead of the name for the server:
//IPAddress server(74,125,232,128);  // numeric IP for Google (no DNS)
char server[] = "127.0.0.1";    // name address for Google (using DNS)

// Set the static IP address to use if the DHCP fails to assign
IPAddress ip(169, 254, 144, 105); 

// Initialize the Ethernet client library
// with the IP address and port of the server 
// that you want to connect to (port 80 is default for HTTP):
EthernetClient client;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  // start the Ethernet connection:
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // no point in carrying on, so do nothing forevermore:
    // try to congifure using IP address instead of DHCP:
    Ethernet.begin(mac, ip);
  }
  // give the Ethernet shield a second to initialize:
  delay(1000);
  Serial.println("connecting...");

  // if you get a connection, report back via serial:
  if (client.connect(server, 80)) {
    Serial.println("connected");
    // Make a HTTP request:
    client.println("GET /upload.txt HTTP/1.1");
    client.println("Host: 127.0.0.1");
    client.println("Connection: close");
    client.println();
  } 
  else {
    // kf you didn't get a connection to the server:
    Serial.println("connection failed");
  }
}

void loop()
{
  // if there are incoming bytes available 
  // from the server, read them and print them:
  if (client.available()) {
    while(client.available()) {
      char c = client.read();
      myFile = SD.open("test.txt", FILE_WRITE);
      if (myFile) {
        Serial.print("Writing to test.txt...");
        myFile.println(c);
        Serial.print(c);
      }
      myFile.close();
    }
  }
  // if the server's disconnected, stop the client:
  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
    // do nothing forevermore:
    while(true);
  }
}

If the command connection opens and allows you to login, then fails at the data connection open, that is normally a FTP server firewall problem.

With the HTTP code, you can't use 127.0.0.1 for the server on the Arduino. That is localhost. If the server is on a localnet, you must use the local ip address of the server.

ok my mistake, the server is on a local host was trying to connect to a xampp site on my pc. should i use the IP of my pc then?

The server ip should be the ip of your server (PC). Replace the 'x' with your PC ip.

IPAddress server(192,168,x,x);

ok thank you.
Is the section for writing to the file and saving it structured alright?

MarlonVets:
Is the section for writing to the file and saving it structured alright?

No. You must save only the data after the header. The data is separated from the header by a blank line (double carriage return/line feed).

I would also recommend checking the first line of the server response to insure the file was found.

ok thanks very much will make the changes and post when it works.

Thanks for all the help SurferTim, IT WORKS! :smiley:
Im using an uno so cut back on some of the checking to save some space for the rest of the application. I also have to decode ID barcodes using a Line reader with it. Anyways heres is the code.

/*
  File Retreiving Web client
 
 This sketch connects to a server
 using an Arduino Wiznet Ethernet shield and retrieves a specified text file. 
 
 Circuit:
 * Ethernet shield attached to pins 10, 11, 12, 13
 
 created 18 April 2014
 by Marlon Thompson
 based on work by David A. Mellis, Tom Igoe,Adrian McEwen
  */

#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>

// MAC address from Ethernet shield sticker under board
byte mac[] = { 0x9A, 0xF2, 0xFE, 0x7E, 0x97, xxx };
IPAddress ip(169, 254, xxx, xxx); // arduino IP address
IPAddress server(169, 254,xxx, xxx);  // laptop running apache LAN IP address

File myFile;
int Save=0;

EthernetClient client; //apache web server running on port 80

void setup()
{
  Serial.begin(9600);
  
  pinMode(10, OUTPUT);
   
  if (!SD.begin(4)) {
    Serial.println("SD initialization failed!"); // Setup SD card 
    return;
  }
  Serial.println("SD initialized.");
  
  
  Ethernet.begin(mac, ip);
    Serial.println("starting arduino client");
  Serial.println();

  delay(1000);

  Serial.println("connecting...");

  if (client.connect(server, 80)) {
    Serial.println("connected");
    client.println("GET /arduino.txt HTTP/1.0"); //text file in apache htdocs folder
    client.println();
  } 
  else {
    Serial.println("connection failed");
  }
}

void loop()
{
  boolean currentLineIsBlank = true;
  if (client.available()) {
     Save=0;
    while (client.available()){
    char c = client.read();
    if (c == '\n' && currentLineIsBlank)  // check for bank line at the end of the header
    {          
      Save=1;
    }
     if (Save==1){
      Serial.print(c);
         myFile = SD.open("test.txt", FILE_WRITE);  
      if (myFile) {
        myFile.print(c);          // writing text SD txt file
    }
     }
    myFile.close();
        // every line of text received from the client ends with \r\n
    if (c == '\n') {
      // last character on line of received text
      // starting new line with next character read
      currentLineIsBlank = true;
    } 
    else if (c != '\r') {
      // a text character was received from client
      currentLineIsBlank = false;
    }
    }
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    Serial.println("Text file Retrieved and saved");
    Serial.println("");
    client.stop();
    for(;;);
  }
 
}

So the above code works fine, it copies the text file to the arduino SD but when the text file is read by the arduino it has other strange characters in it. Is there anyway i could filter the characters to only allow 0-9 and \r\n?