How do I read TCP data on the Arduino (with Ethernet shield)

I apologize if this was answered somewhere else, but what I could find didn't answer my question - or I may not have understood the answer (Hit me with the 2x4 of logic if that's the case!). I've just started learning how TCP communication works, and I'm having trouble wrapping my head around how to read data that is coming to the Arduino. I can send data, and I can see how to get a single character, but I'm not grasping how to concatenate a string of data coming in.

Here is what I have tried:

// x is the length of a command, usually 4
char command[x]
...
  while(client)
  {
      for(int a = 1; a < x; a++)
      {
        command[a] = client.read();
      }
      // do something with [i]command[/i]
  }

I'm not sure if this is even close to the right way to do this. Can anyone help with this? Sample code, maybe?

Thanks in advance!

Michael

This is serial, not TCP, but the idea is the same:

Web server test code that forms a string from data sent and displays the result in the serial monitor.

//zoomkat 3-25-12
//submit box code
//for use with IDE 1.0
//open serial monitor to see what the arduino receives
//use the \ slash to escape the " in the html 
//address will look like http://192.168.1.102:84 when submited
//for use with W5100 based ethernet shields
//note that the below bug fix may be required
// http://code.google.com/p/arduino/issues/detail?id=605 

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

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address
byte ip[] = { 192, 168, 1, 102 }; // ip in lan
byte gateway[] = { 192, 168, 1, 1 }; // internet access via router
byte subnet[] = { 255, 255, 255, 0 }; //subnet mask
EthernetServer server(84);; //server port

String readString; 

//////////////////////

void setup(){

  pinMode(4, OUTPUT); //pin selected to control
  //start Ethernet
  Ethernet.begin(mac, ip, gateway, subnet);
  server.begin();

  //enable serial data print 
  Serial.begin(9600); 
  Serial.println("servertest1"); // so I can keep track of what is loaded
}

void loop(){
  // Create a client connection
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        //read char by char HTTP request
        if (readString.length() < 100) {

          //store characters to string 
          readString += c; 
          //Serial.print(c);
        } 

        //if HTTP request has ended
        if (c == '\n') {

          ///////////////
          Serial.println(readString);

          //now output HTML data header

          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println();

          client.println("<HTML>");
          client.println("<HEAD>");
          client.println("<TITLE>Arduino GET test page</TITLE>");
          client.println("</HEAD>");
          client.println("<BODY>");

          client.println("<H1>HTML form GET example</H1>");

          client.println("<FORM ACTION=\"http://192.168.1.102:84\" method=get >");

          client.println("Pin 4 \"on\" or \"off\": <INPUT TYPE=TEXT NAME=\"LED\" VALUE=\"\" SIZE=\"25\" MAXLENGTH=\"50\">
");

          client.println("<INPUT TYPE=SUBMIT NAME=\"submit\" VALUE=\"Change Pin 4!\">");

          client.println("</FORM>");

          client.println("
");

          client.println("</BODY>");
          client.println("</HTML>");

          delay(1);
          //stopping client
          client.stop();

          /////////////////////
          if(readString.indexOf("on") >0)//checks for on
          {
            digitalWrite(4, HIGH);    // set pin 4 high
            Serial.println("Led On");
          }
          if(readString.indexOf("off") >0)//checks for off
          {
            digitalWrite(4, LOW);    // set pin 4 low
            Serial.println("Led Off");
          }
          //clearing string for next read
          readString="";

        }
      }
    }
  }
}

Thanks, Nick! It took some tweaking, but I was able to get it and see where I was going wrong.

For future reference for anyone who needs help, here is the test/proof of concept code I used:

/*
    Ethernet TCP client/server communications test/proof of concept
    Programmed by Michael E. Landon on 09/20/2012
    (With parts borrowed/stolen from Nick Gammon)
    
    This is a primitive example of using the Arduino Uno w/Ethernet Shield
    with outputs controlled and monitored through a TCP connection.
    
    Reading an output's state:
      Format:    rx    (where x= output to read. 0-7 valid in this test)
      
    Writing to an output:
      Format:    wx:y  (where x = output to write to. 0-7 valid in this test)
                       (y = boolian state to write. (0 or 1))  
    
    Output 0 = pin 2, Output 1 = pin 3, etc. 
    Only 8 contiguous outputs were available for this test, hence only 8 addressed.
    
    Posted in the Arduino Forums to assist those who like me needed to see
    how it was done!
   
*/

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


// Set values below to match your network needs:
byte mac[] = {0x54, 0x52, 0x49, 0x41, 0x44, 0x00};   // MAC Address
byte ip[] = {10, 0, 0, 47};                          // Network Address
byte gateway[] = {10, 0, 0, 1};                      // Gateway Address
byte subnet[] = {255, 255, 255, 0};                  // Subnet Mask
EthernetServer server(23);                           // Set Server Port
EthernetClient client;                               // Define client
const int MAX_LEN = 10;                              // Maximum length of a command


void setup()
{
  Ethernet.begin(mac, ip, gateway, subnet);          // Start the Ethernet connection
  server.begin();                                    // Begin acting like a server
  for(int x = 2; x < 9; x++)                         // Set up pins 2-9
  {                                                  // (we will call them 0-7 in our commands)
    pinMode(x, OUTPUT);                              // Make them all outputs
  }                                                  // This is the maximum we can do with an UNO
}                                                    // A MEGA could easily handle more

void loop()
{
  char command[MAX_LEN];        // Set up the command buffer
  static int c = 0;             // Set up a counter (static so it does not get overwritten outside of this loop
  char inByte;                  // Set up a character buffer to grab the input

  client = server.available();  // Check for server availability
  if(client)
  {
    inByte = client.read();     // Read the character in the buffer
    switch (inByte)             // check for certain types
    {
    case 13:                    // carriage return: process the command
      command[c] = 0;           // make the current position a terminating null byte
      DoCommand(command);	// Process the command
      c = 0;                    // reset counter for next time
      break;                    // exit when done
    case 10:			// linefeed: Ignore it
      break;                    // and skip to the end
    default:                    // any character other than a CR or LF 
      if (c < (MAX_LEN - 1))	// put in the command string (until it's full)
      {
        command[c] = inByte;    // command concactination
        c++;                    // Increment our counter
      }
    }                           // end of switch
  }                             // end of if statement
}                               // end of main loop

void DoCommand(char * cmd)	// Command received: process it!
{
  switch (cmd[0])		// Check first character of the command:
  {
  case 'r':			// r = read pin's state
    ReadPins(cmd);		// Proceed to read the pin
    break;
  case 'w':			// w = write a state to a pin
    WritePins(cmd);		// Proceed to write to the pin
    break;
  default:
    client.println("ERROR_01"); // ERROR_01 = bad command - only 'r' and 'w' are acceptable
  }
}

void ReadPins(char * cmd)	// Read a pin state
{
  //Serial.println("Reading");	//debug	
  byte x;					
  x = cmd[1] - 46;		// We get an ASCII character of 0-7, which has a value in the range of 48-55
                                // by subtracting 46, we get a value of 2-9, which represent the pins used as outputs 0-7 for this test.
  if((x > 9) || (x < 2))	// If the result is outside this range, we throw an error.
  {
    client.println("ERROR_02"); // ERROR_02 = invalid pin number
    return;			// and exit before we do any further harm.
  }
  client.print("Output ");	// send out the TCP connection a message the output in question
  client.print(x - 2);
  if(digitalRead(x))
  {
    client.println(" is on.");  // is currently on..
  }
  else
  {
    client.println(" is off."); // or off (as the case may be)
  }
}				// done with reading

void WritePins(char * cmd)	// Write a pin state
{	
  byte x;
  x = cmd[1] - 46;		// We get an ASCII character of 0-7, which is a value in the range of 48-55
                                // by subtracting 46, we get a value of 2-9, which represent our 0-7 outputs for this test.
  if((x > 9) || (x < 2))	// If the result is outside this range, we throw an error.
  {
    client.println("ERROR_02"); // ERROR_02 = invalid pin number
    return;			// and exit before we do any further harm.
  }
  if(cmd[2] != ':')		// If the third character is not a colon, we throw an error.
  {
    client.println("ERROR_03"); // ERROR_03 = unknown separator 
    return;			// and exit before we do any further harm.
  }
  switch (cmd[3])		// check the 4th character
  {
  case '0':			// 0 = turn the output off
    digitalWrite(x, LOW);	// Write to the pin to turn it off
    client.print("Output ");	// Send out the TCP connection a message the output in question
    client.print(x - 2);
    client.println(" is off.");	// is now turned off.
    break;
  case '1':			// 1 = turn the output on
    digitalWrite(x, HIGH);	// Write to the pin to turn it on
    client.print("Output ");	// Send out the TCP connection a message the output in question
    client.print(x - 2);
    client.println(" is on.");	// is now turned on.
    break;
  default:
    client.println("ERROR_04");	// ERROR_04 = Only Boolean states allowed in this test.
  }
}

Forgive my verboseness with the comments - early in my career I had to adjust code that had no comments at all :0, which cemented in my mind a notion one of my professors hammered at us in college: "put comments on every line of code!"

This was a proof of concept for an application that involved substituting a more sophisticated solution to what was used in a previous project, which involved turning on and off some digitals via commands sent through a TCP connection.

Thanks again, Nick. Thanks to zoomkat too - I was already implementing Nick's suggestion by the time I saw your suggestion. The reading part looks similar to what I ended up using, just a slightly different approach.

Michael

OK, so after playing around with this for a bit, I ended up with the concatenation loop sometimes putting an invalid character at the beginning of the string. I have no idea why it works, but if I put a delayMicroseconds(1); in between the command

=inByte: and the c++; lines, it goes away. 

It started almost right after I posted the code (I blame Murphy), and to track down where the problem was I added lines of client.print(x) at various places to give me some idea where things were going wrong. Then I couldn't get it to fail, so I started taking the lines back out and this location was the only place that made any difference (take it out, it won't work; put it back, it does)!  So I tried a delay(1) in it's place and it worked, then tried the delayMicrosecond() and whittled it down to just 1 microsecond and it still works. Since that is essentially only delaying the program about 16 clock cycles at this point, I can live with that.

MichaelL65:

  char command[MAX_LEN];        // Set up the command buffer

static int c = 0;             // Set up a counter (static so it does not get overwritten outside of this loop

Having a static index into a non-static array raises all sorts of red flags. Presumably you intended command to be static too.

I definitely agree with PeterH there. Also:

    inByte = client.read();     // Read the character in the buffer

Try putting a test for client.available() before the read.