Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT

Simple Arduino Web Server with Ethernet Shield

4.33/5 (8 votes)
23 May 2017CPOL5 min read 33.4K   513  
Basic implementation of HTTP request handling for Arduino with Wiznet 5100 Ethernet shield

Introduction

Here's a dead simple web server for the Arduino with Wiznet 5100 Ethernet shield. You can load this sketch and begin receiving requests immediately!

Image 1

  • Implements IPV4. This example uses TCP/IP.
  • Listens on port 80, the standard TCP port for unencrypted HTTP traffic. The device responds at http://192.168.1.23/.
  • The default gateway is disabled by default. This means the device will only respond to requests from your local/private network.
  • If for some reason you want it to respond to Internet requests, you can map a port through your router and set the default gateway to 192.168.1.1. The device will then respond through the router. For more info, do a search for "port address translation."
  • Client requests and response codes are written to the console with 115200 baud.
  • This web server is appropriate for text only, so any images must be hosted on a REAL web server (or use a more powerful Arduino).
  • Keep realistic expectations. If your project exceeds basic automation requirements, consider a full embedded system or server operating system.

by Mark Scammacca

What You Need

(Total cost about $10)

  1. Arduino Uno - These are available everywhere. Find one on eBay for a few bucks.
    Important: Pick one that has the micro USB connector so that the Wiznet 5100 ethernet shield will fit over it easily.
  2. Wiznet 5100 Ethernet Shield for Arduino - these are all over eBay as well.

    Optional: Pick one that is POE (Power over Ethernet) capable. The POE board must be purchased separately and soldered on, but then your POE switch can power all of your Arduino devices directly from the Ethernet cable.

    Important: There are other types of ethernet shields, and this code works only with the Wiznet compatible ones. But the general principal will work with some minor modifications with any ethernet shield.

  3. Arduino development software (free) - Install it completely free. There are no dependencies required for this project besides what is already in the development environment. No chip programmer needed - programming is done over USB. The USB port also acts as a serial port when the Arduino software is installed, so you can monitor the console output from this code with Putty or through the built in serial console in the Arduino software.

Power over Ethernet

The Wiznet 5100 shield is available with Power over Ethernet, but this requires an add-on board, purchased separately. Some models have the solder connections available to accept the POE add-on board, and some do not. Check carefully. I have seen them listed as "POE Ethernet Shields" but they mean "POE capable, sold separately." The device can then be powered by a POE switch or a POE injector.

If you are more curious about POE and picking the right kind of switch, there are three main standards to be aware of:

  1. IEEE 802.3af-2003
  2. IEEE 802.3at-2009 (more power and should be backwards compatible with af devices)
  3. Proprietary - usually cheapest, and sometimes incompatible with 802.3af or 802.3at devices, having smokey results. Sometimes includes a matching terminator.

Also look into POE splitters. These simplify connecting non-POE devices to POE switches because they provide a barrel plug output. The POE splitter takes the power off the line and supplies it via barrel plug to the device. They accept 48V POE power and reduce it to 5VDC, 12VDC, etc. I have had great luck with them.

Security Considerations

The security of this code has not been reviewed -- so connecting it directly to the Internet is probably a BAD idea. The default gateway is set to 0.0.0.0 for this reason. If you choose to connect it to your router, set the default gateway to 192.168.1.1 (or whatever your router's IP address is) and Google "Port Address Translation".

Do not use this code in a real product without carefully securing the device against hacking. People commonly hack IoT devices once they capture enough attention to make hacking them worthwhile.

Using the Code

After installing Arduino software, simply connect the USB and load this sketch to your Arduino device. The device and Ethernet shield can be powered by USB.

The requests are handled in the void respond() function.

The default page is just a splash page with a hit counter: http://192.168.1.23/

The test page shows you how to pass in args and write them back in the response: http://192.168.1.23/test.html

Each request must be handled in full before the Arduino can respond to the next request, so make them short.

(Just highlight the code and copy/paste. Do not use the "Copy code" button or the HTML elements will be broken when you paste.)

C++
/*
  Board: Duemilanove or Diecimila
  Processor: ATmega328
  Programmer: AVR-ISP mkII

  UNO 5V BOARD
  and Wiznet 5100 Ethernet Shield
  
  X0 - RX - FTDI
  X1 - TX - FTDI
  2  DIG02 - 
  3  DIG03 - 
  4  DIG04 - SD CARD             / RESERVED
  5  TIMR1 - 
  6  DIG06 -
  7  DIG07 - 
  8  DIG08 - 
  9  DIG09 - 
  10 DIG10 - SPI ETHERNET SS     / RESERVED
  11 DIG11 - SPI ETHERNET MOSI   / RESERVED
  12 DIG12 - SPI ETHERNET MISO   / RESERVED
  13 DIG13 - SPI ETHERNET SCK    / RESERVED (LED FLICKERS DURING SPI COMM)
  22 ADC00 - 
  23 ADC01 - 
  24 ADC02 - 
  25 ADC03 - 
  26 ADC04 - 
  27 ADC05 - 
  18? ADC06 - 
  21? ADC07 - 
*/

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

/*
  MACRO for string handling from PROGMEM
  https://todbot.com/blog/2008/06/19/how-to-do-big-strings-in-arduino/
  max 149 chars at once ...
*/
char p_buffer[150];
#define P(str) (strcpy_P(p_buffer, PSTR(str)), p_buffer)

// Ethernet Interface Settings
byte mac[] =      { 0xAD, 0xDE, 0xEF, 0xBB, 0xFD, 0xDD };
IPAddress ip      (192, 168, 1, 23);  // Private static IP address
IPAddress myDns   (192, 168, 1, 1);   // DNS is not needed for this example
IPAddress gateway (0, 0, 0, 0);       // Default gateway disabled for security reasons
IPAddress subnet  (255, 255, 255, 0); // Class C subnet; typical

// HTTP lives on TCP port 80
EthernetServer server(80);

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

// string buffers for receiving URL and arguments
char bufferUrl[256];
char bufferArgs[512];
int urlChars = 0;
int argChars = 0;

// number of characters read on the current line
int lineChars = 0;

// total # requests serviced
long requests = 0;

// connection state while receiving a request
int state = 0;

/*
  Typical request: GET /<request goes here>?firstArg=1&anotherArg=2 HTTP/1.1
  State 0 - connection opened
  State 1 - receiving URL
  State 2 - receiving Arguments
  State 3 - arguments and/or URL finished
  State 4 - client has ended request, waiting for server to respond
  State 5 - server has responded
  
  Example of what the server receives:
  
  GET /test.html HTTP/1.1
  Host: 192.168.1.23
  Connection: keep-alive
  Cache-Control: max-age=0
  Upgrade-Insecure-Requests: 1
  User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 
  (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
  Accept-Encoding: gzip, deflate, sdch
  Accept-Language: en-US,en;q=0.8
*/

void loop()
{
  // listen for incoming clients
  EthernetClient client = server.available();
  
  if (client) 
  {
    state = 0;
    urlChars = 0;
    argChars = 0;
    lineChars = 0;
    bufferUrl[0] = 0;
    bufferArgs[0] = 0;

    while (client.connected()) 
    {
      if (client.available()) 
      {
        // read and echo data received from the client
        char c = client.read();
        Serial.print(c);

        // ignore \r carriage returns, we only care about \n new lines
        if (c == '\r')
          continue;        
          
        // control what happens to the buffer:
        if (state == 0 && c == '/')
        {
          // Begin receiving URL
          state = 1; 
        }
        else if (state == 1 && c == '?')
        {
          // Begin receiving args
          state = 2;
        }
        else if ((state == 1 || state == 2) && c == ' ')
        {
          // Received full request URL and/or args
          state = 3;
        }
        else if (state == 1 && urlChars < 255)
        {
            // Receiving URL (allow up to 255 chars + null terminator)
            bufferUrl[urlChars++] = c;
            bufferUrl[urlChars] = 0;
        }
        else if (state == 2 && argChars < 511)
        {
            // Receiving Args (allow up to 511 chars + null terminator)
            bufferArgs[argChars++] = c;
            bufferArgs[argChars] = 0;
        }
        else if (state == 3 && c == '\n' && lineChars == 0)
        {
          // Received a line with no characters; 
          // this means the client has ended their request
          state = 4;
        }

        // record how many characters on the line so far:
        if (c == '\n')
          lineChars = 0;
        else
          lineChars++;

        // OK to respond
        if(state == 4)
        {
          // Response given
          state = 5; 

          // increment internally for fun purposes
          requests++;
          Serial.print(P("Request # "));
          Serial.print(requests);
          Serial.print(P(": "));
          Serial.println(bufferUrl);

          // handle the response
          respond(client);

          // exit the loop
          break;
        }
      }
    }
    
    // flush and close the connection:
    client.flush();
    client.stop();
  }
}

void respond(EthernetClient client)
{
  if (strcmp(bufferUrl, P("")) == 0)
  {
    // Requested: /  (DEFAULT PAGE)
    
    // send response header
    sendHttpResponseOk(client);

    // send html page
    // max length: ------------------------------------------------------  (149 chars)
    client.println(P("<HTML><head><title>Welcome</title></head><body><h1>Welcome, visitor"));
    client.print(requests);
    client.println(P("!</h1>Click here to visit the <a href=/test.html>Test Page</a><p>"));
    client.println(P("String output is stored in progmem to conserve RAM. 
    That's what all the P( ) stuff is about. Buffer is big enough for 149 chars at once. "));
    client.println(P("Be careful not to exceed!<p><font color=red>
    This web server is not secured for public access. Use at your own risk.</font> "));
    client.println(P("If you want to use this in an actual product, 
    at least leave the gateway IP setting disabled. 
    You should consider a design where the Arduino acts"));
    client.println(P("as the client, or maybe a design where the Arduino 
    can only be contacted by a more fully-secured server.
    <p>Requests are echoed to the console"));
    client.println(P("with baud rate of 115200. Have fun!<p>
    <img src='https://cdn.meme.am/instances/250x250/54595677.jpg'/></body></html>"));
  }
  else if (strcmp(bufferUrl, P("test.html")) == 0)
  {
    // Requested: test.html
    
    // send response header
    sendHttpResponseOk(client);

    // send html page
    client.println(P("<HTML><head><title>Test Page</title>
    </head><body><h1>Test Page</h1>"));
    client.println(P("<br><b>Resource:</b> "));
    client.println(bufferUrl);
    client.println(P("<br><b>Arguments:</b> "));
    client.println(bufferArgs);
    // max length: ------------------------------------------------  (149 chars)
    client.println(P("<br><br><form action='/test.html?' 
    method='GET'>Test arguments: <input type=text name='arg1'/> 
    <input type=submit value='GET'/></form>"));
    client.println(P("</body></html>"));
  }
  else
  {
    // All other requests - 404 not found
    
    // send 404 not found header (oops)
    sendHttp404(client);
    client.println(P("<HTML><head><title>Resource not found
    </title></head><body><h1>The requested resource was not found</h1>"));
    client.println(P("<br><b>Resource:</b> "));
    client.println(bufferUrl);
    client.println(P("<br><b>Arguments:</b> "));
    client.println(bufferArgs);
    client.println(P("</body></html>"));
  }
}

// 200 OK means the resource was located on the server and the browser 
// (or service consumer) should expect a happy response
void sendHttpResponseOk(EthernetClient client)
{
  Serial.println(P("200 OK"));
  Serial.println();
  
  // send a standard http response header
  client.println(P("HTTP/1.1 200 OK"));
  client.println(P("Content-Type: text/html"));
  client.println(P("Connnection: close")); // do not reuse connection
  client.println();
}

// 404 means it ain't here. quit asking.
void sendHttp404(EthernetClient client)
{
  Serial.println(P("404 Not Found"));
  Serial.println();
  
  client.println(P("HTTP/1.1 404 Not Found"));
  client.println(P("Content-Type: text/html"));
  client.println(P("Connnection: close")); // do not reuse connection
  client.println();
}

Example Output

Image 2

Image 3

That's It!

You can read or manipulate digital IO pins or do anything you would normally do.

Memory Usage Note

Since strings take up valuable RAM space, I've referenced a Macro which stores and retrieves these from PROGMEM. This allows you to make fairly large web pages for such a tiny processor (shame on you!) and store them in abundant flash memory without sacrificing all of your RAM.

The trade-off is that you MUST remember to limit each line of text to 149 chars or less, or the buffer which reads the strings from PROGMEM will overflow, and your code will have strange bugs.

If you require more memory, consider an ATmega or something beefier, or an SD Card (there are other implementations of HTTP for Arduino which use this).

History

  • 20th May, 2017: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)