Arduino SD Card Web Server – Linking Pages

Created on: 2 March 2013

Part 10 of the Arduino Ethernet Shield Web Server Tutorial

This part of the Arduino Ethernet shield web server tutorial shows how to create links between web pages that are hosted on the micro SD card of the Arduino web server.

These are links on a hosted web page that can be clicked in order to go to or open a different web page.

Creating Links in HTML

Links are created in HTML by using the HTML <a> tag. Text between the opening <a> tag and closing </a> tag becomes a clickable link on the web page.

The value of the href attribute of the <a> tag must contain the file name of the web page that is linked to, e.g.:

<p>Go to <a href="page2.htm">page 2</a>.</p>

The above line of HTML will create a paragraph of text with the page 2 part of the paragraph becoming a link to a file called page2.htm.

The file page2.htm must exist and also be in the same directory as the page that contains the link to it.

Example HTML Files

Two HTML files will be used as examples in this part of the tutorial. They must be saved to the micro SD card and the micro SD card must be plugged into the Ethernet shield.

The main page that will be loaded first from the server is made from the following HTML code (file name is index.htm):

<!DOCTYPE html>
<html>
    <head>
        <title>Arduino SD Card Web Page</title>
    </head>
    <body>
        <h1>Arduino SD Card Page with Link</h1>
        <p>Go to <a href="page2.htm">page 2</a>.</p>
    </body>
</html>

The above page links to a second page called page2.htm:

<!DOCTYPE html>
<html>
    <head>
        <title>Arduino SD Card Web Page 2</title>
    </head>
    <body>
        <h1>Arduino SD Card Page 2</h1>
        <p>Go back to <a href="index.htm">main page</a>.</p>
    </body>
</html>

page2.htm links back to the main page index.htm.

Create the above two files (index.htm and page2.htm) and copy them to your micro SD card. Insert the micro SD card into the Ethernet shield micro SD card holder.

These pages can be tested on a computer (with the two files in the same folder on the hard-drive) by opening index.htm in a browser and clicking the link. page2.htm should open when the link is clicked. Clicking the link on page2.htm should send the browser back to index.htm.

This video shows how to copy the web page files to the SD card and then shows the Arduino hosting the pages. The sketch is described below in this part of the tutorial.

HTTP Page Requests

When a web browser first requests a page from the Arduino web server, it sends an HTTP request similar to this:

GET / HTTP/1.1
Host: 10.0.0.20
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:19.0) Gecko/20100101 Firefox/19.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-ZA,en-GB;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive

We have already seen this HTTP request in previous parts of this tutorial.

When the link on the page is clicked (the link on the index.htm page to the page2.htm page in our example), the web browser sends the following HTTP request:

GET /page2.htm HTTP/1.1
Host: 10.0.0.20
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:19.0) Gecko/20100101 Firefox/19.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-ZA,en-GB;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://10.0.0.20/
Connection: keep-alive

So the initial HTTP request contains a GET request for the root file: GET / (this would be our index.htm file).

When the link is clicked, the request is now for a specific page: GET /page2.htm – now we know that we must check the HTTP request to see whether it is requesting the root file or a specific file that was linked to. This check will be done in the Arduino sketch.

Arduino Sketch for Linked Web Pages on Web Server

The sketch below is a modified version of the first SD card web server from part 4 of this series.

The eth_websrv_SD_link Arduino sketch:

/*--------------------------------------------------------------
  Program:      eth_websrv_SD_link

  Description:  Arduino web server that serves up a basic web
                page that links to a second page. Clicking the
                link will open the second page. The second page
                links back to the first page.
  
  Hardware:     Arduino Uno and official Arduino Ethernet
                shield. Should work with other Arduinos and
                compatible Ethernet shields.
                2Gb micro SD card formatted FAT16
                
  Software:     Developed using Arduino 1.0.5 software
                Should be compatible with Arduino 1.0 +
                
                Requires index.htm and page2.htm to be on the
                micro SD card in the Ethernet shield micro
                SD card socket.
  
  References:   - WebServer example by David A. Mellis and 
                  modified by Tom Igoe
                - SD card examples by David A. Mellis and
                  Tom Igoe
                - Ethernet library documentation:
                  http://arduino.cc/en/Reference/Ethernet
                - SD Card library documentation:
                  http://arduino.cc/en/Reference/SD

  Date:         2 March 2013
  Modified:     14 June 2013
                - removed use of String class, used too much SRAM
                - added StrClear() and StrContains() functions
                - disable Ethernet chip at startup
 
  Author:       W.A. Smith, http://startingelectronics.org
--------------------------------------------------------------*/

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

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

// MAC address from Ethernet shield sticker under board
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 20);   // IP address, may need to change depending on network
EthernetServer server(80);       // create a server at port 80
File webFile;                    // handle to files on SD card
char HTTP_req[REQ_BUF_SZ] = {0}; // buffered HTTP request stored as null terminated string
char req_index = 0;              // index into HTTP_req buffer

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.");

    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++;
                }
                Serial.print(c);    // print HTTP request character to serial monitor
                // 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");
                    client.println("Content-Type: text/html");
                    client.println("Connnection: close");
                    client.println();
                    // open requested web page file
                    if (StrContains(HTTP_req, "GET / ")
                                 || StrContains(HTTP_req, "GET /index.htm")) {
                        webFile = SD.open("index.htm");        // open web page file
                    }
                    else if (StrContains(HTTP_req, "GET /page2.htm")) {
                        webFile = SD.open("page2.htm");        // open web page file
                    }
                    // send web page to client
                    if (webFile) {
                        while(webFile.available()) {
                            client.write(webFile.read());
                        }
                        webFile.close();
                    }
                    // 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)
}

// 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;
}

NOTE: The IP address is set to 192.168.0.20 in this sketch and not 10.0.0.20 as in other sketches in this tutorial, so change it for your system if necessary.

The changes to the original SD card sketch from part 4 are described below.

HTTP Request

The sketch was modified to store the HTTP request from the web browser in the string HTTP_req. This string can then be searched to find out which page is being requested.

The HTTP request is sent out of the serial port and can be viewed in the Arduino serial monitor window for diagnostics and debugging purposes.

Sending the Correct Web Page

After the Arduino has received the HTTP request from the browser, it responds with a standard HTTP header and then sends the requested web page.

The code that selects which web page to send is shown here:

// open requested web page file
if (StrContains(HTTP_req, "GET / ")
             || StrContains(HTTP_req, "GET /index.htm")) {
    webFile = SD.open("index.htm");        // open web page file
}
else if (StrContains(HTTP_req, "GET /page2.htm")) {
    webFile = SD.open("page2.htm");        // open web page file
}

All this code does is open either index.htm or page2.htm from the SD card. The code that sends the file is the same as the code from part 4 of this series.

The code to select the correct file looks at the received HTTP request using the StrContains() function. HTTP_req is the string in our sketch that contains the HTTP request. If the HTTP request contains "GET / ", then this is a request for our root file index.htm.

If the HTTP request string contains "GET /page2.htm", then page2.htm will be opened and sent to the web browser.

When the link on page2.htm is clicked, it links back to index.htm and not /. This is the reason for checking if the HTTP request contains "GET / " or "GET /index.htm" in the first if statement in the above code listing.

Sketch Improvements

The above sketch is used to demonstrate the mechanism for opening page links on the Arduino web server, so was kept simple. Some improvements that could be made to the code would be firstly to extract the file name after the GET in the HTTP request and then open the file without checking for the specific name in the code. A second improvement would be to handle the case where a page is requested by the browser, but it does not exist on the SD card.