New WebServerST code with Ajax and XML - HOW ?

Hello !

This time I’m having a little problem with combining two functionalities from two codes.
Till today, I was using web serwer code from this tutorial : http://startingelectronics.com/tutorials/arduino/ethernet-shield-web-server-tutorial/SD-card-AJAX-XML-web-server/

My arduino is sending 14 floats and 24 boolean variables to the web. There is 4 files on SD card. With this code everything works ok but it is SOOOOOOO SLOOOOW…

Here is an example from this tutorial :

/*--------------------------------------------------------------
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>

// size of buffer used to capture HTTP requests
#define REQ_BUF_SZ   50

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 20);
EthernetServer server(80);  
File webFile;               
char HTTP_req[REQ_BUF_SZ] = {0}; 
char req_index = 0;          

void setup()
{
    // disable Ethernet chip
    pinMode(10, OUTPUT);
    digitalWrite(10, HIGH);
    
    Serial.begin(9600);       // for debugging
    
    // initialize SD card
    Serial.println("Initializing SD card...");
    if (!SD.begin(4)) {
        Serial.println("ERROR - SD card initialization failed!");
        return;    // init failed
    }
    Serial.println("SUCCESS - SD card initialized.");
    // check for index.htm file
    if (!SD.exists("index.htm")) {
        Serial.println("ERROR - Can't find index.htm file!");
        return;  // can't find index file
    }
    Serial.println("SUCCESS - Found index.htm file.");
    pinMode(7, INPUT);        // switch is attached to Arduino pin 7
    pinMode(8, INPUT);        // switch is attached to Arduino pin 8
    
    Ethernet.begin(mac, ip);  // initialize Ethernet device
    server.begin();           // start to listen for clients
}

void loop()
{
    EthernetClient client = server.available();  // try to get client

    if (client) {  // got client?
        boolean currentLineIsBlank = true;
        while (client.connected()) {
            if (client.available()) {   // client data available to read
                char c = client.read(); // read 1 byte (character) from client
                // buffer first part of HTTP request in HTTP_req array (string)
                // leave last element in array as 0 to null terminate string (REQ_BUF_SZ - 1)
                if (req_index < (REQ_BUF_SZ - 1)) {
                    HTTP_req[req_index] = c;          // save HTTP request character
                    req_index++;
                }
                // last line of client request is blank and ends with \n
                // respond to client only after last line received
                if (c == '\n' && currentLineIsBlank) {
                    // send a standard http response header
                    client.println("HTTP/1.1 200 OK");
                    // remainder of header follows below, depending on if
                    // web page or XML page is requested
                    // Ajax request - send XML file
                    if (StrContains(HTTP_req, "ajax_inputs")) {
                        // send rest of HTTP header
                        client.println("Content-Type: text/xml");
                        client.println("Connection: keep-alive");
                        client.println();
                        // send XML file containing input states
                        XML_response(client);
                    }
                    else {  // web page request
                        // send rest of HTTP header
                        client.println("Content-Type: text/html");
                        client.println("Connection: keep-alive");
                        client.println();
                        // send web page
                        webFile = SD.open("index.htm");        // open web page file
                        if (webFile) {
                            while(webFile.available()) {
                                client.write(webFile.read()); // send web page to client
                            }
                            webFile.close();
                        }
                    }
                    // display received HTTP request on serial port
                    Serial.print(HTTP_req);
                    // reset buffer index and all buffer elements to 0
                    req_index = 0;
                    StrClear(HTTP_req, REQ_BUF_SZ);
                    break;
                }
                // 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;
                }
            } // end if (client.available())
        } // end while (client.connected())
        delay(1);      // give the web browser time to receive the data
        client.stop(); // close the connection
    } // end if (client)
}

// send the XML file with switch statuses and analog value
void XML_response(EthernetClient cl)
{
    int analog_val;
    
    cl.print("<?xml version = \"1.0\" ?>");
    cl.print("<inputs>");
    cl.print("<button1>");
    if (digitalRead(7)) {
        cl.print("ON");
    }
    else {
        cl.print("OFF");
    }
    cl.print("</button1>");
    cl.print("<button2>");
    if (digitalRead(8)) {
        cl.print("ON");
    }
    else {
        cl.print("OFF");
    }
    cl.print("</button2>");
    // read analog pin A2
    analog_val = analogRead(2);
    cl.print("<analog1>");
    cl.print(analog_val);
    cl.print("</analog1>");
    cl.print("</inputs>");
}

// sets every element of str to 0 (clears array)
void StrClear(char *str, char length)
{
    for (int i = 0; i < length; i++) {
        str[i] = 0;
    }
}

// searches for the string sfind in the string str
// returns 1 if string found
// returns 0 if string not found
char StrContains(char *str, char *sfind)
{
    char found = 0;
    char index = 0;
    char len;

    len = strlen(str);
    
    if (strlen(sfind) > len) {
        return 0;
    }
    while (index < len) {
        if (str[index] == sfind[found]) {
            found++;
            if (strlen(sfind) == found) {
                return 1;
            }
        }
        else {
            found = 0;
        }
        index++;
    }

    return 0;
}

Today I found web server code written by SurferTim :http://playground.arduino.cc/Code/WebServerST

And it is incredibly fast ! I tried to adapt it to my needs and I made arduino serving my 4 pages with no problem. But I cannot get ajax / xml part to work. I’m not sure where and how should I place this piece of code from tutorial in Tim’s code :

if (StrContains(HTTP_req, "ajax_inputs")) {
                        // send rest of HTTP header
                        client.println("Content-Type: text/xml");
                        client.println("Connection: keep-alive");
                        client.println();
                        // send XML file containing input states
                        XML_response(client);
                    }

I have added XML file type in Tim’s original code.

In Tim’s code, there is :

if(strcmp(requestBuffer,"/MYTEST.PHP") == 0) {
#ifdef ServerDEBUG
            Serial.println(F("dynamic page"));            
#endif

And I channged it to :

if (strcmp(requestBuffer,"ajax_inputs") == 0) {
            strcat_P(tBuf, PSTR("Connection: keep-alive"));
            client.write(tBuf); 
            strcat_P(tBuf, PSTR("\r\n\r\n"));
                client.write(tBuf);

I tried many things but all I’m getting in serial debug messages is :“File not found” “Closed” . I can see Ajax requests every second but server cant find XML file ( because it’s not on SD card )

Could anyone tell me, how to combine this “virtual” XML file with Tim’s code or is there different way to refresh automaticaly about 40 variables on web page without reloading the page itself? Ajax approach seems perfect for this but I really need faster responding server ( WebServerST code is very fast ) .
I’m using arduino Mega and WIZ W5100.
I can’t post my whole code because is quite large.
Thanks in advance!

If you look at my code, you will see all requests are converted to upper case, so you must use the strcmp with upper case.

if (strcmp(requestBuffer,"AJAX_INPUTS") == 0) {

edit: It may also require a slash.

if (strcmp(requestBuffer,"/AJAX_INPUTS") == 0) {

Hmm.. this is one thing to try. I'll test it this evening.
So you recon it is not a problem with the fact, that program is looking for a physical XML file on SD card ?

Thank you for your help ,Tim!

If it requests the file ajax_inputs, it will work. If it is a POST request, you will need to retrieve the data sent after the blank line.

It does require upper case and a slash in my test.

Now i see another “problem” : in oryginal code that I used, arduino is checking if received client response CONTAINS String “ajax_inputs”

if (StrContains(HTTP_req, "ajax_inputs")) {
                        // send rest of HTTP header
                        client.println("Content-Type: text/xml");
                        client.println("Connection: keep-alive");
                        client.println();
                        // send XML file containing input states
                        XML_response(client);
                    }

and your code is comparing two strings and returning 0 if they match :

if (strcmp(requestBuffer,"AJAX_INPUTS") == 0) {

But when I look at web page code and client response i see that client is sending string like :

GET/ajax_inputs+100234468739

This is because nocache function in web page code :

       <script>
        function GetArduinoInputs()
        {
            nocache = "&nocache=" + Math.random() * 1000000;
            var request = new XMLHttpRequest();
            request.onreadystatechange = function()
            {
                if (this.readyState == 4) {
                    if (this.status == 200) {
                        if (this.responseXML != null) {
                            // extract XML data from XML file (containing switch states and analog value)
                            document.getElementById("input1").innerHTML =
                                this.responseXML.getElementsByTagName('button1')[0].childNodes[0].nodeValue;
                            document.getElementById("input2").innerHTML =
                                this.responseXML.getElementsByTagName('button2')[0].childNodes[0].nodeValue;
                            document.getElementById("input3").innerHTML =
                                this.responseXML.getElementsByTagName('analog1')[0].childNodes[0].nodeValue;
                        }
                    }
                }
            }
            request.open("GET", "ajax_inputs" + nocache, true);                    // client response
            request.send(null);
            setTimeout('GetArduinoInputs()', 1000);
        }
    </script>

So, is it possible to use, for example, strncmp instead of strcmp ? Then I can limit count of chars to look for to 11 and discard the "+[nocache number] " part ?

Like so :

if (strncmp(requestBuffer,"/ajax_inputs", 11) == 0) {
            strcat_P(tBuf, PSTR("Content-Type: text/xml"));
            client.write(tBuf);
            strcat_P(tBuf, PSTR("Connection: keep-alive"));
            client.write(tBuf); 
            strcat_P(tBuf, PSTR("\r\n\r\n"));
                client.write(tBuf);                    
            XML_response(client);
          }

After all changes made, I got this response in serial monitor :

Client request #105: GET /AJAX_INPUTS HTTP/1.1
file = /AJAX_INPUTS
file type = 
method = GET
params = 
protocol = HTTP/1.1
dynamic page
Sending response
disconnected

code :

         if (strncmp(requestBuffer,"/AJAX_INPUTS", 10) == 0) {
            strcat_P(tBuf, PSTR("\r\nContent-Type: text/xml"));
            client.write(tBuf);
            strcat_P(tBuf, PSTR("\r\nConnection: keep-alive"));
            client.write(tBuf); 
            strcat_P(tBuf, PSTR("\r\n\r\n"));
                client.write(tBuf);           // ?????????
            XML_response(client);
            #ifdef ServerDEBUG
            Serial.println(F("dynamic page"));            
#endif
          }

          
          else {
            if(strcmp(fileName,"/") == 0) {
              strcpy(fileName,"/INDEX.HTM");
              strcpy(fileType,"HTM");

#ifdef ServerDEBUG
              Serial.print(F("Home page "));            
#endif
            }

#ifdef ServerDEBUG
            Serial.println(F("SD file"));            
#endif
            if(strlen(fileName) > 35) {
#ifdef ServerDEBUG
              Serial.println(F("filename too long"));
#endif
              sendBadRequest(client);

              return;
            }
            else if(strlen(fileType) > 3 || strlen(fileType) < 1) {

#ifdef ServerDEBUG
              Serial.println(F("file type invalid size"));
#endif
              sendBadRequest(client);
              return;
            }
            else {
#ifdef ServerDEBUG
              Serial.println(F("filename format ok"));
#endif
              if(SD.exists(fileName)) {
#ifdef ServerDEBUG
                // SRAM check
                Serial.print(F("SRAM = "));
                Serial.println(freeRam());

                Serial.print(F("file found.."));                
#endif


                File myFile = SD.open(fileName);

                if(!myFile) {
#ifdef ServerDEBUG
                  Serial.println(F("open error"));
#endif
                  sendFileNotFound(client);
                  return;
                }
#ifdef ServerDEBUG
                else Serial.print(F("opened.."));
#endif

                strcpy_P(tBuf,PSTR("HTTP/1.0 200 OK\r\nContent-Type: "));
//                client.write(tBuf);
//                client.print(F("HTTP/1.0 200 OK\r\nContent-Type: "));

                // send Content-Type
                if(strcmp(fileType,"HTM") == 0) strcat_P(tBuf,PSTR("text/html"));
                else if(strcmp(fileType,"PHP") == 0) strcat_P(tBuf,PSTR("text/html"));
                else if(strcmp(fileType,"TXT") == 0) strcat_P(tBuf,PSTR("text/plain"));
                else if(strcmp(fileType,"CSS") == 0) strcat_P(tBuf,PSTR("text/css"));
                else if(strcmp(fileType,"GIF") == 0) strcat_P(tBuf,PSTR("image/gif"));
                else if(strcmp(fileType,"JPG") == 0) strcat_P(tBuf,PSTR("image/jpeg"));
                else if(strcmp(fileType,"JS") == 0) strcat_P(tBuf,PSTR("application/javascript"));
                else if(strcmp(fileType,"ICO") == 0) strcat_P(tBuf,PSTR("image/x-icon"));
                else if(strcmp(fileType,"PNG") == 0) strcat_P(tBuf,PSTR("image/png"));
                else if(strcmp(fileType,"PDF") == 0) strcat_P(tBuf,PSTR("application/pdf"));
                else if(strcmp(fileType,"ZIP") == 0) strcat_P(tBuf,PSTR("application/zip"));
                else if (strcmp(fileType,"XML") == 0) strcat_P(tBuf, PSTR("text/xml"));
                else strcat_P(tBuf,PSTR("text/plain"));

                strcat_P(tBuf,PSTR("\r\nConnection: close\r\n\r\n"));
                client.write(tBuf);

                if(strcmp(methodBuffer,"GET") == 0)  {
#ifdef ServerDEBUG
                  Serial.print(F("send.."));
#endif
                  while(myFile.available()) {
                    clientCount = myFile.read(tBuf,64);
                    client.write((byte*)tBuf,clientCount);
                  }
                }
                myFile.close();              
#ifdef ServerDEBUG
                Serial.println(F("closed"));
#endif
                client.stop();                
#ifdef ServerDEBUG
                Serial.println(F("disconnected"));
#endif
                return;
              }
              else {
#ifdef ServerDEBUG
                Serial.println(F("File not found"));
#endif
                sendFileNotFound(client);
                return;
              }

            }
          }

          pch = strtok(paramBuffer,"&");

          while(pch != NULL)
          {
            if(strncmp(pch,"t=",2) == 0)
            {
              t = atoi(pch+2);
#ifdef ServerDEBUG
              Serial.print("t=");
              Serial.println(t,DEC);             
#endif
            }

            if(strncmp(pch,"r=",2) == 0)
            {
              r = atoi(pch+2);
#ifdef ServerDEBUG
              Serial.print("r=");              
              Serial.println(r,DEC);
#endif
            }


            pch = strtok(NULL,"& ");
          }
#ifdef ServerDEBUG
          Serial.println(F("Sending response"));
#endif
 
          client.print(F("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"));

          if(strcmp(methodBuffer,"GET") == 0) {

            strcpy_P(tBuf,PSTR("<html><head><script type=\"text/javascript\">"));
            client.write(tBuf);
            strcpy_P(tBuf,PSTR("function show_alert() {alert(\"This is an alert\");}"));
            client.write(tBuf);
            strcpy_P(tBuf,PSTR("</script></head>"));
            client.write(tBuf);

            strcpy_P(tBuf,PSTR("<body><H1>TEST</H1><form method=GET onSubmit=\"show_alert()\">"));
            client.write(tBuf);
            strcpy_P(tBuf,PSTR("T: <input type=text name=t>
"));
            client.write(tBuf);
            strcpy_P(tBuf,PSTR("R: <input type=text name=r>
<input type=submit></form></body></html>"));
            client.write(tBuf);
          }

          client.stop();
        }
        else if (c == '\n') {
          currentLineIsBlank = true;
          currentLineIsGet = false;
        } 
        else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }

      loopCount++;

      if(loopCount > 1000) {
        // close connection
        client.stop();
#ifdef ServerDEBUG
        Serial.println("\r\nTimeout");
#endif
      }

   
     delay(1);
    }
#ifdef ServerDEBUG
    Serial.println(F("disconnected"));
#endif
  }
}

void sendFileNotFound(EthernetClient thisClient) {
  char tBuf[64];
  strcpy_P(tBuf,PSTR("HTTP/1.0 404 File Not Found\r\n"));
  thisClient.write(tBuf);
  strcpy_P(tBuf,PSTR("Content-Type: text/html\r\nConnection: close\r\n\r\n"));
  thisClient.write(tBuf);
  strcpy_P(tBuf,PSTR("<html><body><H1>FILE NOT FOUND</H1></body></html>"));
  thisClient.write(tBuf);
  thisClient.stop();  
#ifdef ServerDEBUG
  Serial.println(F("disconnected"));
#endif
}

void sendBadRequest(EthernetClient thisClient) {
  char tBuf[64];
  strcpy_P(tBuf,PSTR("HTTP/1.0 400 Bad Request\r\n"));
  thisClient.write(tBuf);
  strcpy_P(tBuf,PSTR("Content-Type: text/html\r\nConnection: close\r\n\r\n"));
  thisClient.write(tBuf);
  strcpy_P(tBuf,PSTR("<html><body><H1>BAD REQUEST</H1></body></html>"));
  thisClient.write(tBuf);
  thisClient.stop();  
#ifdef ServerDEBUG
  Serial.println(F("disconnected"));
#endif
}

void  strtoupper(char* aBuf) {

  for(int x = 0; x<strlen(aBuf);x++) {
    aBuf[x] = toupper(aBuf[x]);
  }
}


void XML_response(EthernetClient xml)
{
  char tBuf[64];
  strcpy_P(tBuf, PSTR("<?xml version = \"1.0\" ?>"));
  xml.write(tBuf);
  strcpy_P(tBuf, PSTR("<inputs>"));
  xml.write(tBuf);

  strcpy_P(tBuf, PSTR("<analog1>"));
  xml.write(tBuf);
  strcpy_P(tBuf, PSTR("1"));
  xml.write(tBuf);
  strcpy_P(tBuf, PSTR("</analog1>"));
  xml.write(tBuf);

  strcpy_P(tBuf, PSTR("<analog2>"));
  xml.write(tBuf);
  strcpy_P(tBuf, PSTR("2"));
  xml.write(tBuf);
  strcpy_P(tBuf, PSTR("</analog2>"));
  xml.write(tBuf);

  strcpy_P(tBuf, PSTR("</inputs>"));
  xml.write(tBuf);

}

Page is displayed but no values are shown. One thing I found : it doesn’t matter if i remove this from the code :

XML_response(client);

Response is still the same. So I think, arduino isn’t sending this part to web page. I’m really confused now …

I'm not an ajax user. Maybe there are others who can help you convert my code to handle ajax. It appears ajax uses a persistent connection, but I could be wrong.

SurferTim, you did a great job writting this web server code - It is fast and.. I can't get it to crash :wink:

Maybe you could recomend something else than Ajax? Some solution that will let me updade about 35 - 40 variables ( floats and chars ) without need of refreshing web page? Update speed once per second.

I can't get it to crash either, and I know a bunch of good things to try. :slight_smile:

Once per second is a challenge if you are planning on opening and closing a connection and sending/receiving data in between. It may be best to leave the connection open. The disadvantage of leaving the connection open with my code is only one client can connect at a time.

I have a telnet/persistent connection example on the playground also. It can handle multiple persistent connections, but it is not as easy to understand.

Allowing only one client to connect at a time is ok if server will not crash when semeone else will try to connect. Even one update per 2 seconsd is quite ok.

I'll have a look at your telnet example.

Thank you so much for your help and your excellent code !

For raw data without frills, meta refresh can be faster than you probably can read the data. The below supplies the arduino analog pin voltage values to a web page.

// zoomkat's meta refresh data frame test page 5/25/13
// use http://192.168.1.102:84 in your brouser for main page
// http://192.168.1.102:84/data static data page
// http://192.168.1.102:84/datastart meta refresh data page
// for use with W5100 based ethernet shields
// set the refresh rate to 0 for fastest update
// use STOP for single data updates

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

const int analogInPin0 = A0;
const int analogInPin1 = A1;
const int analogInPin2 = A2;
const int analogInPin3 = A3;
const int analogInPin4 = A4;
const int analogInPin5 = A5;

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address
byte ip[] = { 192, 168, 1, 102 }; // arduino 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
unsigned long int x=0; //set refresh counter to 0
String readString; 

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

void setup(){
  Serial.begin(9600);
    // disable SD SPI if memory card in the uSD slot
  pinMode(4,OUTPUT);
  digitalWrite(4,HIGH);

  Ethernet.begin(mac, ip, gateway, gateway, subnet);
  server.begin();
  Serial.println("meta refresh data frame test 5/25/13"); // so I can keep track of what is loaded
}

void loop(){
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
         if (readString.length() < 100) {
          readString += c; 
         } 
        //check if HTTP request has ended
        if (c == '\n') {

          //check get atring received
          Serial.println(readString);

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

          //generate data page
          if(readString.indexOf("data") >0) {  //checks for "data" page
            x=x+1; //page upload counter
            client.print("<HTML><HEAD>");
            //meta-refresh page every 1 seconds if "datastart" page
            if(readString.indexOf("datastart") >0) client.print("<meta http-equiv='refresh' content='1'>"); 
            //meta-refresh 0 for fast data
            if(readString.indexOf("datafast") >0) client.print("<meta http-equiv='refresh' content='0'>"); 
            client.print("<title>Zoomkat's meta-refresh test</title></head><BODY>
");
            client.print("page refresh number: ");
            client.print(x); //current refresh count
            client.print("

");
            
              //output the value of each analog input pin
            client.print("analog input0 is: ");
            client.print(analogRead(analogInPin0));
            
            client.print("
analog input1 is: ");
            client.print(analogRead(analogInPin1));
                        
            client.print("
analog input2 is: ");
            client.print(analogRead(analogInPin2));
            
            client.print("
analog input3 is: ");
            client.print(analogRead(analogInPin3));
                                    
            client.print("
analog input4 is: ");
            client.print(analogRead(analogInPin4));
            
            client.print("
analog input5 is: ");
            client.print(analogRead(analogInPin5));
            client.println("
</BODY></HTML>");
           }
          //generate main page with iframe
          else
          {
            client.print("<HTML><HEAD><TITLE>Zoomkat's frame refresh test</TITLE></HEAD>");
            client.print("Zoomkat's Arduino frame meta refresh test 5/25/13");
            client.print("

Arduino analog input data frame:
");
            client.print("&nbsp;&nbsp;<a href='http://192.168.1.102:84/datastart' target='DataBox' title=''yy''>META-REFRESH</a>");
            client.print("&nbsp;&nbsp;&nbsp;&nbsp;<a href='http://192.168.1.102:84/data' target='DataBox' title=''xx''>SINGLE-STOP</a>");
            client.print("&nbsp;&nbsp;&nbsp;&nbsp;<a href='http://192.168.1.102:84/datafast' target='DataBox' title=''zz''>FAST-DATA</a>
");
            client.print("<iframe src='http://192.168.1.102:84/data' width='350' height='250' name='DataBox'>");
            client.print("</iframe>
</HTML>");
          }
          delay(1);
          //stopping client
          client.stop();
          //clearing string for next read
          readString="";
        }
      }
    }
  }
}

zoomkat, thank you for your example but I allready tried something like this. I need to store web pages on SD card because I need 6 subpages in total and they are quite "large". So I'm looking for a way to "inject" variables to pages served from SD card ... update speed isn't so much important - it can be one update per 3..4..5 seconds.
I'm allready sending some values to Xively so I tried sendig all of them and use their java script in web files to read the values back but Xively limits the number and speed of updates .....

hello waski!
i am also trying to do the same experiment but i too struck up?
plz can you help me?
hve u finished this project?
plz reply!