Pages: [1]   Go Down
Author Topic: Ethernet Web Thermostat: more efficient TCP packages.  (Read 1841 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 5
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi everybody, I'm new on the Arduino community, but till now I have read a lot about all the amazing projects done and now I'm working on my own.
Like a lot of you I have decided to construct a heating controller based on the Arduino 1.0 But I found some problems.
Some sketch using HTML web server are sending INT values to the client just by typing Client.print(int value). So if I would like to display the hour on a web page, I should do something like:

Code:
client.print(" </td><td>");
  client.print(hour());
  client.print(":");
  if (minute()<10){client.print("0");}
  client.print(minute());
  client.print(":");
  if (second()<10){client.print("0");}
  client.print(second());
  client.print(" </td><td>");

which is not efficient in the point of view of the TCP protocol. I mean, each time I'm sending a package of just some bytes (like hour() or minute() ) I have to send the hole header (20 bytes if I'm right).
So I want to make a longer string (maybe of 200-500 bytes) before sending the package.

My question is, has somebody done something similar or tried to handle this problem? Of course what I what to send is not just the hour but some data stored in a matrix. So when sending 100 values (for a 10x10 matrix) I don't need to send 100 TCP packages plus the one of the HTML code, but just few of them.

I'm working on the following code that will conform my string, but I have some weird results. If I make the buffer bigger than 10 bytes (20 for example) I will have a strange output. The same will happen if I uncomment the last "If" case and "strcat" function (with a buffer of size 10 bytes).

Any Idea to proceed?

Tnx you all!

Nico.

Code:
void setup() {
  Serial.begin(9600);
  delay(1);

char buffer[10];
int h=10;
int m=5;
int s=33;

bufer[0]='\0';
char Hora[]="La hora es: ";    //Formamos la hora con min y sec

  itoa(h,buffer,10);
  Serial.println(buffer);
  if (h<10)  {strcat(Hora,"0");}
  strcat(Hora,buffer);
  strcat(Hora,":");
 
  bufer[0]='\0';
  itoa(m,buffer,10);
  Serial.println(buffer);
  if (m<10)  {strcat(Hora,"0");}
  strcat(Hora,buffer);
  strcat(Hora,":");
 
  bufer[0]='\0';
  itoa(s,buffer,10);
  Serial.println(buffer);
//  if (s<10)  {strcat(Hora,"0");}
//  strcat(Hora,buffer);
  Serial.println(Hora);

}

void loop() {}
Logged

New River, Arizona
Offline Offline
God Member
*****
Karma: 19
Posts: 928
Arduino rocks
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I had exactly the same problem, but took a different approach to handling it.  I used the (relatively undocumented) sprintf() function.  You can find documentation using google for this.  I also used a couple of buffers for most of the things I do to keep memory usage down to a minimum.  I cut this out of the code for a device I have running that send packets to Pachube for graphing and storage.  I picked this one because it handles time and some floating point data.  sprintf() on the arduino doesn't support floats and you have to do it yourself.  It also handles the 'Content Length:' that some web sites require.

Code:
tNow = now();
  strcpy_P(Dbuf2,PSTR("Data at %02d:%02d:%02d: "));  // Debugging
  sprintf(Dbuf,Dbuf2,hour(tNow),minute(tNow),second(tNow));
//  Serial.print(Dbuf);
  // construct the data buffer so we know how long it is
  int poolMotorState = 0;
  if(strcmp(poolData.motorState,"High") == 0)
     poolMotorState = 2;
  else if(strcmp(poolData.motorState,"Low") == 0)
    poolMotorState = 1;
  strcpy_P(Dbuf2, PSTR("%d,%d,0.%d,%d.%02d,%d.%02d,%d.%02d,%d,%d,%d,%d"));
  sprintf(dataBuf,Dbuf2,
    (int)round(realPower),
    (int)round(apparentPower),
    (int)(powerFactor*100),
    (int)rmsCurrent,
    (int)(((rmsCurrent+0.005) - (int)rmsCurrent) * 100),
    (int)rmsVoltage,
    (int)(((rmsVoltage+0.005) - (int)rmsVoltage) * 100),
    (int)(frequency),
    (int)(((frequency+0.005) - (int)frequency) * 100),
    (ThermoData[0].currentTemp + ThermoData[1].currentTemp)/2,
    (int)round(outsideSensor.temp),
    poolMotorState,
    // if the pool is off, report 10, if the number is out of whack, limit it.
    poolMotorState == 0 ? 10 : (poolData.poolTemp <= 120 ? poolData.poolTemp : 120) );
    //Serial.println(dataBuf);
    //return;
  strcpy_P(Dbuf,PSTR("Pachube Connecting..."));
  Serial.print(Dbuf);
  if(pachube.connect(5000)){ // set a limit on how long the connect will wait
    strcpy_P(Dbuf,PSTR("OK..."));
    Serial.print(Dbuf);
    tNow = now();
    strcpy_P(Dbuf,PSTR("PUT /api/9511.csv HTTP/1.1\n"));
    pachube.write(Dbuf);
    strcpy_P(Dbuf,PSTR("Host: www.pachube.com\n"));
    pachube.write(Dbuf);
    strcpy_P(Dbuf,PSTR("X-PachubeApiKey: secretnumber\n"));
    pachube.write(Dbuf);
    strcpy_P(Dbuf2,PSTR("Content-Length: %d\n"));
    sprintf(Dbuf,Dbuf2,strlen(dataBuf));
    pachube.write(Dbuf);
    strcpy_P(Dbuf,PSTR("Content-Type: text/csv\n"));
    pachube.write(Dbuf);
    strcpy_P(Dbuf,PSTR("Connection: close\n\n"));  // has an extra nl to end the transaction
    pachube.write(Dbuf);
    pachube.write(dataBuf);
    pachube.write("\n");

I got tired of having a lot of if statements and special code to handle adding zeros (or not) to make it look nice and came up with this.
Logged

Trying to keep my house under control http://www.desert-home.com/

Offline Offline
Newbie
*
Karma: 0
Posts: 5
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi draythomp!

Tnx for your answer. It was very useful! But I decided to take another approach. Your solutions works because you know exactly what you are sending, so you can conform your packets before. But in my cas it will take me a really long time doing so. Then I decided to make some print functions that will be in charge of creating a buffer of maximum 200 bytes and when almost full, sending it through the net.
Just for making it easier I'm using Serial.println() instead of client.print(), but if someone wants to use it, you just have to change them (and maybe also pass the client to each of the functions).

Anyway, I would like to create a unique function to send ints or chars, but I don't know if there is an upper class common to both. Any idea?

Here is the code with one example.

Code:
const int Buffer_length = 200;
char buffer[Buffer_length];  //public variables

void setup() {
  Serial.begin(9600);
  buffer[0]='\0'; //reset the buffer
  Serial.println();
}

void loop() {
 
  //information to be send
  printI(12547); //Note that we are using int, no longs, take care with the size of the number!!!
  printC("mi amol");
  printdate(12,3,2012);
  printC("hhhhhhhhsdfvzdjjjjjjjjxhzgdbashxb"); //try making it bigger (if more than 200 bytes will not work
  printime(6,3,59);
  printflush();    //This function is used at the end of the program.
                   //if some info was left in the buffer we have to send it.

}

void printI(int num){
  if (strlen(buffer)<Buffer_length-6)      //we add the int value only if we have enough space!
  {
    sprintf(buffer,"%s%d",buffer,num); //we add the int value to the buffer
  }
  else {
    Serial.println(buffer); //we send the buffer though the net
    buffer[0]='\0'; //we reset the buffer
  }
}
void printC(char info[]){
  int length_array1=strlen(buffer);
  int length_array2=strlen(info);
  if (Buffer_length-1>length_array1+length_array2)
  {
    strcat(buffer,info);
  }
  else {
    Serial.println(buffer);
    buffer[0]='\0';
  }
}

void printime(int h, int m, int s) { //size 8 bytes
  if (strlen(buffer)<Buffer_length-9)
  {
    sprintf(buffer,"%s%02d:%02d:%02d",buffer,h,m,s);
  }
  else {   
    Serial.println(buffer);
    buffer[0]='\0';
  }
}

void printdate (int d, int m, int y) { //size 10 bytes
  if (strlen(buffer)<Buffer_length-11)
  {
    sprintf(buffer,"%s%02d:%02d:%02d",buffer,d,m,y);
  }
  else {   
    Serial.println(buffer);
    buffer[0]='\0';
  }
}

void printflush(){  //we send any information left
  Serial.println(buffer);
  buffer[0]='\0';
}
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 5
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Ok, So I finally came up with something interesting.

As I said before, each time we call
Code:
client.print(blabla);
we are sending 1 TCP packet. So if I want to make an HTML table like the  web server example included in the compiler, we are going to send a lot of small packets. Specially each time we send an int number (just 2 bytes).
The header of the TCP packet is about 20 bytes so it's a lot of overhead for sending small amounts of info.

By implementing the functions I wrote before on the web server example, I reduced the time in a 33% (I changed the table from 5 to 150 entries).

Take care about the buffer size because of 2 reasons:
1) It has to be bigger than the info we want to put inside.
2) For reducing the time of refreshing the web page, I found an optimum buffer of 60 Bytes.

I though that a bigger buffer will be even better, but it's not. Maybe the time of writing/reading the data in the buffer takes more time than the overhead of the TCP protocol. Any way, here is the example with the implemented functions. Try to change the buffer size and refresh many time the web page and you will see the effect.

Hope it will be useful for someone!

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

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,1, 177);

const int Buffer_length = 60;         //Optimum buffer size
char buffer[Buffer_length];  //buffer for TCP packets

// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);

void setup()
{
  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.begin(9600);
}

void loop()
{
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank) {
          int inicio=millis();
          // send a standard http response header
          printC("HTTP/1.1 200 OK\n",client);
          printC("Content-Type: text/html\n",client);
          printC("\n",client);

          // output the value of each analog input pin
          for (int analogChannel = 0; analogChannel < 150; analogChannel++) {
            printC("analog input ",client);
            printI(analogChannel,client);
            printC(" is ",client);
            printI(analogChannel*100,client);
            printC("<br />",client);
          }
          printflush(client);
          int fin=millis();
          Serial.print("Elapsed time= ");
          Serial.println(fin-inicio);
          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        }
        else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
  }
}

void printI(int num, EthernetClient client){
  if (strlen(buffer)<Buffer_length-6)      //we add the int value only if we have enough space!
  {
    sprintf(buffer,"%s%d",buffer,num); //we add the int value to the buffer
//    Serial.print("agrege int a buffer= "); Serial.println(num);
  }
  else {
    client.println(buffer); //we send the buffer though the net
//    Serial.println(buffer);
    buffer[0]='\0'; //we reset the buffer
    sprintf(buffer,"%s%d",buffer,num);
//    Serial.println("mande el buffer");
  }
}
void printC(char info[], EthernetClient client){
  int length_array1=strlen(buffer);
  int length_array2=strlen(info);
  if (Buffer_length-1>length_array1+length_array2)
  {
    strcat(buffer,info);
//    Serial.println("agrege char a buffer");
  }
  else {
    client.println(buffer);
//    Serial.println("mande el buffer");
//    Serial.println(buffer);
    buffer[0]='\0';
    strcat(buffer,info);
  }
}

void printime(int h, int m, int s, EthernetClient client) { //size 8 bytes
  if (strlen(buffer)<Buffer_length-9)
  {
    sprintf(buffer,"%s%02d:%02d:%02d",buffer,h,m,s);
  }
  else {   
    client.println(buffer);
    buffer[0]='\0';
    sprintf(buffer,"%s%02d:%02d:%02d",buffer,h,m,s);
  }
}

void printdate (int d, int m, int y, EthernetClient client) { //size 10 bytes
  if (strlen(buffer)<Buffer_length-11)
  {
    sprintf(buffer,"%s%02d:%02d:%02d",buffer,d,m,y);
  }
  else {   
    client.println(buffer);
    buffer[0]='\0';
    sprintf(buffer,"%s%02d:%02d:%02d",buffer,d,m,y);
  }
}

void printflush(EthernetClient client){  //we send any information left
  client.println(buffer);
//  Serial.println("flushed el buffer");
//  Serial.println(buffer);
  buffer[0]='\0';
}
Logged

Pages: [1]   Go Up
Jump to: